Eliminar todos menos los números de NSString

157

Tengo un NSString (número de teléfono) con algunos paréntesis y guiones, ya que algunos números de teléfono están formateados. ¿Cómo eliminaría todos los caracteres, excepto los números de la cadena?

Ben Harris
fuente

Respuestas:

375

Antigua pregunta, pero ¿qué tal:

  NSString *newString = [[origString componentsSeparatedByCharactersInSet:
                [[NSCharacterSet decimalDigitCharacterSet] invertedSet]] 
                componentsJoinedByString:@""];

Explota la cadena de origen en el conjunto de no dígitos, luego los vuelve a ensamblar usando un separador de cadena vacío. No es tan eficiente como seleccionar caracteres, pero es mucho más compacto en código.

simonobo
fuente
66
¡Gracias! Para otros principiantes, puede crear su propio NSCharacterSet personalizado haciendoNSCharacterSet *myCharSet = [NSCharacterSet characterSetWithCharactersInString:@"charactersGoHere"]
guptron el
1
Genial gracias! Solo para mi curiosidad, ¿tienes una idea de por qué NSString *pureNumbers = [pureNumbers stringByTrimmingCharactersInSet: [NSCharacterSet decimalDigitCharacterSet] invertedSet]no funciona?
Thomas Besnehard
1
@Tommecpe stringByTrimmingCharactersInSet solo elimina desde el principio y el final de la cadena, por lo que no afecta después del primer carácter no coincidente o antes del último carácter no coincidente.
simonobo
Quiero mantener solo los números y el alfabeto, ¿cómo puedo hacerlo?
Jacky
1
@Jacky en el ejemplo anterior, lo reemplazaría [NSCharacterSet decimalDigitCharacterSet]con otro que solo contenga números y letras. Puede construir uno creando a NSMutableCharaterSety pasando a decimalDigitCharacterSet, uppercaseLetterCharacterSety lowercaseLetterCharacterSeta formUnionWithCharacterSet:. Tenga en cuenta que también letterCharacterSetincluye marcas, de ahí el uso de versiones en mayúsculas y minúsculas.
kadam
75

No es necesario usar una biblioteca de expresiones regulares como sugieren las otras respuestas: se llama a la clase que busca NSScanner. Se usa de la siguiente manera:

NSString *originalString = @"(123) 123123 abc";
NSMutableString *strippedString = [NSMutableString 
        stringWithCapacity:originalString.length];

NSScanner *scanner = [NSScanner scannerWithString:originalString];
NSCharacterSet *numbers = [NSCharacterSet 
        characterSetWithCharactersInString:@"0123456789"];

while ([scanner isAtEnd] == NO) {
  NSString *buffer;
  if ([scanner scanCharactersFromSet:numbers intoString:&buffer]) {
    [strippedString appendString:buffer];

  } else {
    [scanner setScanLocation:([scanner scanLocation] + 1)];
  }
}

NSLog(@"%@", strippedString); // "123123123"

EDITAR: He actualizado el código porque el original estaba escrito en la parte superior de mi cabeza y pensé que sería suficiente para señalar a las personas en la dirección correcta. Parece que las personas buscan el código, simplemente pueden copiar y pegar directamente en su aplicación.

También estoy de acuerdo en que la solución de Michael Pelz-Sherman es más apropiada que usarla NSScanner, por lo que es posible que desee echarle un vistazo.

Nathan de Vries
fuente
+1 Buena respuesta que aborda directamente la pregunta. Edité mi respuesta para defender este enfoque, pero dejo la segunda mitad tal como está, ya que todavía es útil), que aborda el problema del otro lado de formatear un número de teléfono para mostrarlo. (A continuación, ¿puede dejar un comentario constructivo al votar a favor, aunque solo sea para lectores posteriores?)
Quinn Taylor el
44
Puede ser útil saber que hay un método + decimalDigitCharacterSet de NSCharacterSet que le dará todos los dígitos decimales. Esto es ligeramente diferente al conjunto de listas de Nathan, porque incluye todos los símbolos que representan números decimales, incluidos, por ejemplo, dígitos árabes-índicos (١٢٣٤٥, etc.). Dependiendo de su aplicación, eso podría ocasionalmente ser un problema, pero generalmente es bueno o neutral, y un poco más corto de escribir.
Rob Napier el
Estoy bastante seguro de que esta respuesta en realidad no funciona, y no es realmente el enfoque correcto para el problema. Si realmente prueba el código como se muestra (primero agrega una @ antes del primer parámetro a NSLog, convirtiéndolo en una cadena objc), encontrará que imprime <null> o se bloquea. ¿Por qué? Vea mi respuesta a continuación.
Jack Nutting
No necesita otra respuesta, para eso están los comentarios. He actualizado la solución, incluida una referencia a la solución de Michael Pelz-Sherman.
Nathan de Vries
44
Eso es muuuuucho a complicado.
ryyst
63

La respuesta aceptada es exagerada para lo que se pregunta. Esto es mucho más simple:

NSString *pureNumbers = [[phoneNumberString componentsSeparatedByCharactersInSet:[[NSCharacterSet decimalDigitCharacterSet] invertedSet]] componentsJoinedByString:@""];
Yacine Filali
fuente
2
La respuesta (actualmente) aceptada es más o menos idéntica a esta, pero se publicó 13 meses antes.
Caleb
En el momento en que respondí esto, no tenía esta respuesta. Aunque parece que la respuesta actual ya se había propuesto y me la perdí: web.archive.org/web/20101115214033/http://stackoverflow.com/…
Yacine Filali
30

Esto es genial, pero el código no funciona para mí en el iPhone 3.0 SDK.

Si defino strippedString como se muestra aquí, recibo un mensaje BAD ACCESS erroral intentar imprimirlo después de la scanCharactersFromSet:intoStringllamada.

Si lo hago así:

NSMutableString *strippedString = [NSMutableString stringWithCapacity:10];

Termino con una cadena vacía, pero el código no se bloquea.

Tuve que recurrir a la buena C en su lugar:

for (int i=0; i<[phoneNumber length]; i++) {
    if (isdigit([phoneNumber characterAtIndex:i])) {
        [strippedString appendFormat:@"%c",[phoneNumber characterAtIndex:i]];
    }
}
Michael Pelz-Sherman
fuente
Estoy ejecutando 3.0 y esto funciona para mí. La respuesta más popular de Vries no funcionó.
Neo42
La respuesta número uno no funcionará para mí. El escáner se detiene una vez que alcanza un () o - ¡Esta respuesta funciona muy bien! Buena vieja C !! Gracias
Jeff
2
Solo tenga en cuenta que se debe permitir el carácter '+' para el número de teléfono.
Prcela
27

Aunque esta es una vieja pregunta con respuestas de trabajo, me perdí el soporte de formato internacional . Basado en la solución de simonobo, el conjunto de caracteres alterado incluye un signo más "+". Los números de teléfono internacionales también son compatibles con esta enmienda.

NSString *condensedPhoneNumber = [[phoneNumber componentsSeparatedByCharactersInSet:
              [[NSCharacterSet characterSetWithCharactersInString:@"+0123456789"]
              invertedSet]] 
              componentsJoinedByString:@""];

Las expresiones rápidas son

var phoneNumber = " +1 (234) 567-1000 "
var allowedCharactersSet = NSMutableCharacterSet.decimalDigitCharacterSet()
allowedCharactersSet.addCharactersInString("+")
var condensedPhoneNumber = phoneNumber.componentsSeparatedByCharactersInSet(allowedCharactersSet.invertedSet).joinWithSeparator("")

Lo que produce +12345671000 como un formato de número de teléfono internacional común.

alex
fuente
2
Esta es la mejor solución en la lista, especialmente si necesita el signo más para números de teléfono internacionales.
UXUiOS
Por alguna razón, usar un juego de caracteres invertido me asusta en lo que respecta al rendimiento. ¿Alguien sabe si esto es un miedo infundado?
devios1
Este funcionó! ¿Podría explicar que está funcionando? @alex
Jayprakash Dubey
11

Aquí está la versión rápida de esto.

import UIKit
import Foundation
var phoneNumber = " 1 (888) 555-5551    "
var strippedPhoneNumber = "".join(phoneNumber.componentsSeparatedByCharactersInSet(NSCharacterSet.decimalDigitCharacterSet().invertedSet))
Christopher Wade Cantley
fuente
Swift 2.0: phoneNumber.componentsSeparatedByCharactersInSet (NSCharacterSet.decimalDigitCharacterSet (). InvertedSet) .joinWithSeparator ("")
iluvatar_GR
11

Versión rápida de la respuesta más popular:

var newString = join("", oldString.componentsSeparatedByCharactersInSet(NSCharacterSet.decimalDigitCharacterSet().invertedSet))

Editar: sintaxis para Swift 2

let newString = oldString.componentsSeparatedByCharactersInSet(NSCharacterSet.decimalDigitCharacterSet().invertedSet).joinWithSeparator("")

Editar: sintaxis para Swift 3

let newString = oldString.components(separatedBy: CharacterSet.decimalDigits.inverted).joined(separator: "")
Jon Vogel
fuente
¿Hay alguna manera de mantener el símbolo separado por decimales? ¿El punto (o coma) en función de la configuración predeterminada del dispositivo? Su solución elimina todo menos los números
Nicholas
5

Gracias por el ejemplo Solo le falta una cosa al incremento de scanLocation en caso de que uno de los caracteres en originalString no se encuentre dentro del objeto NumberSet CharacterSet. He agregado una declaración else {} para arreglar esto.

NSString *originalString = @"(123) 123123 abc";
NSMutableString *strippedString = [NSMutableString 
        stringWithCapacity:originalString.length];

NSScanner *scanner = [NSScanner scannerWithString:originalString];
NSCharacterSet *numbers = [NSCharacterSet 
        characterSetWithCharactersInString:@"0123456789"];

while ([scanner isAtEnd] == NO) {
  NSString *buffer;
  if ([scanner scanCharactersFromSet:numbers intoString:&buffer]) {
    [strippedString appendString:buffer];
  }
  // --------- Add the following to get out of endless loop
  else {
     [scanner setScanLocation:([scanner scanLocation] + 1)];
  }    
  // --------- End of addition
}

NSLog(@"%@", strippedString); // "123123123"
GregoryN
fuente
4

Acepta solo número de móvil

NSString * strippedNumber = [mobileNumber stringByReplacingOccurrencesOfString:@"[^0-9]" withString:@"" options:NSRegularExpressionSearch range:NSMakeRange(0, [mobileNumber length])];
KumarS
fuente
3

Vale la pena señalar que la respuesta basada componentsSeparatedByCharactersInSet:y aceptada componentsJoinedByString:no es una solución eficiente en la memoria. Asigna memoria para el conjunto de caracteres, para una matriz y para una nueva cadena. Incluso si estas son solo asignaciones temporales, el procesamiento de muchas cadenas de esta manera puede llenar rápidamente la memoria.

Un enfoque más amigable con la memoria sería operar con una copia mutable de la cadena en su lugar. En una categoría sobre NSString:

-(NSString *)stringWithNonDigitsRemoved {
    static NSCharacterSet *decimalDigits;
    if (!decimalDigits) {
        decimalDigits = [NSCharacterSet decimalDigitCharacterSet];
    }
    NSMutableString *stringWithNonDigitsRemoved = [self mutableCopy];
    for (CFIndex index = 0; index < stringWithNonDigitsRemoved.length; ++index) {
        unichar c = [stringWithNonDigitsRemoved characterAtIndex: index];
        if (![decimalDigits characterIsMember: c]) {
            [stringWithNonDigitsRemoved deleteCharactersInRange: NSMakeRange(index, 1)];
            index -= 1;
        }
    }
    return [stringWithNonDigitsRemoved copy];
}

El perfil de los dos enfoques ha demostrado esto usando aproximadamente 2/3 menos memoria.

kadam
fuente
2

Puede usar la expresión regular en una cadena mutable:

NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:
                                @"[^\\d]"
                                options:0
                                error:nil];

[regex replaceMatchesInString:str
                      options:0 
                        range:NSMakeRange(0, str.length) 
                 withTemplate:@""];
Prcela
fuente
1

Desarrollé la mejor solución como categoría para ayudar con problemas más amplios:

Interfaz:

@interface NSString (easyReplace)
- (NSString *)stringByReplacingCharactersNotInSet:(NSCharacterSet *)set 
                                             with:(NSString *)string;
@end

Implementación

@implementation NSString (easyReplace)
- (NSString *)stringByReplacingCharactersNotInSet:(NSCharacterSet *)set 
                                             with:(NSString *)string
{
    NSMutableString *strippedString = [NSMutableString
                                       stringWithCapacity:self.length];

    NSScanner *scanner = [NSScanner scannerWithString:self];

    while ([scanner isAtEnd] == NO) {
        NSString *buffer;
        if ([scanner scanCharactersFromSet:set intoString:&buffer]) {
            [strippedString appendString:buffer];
        } else {
            [scanner setScanLocation:([scanner scanLocation] + 1)];
            [strippedString appendString:string];
        }
    }
    return [NSString stringWithString:strippedString];
}
@end

Uso:

NSString *strippedString = 
 [originalString stringByReplacingCharactersNotInSet:
   [NSCharacterSet setWithCharactersInString:@"01234567890" 
                                        with:@""];
BadPirate
fuente
1

Swift 3

let notNumberCharacters = NSCharacterSet.decimalDigits.inverted
let intString = yourString.trimmingCharacters(in: notNumberCharacters)
guido
fuente
Esto solo recortará caracteres que no sean dígitos desde el principio y el final.
Shebuka
1

rápido 4.1

var str = "75003 Paris, France"
var stringWithoutDigit = (str.components(separatedBy:CharacterSet.decimalDigits)).joined(separator: "")
print(stringWithoutDigit)
Pouya ghasemi
fuente
0

Um. La primera respuesta me parece totalmente incorrecta. NSScanner es realmente para analizar. A diferencia de la expresión regular, tiene que analizar la cadena un pequeño fragmento a la vez. Lo inicializa con una cadena, y mantiene un índice de cuánto avanza la cadena; Ese índice es siempre su punto de referencia, y cualquier comando que le des es relativo a ese punto. Usted le dice: "ok, deme la siguiente porción de caracteres en este conjunto" o "deme el número entero que encuentre en la cadena", y esos comienzan en el índice actual, y avanzan hasta que encuentran algo que no partido. Si el primer carácter ya no coincide, entonces el método devuelve NO y el índice no se incrementa.

El código en el primer ejemplo está escaneando "(123) 456-7890" en busca de caracteres decimales, que ya fallan desde el primer carácter, por lo que la llamada a scanCharactersFromSet: intoString: deja solo la strippedString pasada y devuelve NO; El código ignora totalmente la comprobación del valor de retorno, dejando stripsString sin asignar. Incluso si el primer carácter fuera un dígito, ese código fallaría, ya que solo devolvería los dígitos que encuentre hasta el primer guión o par o lo que sea.

Si realmente quisiera usar NSScanner, podría poner algo así en un bucle y seguir buscando un valor de retorno NO, y si lo consigue, puede incrementar el scanLocation y escanear nuevamente; y también tienes que verificar isAtEnd y yada yada yada. En resumen, herramienta incorrecta para el trabajo. La solución de Michael es mejor.

Jack Nutting
fuente
0

Para aquellos que buscan extracción de teléfono, puede extraer los números de teléfono de un texto usando NSDataDetector, por ejemplo:

NSString *userBody = @"This is a text with 30612312232 my phone";
if (userBody != nil) {
    NSError *error = NULL;
    NSDataDetector *detector = [NSDataDetector dataDetectorWithTypes:NSTextCheckingTypePhoneNumber error:&error];
    NSArray *matches = [detector matchesInString:userBody options:0 range:NSMakeRange(0, [userBody length])];
    if (matches != nil) {
        for (NSTextCheckingResult *match in matches) {
            if ([match resultType] == NSTextCheckingTypePhoneNumber) {
                DbgLog(@"Found phone number %@", [match phoneNumber]);
            }
        }
    }
}

``

Rafael Sanches
fuente
0

Creé una categoría en NSString para simplificar esta operación común.

NSString + AllowCharactersInSet.h

@interface NSString (AllowCharactersInSet)

- (NSString *)stringByAllowingOnlyCharactersInSet:(NSCharacterSet *)characterSet;

@end

NSString + AllowCharactersInSet.m

@implementation NSString (AllowCharactersInSet)

- (NSString *)stringByAllowingOnlyCharactersInSet:(NSCharacterSet *)characterSet {
    NSMutableString *strippedString = [NSMutableString
                                   stringWithCapacity:self.length];

    NSScanner *scanner = [NSScanner scannerWithString:self];

    while (!scanner.isAtEnd) {
        NSString *buffer = nil;

        if ([scanner scanCharactersFromSet:characterSet intoString:&buffer]) {
            [strippedString appendString:buffer];
        } else {
            scanner.scanLocation = scanner.scanLocation + 1;
        }
    }

    return strippedString;
}

@end
Ryan
fuente
0

Creo que actualmente la mejor manera es:

phoneNumber.replacingOccurrences(of: "\\D",
                               with: "",
                            options: String.CompareOptions.regularExpression)
AlKozin
fuente
0

Si solo está buscando obtener los números de la cadena, ciertamente podría usar expresiones regulares para analizarlos. Para hacer expresiones regulares en Objective-C, echa un vistazo a RegexKit . Editar: como señala @Nathan, usar NSScanner es una forma mucho más simple de analizar todos los números de una cadena. No estaba al tanto de esa opción, así que le apoyo por sugerirla. (Ni siquiera me gusta usar regex, así que prefiero enfoques que no los requieran).

Si desea formatear números de teléfono para mostrar, vale la pena echarle un vistazo a NSNumberFormatter . Le sugiero que lea esta pregunta SO relacionada para obtener consejos sobre cómo hacerlo. Recuerde que los números de teléfono tienen un formato diferente según la ubicación y / o la ubicación.

Quinn Taylor
fuente
Oh, las dolorosas horas que he pasado desarrollando buenos formateadores y analizadores de números de teléfono. Los hilos vinculados son un buen comienzo, pero el caso general de formatear números de teléfono globales para mostrar es un largo camino, y como se señala en los hilos vinculados, Apple no le da acceso a los formateadores de números de la libreta de direcciones, y muy inconsistente en cómo se presentan los números de teléfono desde la API de la libreta de direcciones. Lo único más difícil que formatear un número de teléfono para mostrar es determinar si dos números de teléfono son iguales. Al menos la pregunta del OP es el más fácil de los problemas.
Rob Napier el
Creo que esos enlaces para formatear números de teléfono son engañosos a menos que esté satisfecho con una implementación primitiva centrada en los EE. UU. En lugar de un formateador de números de teléfono localizado apropiado de Apple, la única forma de hacerlo correctamente es copiar las plantillas de formato del dispositivo (UIPhoneFormats.plist en OS 2.x) y reproducir las plantillas usted mismo dependiendo de la configuración regional de los usuarios. Esta es una tarea no trivial.
Nathan de Vries
Es por eso que mencioné la localización de formateadores de números. No pretendí publicar ninguna forma de solución completa para eso: es una discusión mucho más larga y tendría más sentido hacer que sea una pregunta SO separada.
Quinn Taylor el
0

Swift 5

let newString = origString.components(separatedBy: CharacterSet.decimalDigits.inverted).joined(separator: "")
Shakeel Ahmed
fuente
-1

Basado en la respuesta de Jon Vogel aquí, es como una extensión de Swift String junto con algunas pruebas básicas.

import Foundation
extension String {
    func stringByRemovingNonNumericCharacters() -> String {
        return self.componentsSeparatedByCharactersInSet(NSCharacterSet.decimalDigitCharacterSet().invertedSet).joinWithSeparator("")
    }
}

Y algunas pruebas que prueban al menos la funcionalidad básica:

import XCTest

class StringExtensionTests: XCTestCase {

    func testStringByRemovingNonNumericCharacters() {

        let baseString = "123"
        var testString = baseString
        var newString = testString.stringByRemovingNonNumericCharacters()
        XCTAssertTrue(newString == testString)

        testString = "a123b"
        newString = testString.stringByRemovingNonNumericCharacters()
        XCTAssertTrue(newString == baseString)

        testString = "a=1-2_3@b"
        newString = testString.stringByRemovingNonNumericCharacters()
        XCTAssertTrue(newString == baseString)

        testString = "(999) 999-9999"
        newString = testString.stringByRemovingNonNumericCharacters()
        XCTAssertTrue(newString.characters.count == 10)
        XCTAssertTrue(newString == "9999999999")

        testString = "abc"
        newString = testString.stringByRemovingNonNumericCharacters()
        XCTAssertTrue(newString == "")
    }
}

Esto responde a la pregunta del OP, pero podría modificarse fácilmente para dejar en el número de teléfono caracteres relacionados como ",; * # +"

Murray Sagal
fuente
-4
NSString *originalPhoneNumber = @"(123) 123-456 abc";
NSCharacterSet *numbers = [[NSCharacterSet characterSetWithCharactersInString:@"0123456789"] invertedSet];
NSString *trimmedPhoneNumber = [originalPhoneNumber stringByTrimmingCharactersInSet:numbers];

];

¡Mantenlo simple!

ryyst
fuente
3
esto solo recortará esos caracteres desde el principio y el final.
Raidfive