Supprimer tout sauf les nombres de NSString

157

J'ai une NSString (numéro de téléphone) avec des parenthèses et des tirets car certains numéros de téléphone sont formatés. Comment supprimer tous les caractères sauf les nombres de la chaîne?

Ben Harris
la source

Réponses:

375

Ancienne question, mais que diriez-vous:

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

Il explose la chaîne source sur l'ensemble des non-chiffres, puis les réassemble à l'aide d'un séparateur de chaîne vide. Pas aussi efficace que la sélection de caractères, mais beaucoup plus compact dans le code.

Simonobo
la source
6
Merci! Pour les autres débutants, vous pouvez créer votre propre NSCharacterSet personnalisé en faisantNSCharacterSet *myCharSet = [NSCharacterSet characterSetWithCharactersInString:@"charactersGoHere"]
guptron
1
Grand merci! Juste pour ma curiosité, avez-vous une idée pourquoi NSString *pureNumbers = [pureNumbers stringByTrimmingCharactersInSet: [NSCharacterSet decimalDigitCharacterSet] invertedSet]ne fonctionne pas?
Thomas Besnehard
1
@Tommecpe stringByTrimmingCharactersInSet ne supprime que le début et la fin de la chaîne, de sorte qu'il n'affecte pas après le premier caractère non correspondant ou avant le dernier caractère non correspondant.
simonobo
je veux ne garder que les chiffres et l'alphabet, comment puis-je le faire?
Jacky
1
@Jacky dans l'exemple ci-dessus, vous le remplaceriez [NSCharacterSet decimalDigitCharacterSet]par un autre contenant uniquement des chiffres et des lettres. Vous pouvez en créer un en créant un NSMutableCharaterSetet en passant un decimalDigitCharacterSet, uppercaseLetterCharacterSetet lowercaseLetterCharacterSetà formUnionWithCharacterSet:. Notez que cela letterCharacterSetinclut également des marques, d'où l'utilisation de versions minuscules et majuscules.
kadam
75

Il n'est pas nécessaire d'utiliser une bibliothèque d'expressions régulières comme le suggèrent les autres réponses - la classe que vous recherchez est appelée NSScanner. Il est utilisé comme suit:

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"

ÉDITER: J'ai mis à jour le code parce que l'original a été écrit sur le dessus de ma tête et j'ai pensé que ce serait suffisant pour orienter les gens dans la bonne direction. Il semble que les gens recherchent du code, ils peuvent simplement copier-coller directement dans leur application.

Je conviens également que la solution de Michael Pelz-Sherman est plus appropriée que l'utilisation NSScanner, alors vous voudrez peut-être y jeter un coup d'œil.

Nathan de Vries
la source
+1 Bonne réponse qui répond directement à la question. J'ai édité ma réponse pour préconiser cette approche, mais je laisse la seconde moitié telle quelle, car elle est toujours utile) qui résout le problème inverse du formatage d'un numéro de téléphone pour l'affichage. (Ensuite, pouvez-vous laisser un commentaire constructif lors du vote négatif, ne serait-ce que pour les lecteurs ultérieurs?)
Quinn Taylor
4
Il peut être utile de savoir qu'il existe une méthode + decimalDigitCharacterSet de NSCharacterSet qui vous donnera tous les chiffres décimaux. Ceci est légèrement différent de l'ensemble des listes Nathan, car il inclut tous les symboles qui représentent des nombres décimaux, y compris, par exemple, des chiffres arabes-indiens (١٢٣٤٥ etc.). Selon votre application, cela peut parfois être un problème, mais généralement c'est bon ou neutre, et un peu plus court à taper.
Rob Napier
Je suis presque sûr que cette réponse ne fonctionne pas réellement, et n'est pas vraiment la bonne approche du problème. Si vous essayez réellement le code comme indiqué (en ajoutant d'abord un @ avant le premier paramètre à NSLog, ce qui en fait une chaîne objc), vous constaterez qu'il affiche <null> ou se bloque. Pourquoi? Voir ma réponse ci-dessous.
Jack Nutting
Pas besoin d'une autre réponse - c'est à cela que servent les commentaires. J'ai mis à jour la solution, y compris une référence à la solution de Michael Pelz-Sherman.
Nathan de Vries
4
C'est waaaay trop compliqué.
ryyst
63

La réponse acceptée est exagérée pour ce qui est demandé. C'est beaucoup plus simple:

NSString *pureNumbers = [[phoneNumberString componentsSeparatedByCharactersInSet:[[NSCharacterSet decimalDigitCharacterSet] invertedSet]] componentsJoinedByString:@""];
Yacine Filali
la source
2
La réponse (actuellement) acceptée est plus ou moins identique à celle-ci, mais a été publiée 13 mois plus tôt.
Caleb
Au moment où j'ai répondu à cela, il n'avait pas cette réponse. Bien qu'il semble que la réponse actuelle ait déjà été proposée et que je l'ai ratée: web.archive.org/web/20101115214033/http
Yacine Filali
30

C'est génial, mais le code ne fonctionne pas pour moi sur le SDK iPhone 3.0.

Si je définis strippedString comme vous le montrez ici, j'obtiens un BAD ACCESS erroren essayant de l'imprimer après l' scanCharactersFromSet:intoStringappel.

Si je le fais comme ça:

NSMutableString *strippedString = [NSMutableString stringWithCapacity:10];

Je me retrouve avec une chaîne vide, mais le code ne plante pas.

J'ai dû recourir au bon vieux C à la place:

for (int i=0; i<[phoneNumber length]; i++) {
    if (isdigit([phoneNumber characterAtIndex:i])) {
        [strippedString appendFormat:@"%c",[phoneNumber characterAtIndex:i]];
    }
}
Michael Pelz-Sherman
la source
J'utilise la version 3.0 et cela fonctionne pour moi. La réponse la plus populaire de Vries n'a pas fonctionné.
Neo42
La réponse numéro un ne fonctionnera pas pour moi. Le scanner s'arrête une fois qu'il atteint un () ou - Cette réponse fonctionne très bien !! Bon vieux C !! Merci
Jeff
2
Notez seulement que le caractère «+» doit être autorisé pour le numéro de téléphone.
Prcela
27

Bien que ce soit une vieille question avec des réponses fonctionnelles, j'ai manqué le support du format international . Basé sur la solution de simonobo, le jeu de caractères modifié comprend un signe plus "+". Les numéros de téléphone internationaux sont également pris en charge par cet amendement.

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

Les expressions Swift sont

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

Ce qui donne +12345671000 comme format de numéro de téléphone international courant.

Alex
la source
2
C'est la meilleure solution de la liste, surtout si vous avez besoin du signe plus pour les numéros de téléphone internationaux.
UXUiOS
Pour une raison quelconque, l'utilisation d'un jeu de caractères inversé me fait peur en ce qui concerne les performances. Quelqu'un sait-il s'il s'agit d'une peur infondée?
devios1
Celui-ci a fonctionné! Pouvez-vous expliquer son fonctionnement? @alex
Jayprakash Dubey
11

Voici la version Swift de ceci.

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

Version Swift de la réponse la plus populaire:

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

Edit: Syntaxe pour Swift 2

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

Edit: Syntaxe pour Swift 3

let newString = oldString.components(separatedBy: CharacterSet.decimalDigits.inverted).joined(separator: "")
Jon Vogel
la source
existe-t-il un moyen de conserver le symbole séparé par des décimales? Le point (ou virgule) en fonction des paramètres par défaut de l'appareil? Votre solution élimine tout sauf les nombres
Nicholas
5

Merci pour l'exemple. Il ne manque qu'une seule chose, l'incrément de scanLocation au cas où l'un des caractères de originalString ne serait pas trouvé à l'intérieur de l'objet CharacterSet numbers. J'ai ajouté une instruction else {} pour résoudre ce problème.

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
la source
4

Il n'accepte que le numéro de mobile

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

Il peut être intéressant de noter que la réponse acceptée componentsSeparatedByCharactersInSet:et componentsJoinedByString:basée n'est pas une solution efficace en mémoire. Il alloue de la mémoire pour le jeu de caractères, pour un tableau et pour une nouvelle chaîne. Même s'il ne s'agit que d'allocations temporaires, le traitement de nombreuses chaînes de cette façon peut rapidement remplir la mémoire.

Une approche plus conviviale pour la mémoire serait d'opérer sur une copie mutable de la chaîne en place. Dans une catégorie sur 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];
}

Le profilage des deux approches a montré cela en utilisant environ 2/3 de mémoire en moins.

kadam
la source
2

Vous pouvez utiliser une expression régulière sur une chaîne mutable:

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

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

Création de la meilleure solution en tant que catégorie pour résoudre des problèmes plus larges:

Interface:

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

Mise en œuvre:

@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

Usage:

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

Swift 3

let notNumberCharacters = NSCharacterSet.decimalDigits.inverted
let intString = yourString.trimmingCharacters(in: notNumberCharacters)
guido
la source
Cela ne réduira que les caractères non numériques du début et de la fin.
Shebuka
1

rapide 4.1

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

Hum. La première réponse me semble totalement erronée. NSScanner est vraiment destiné à l'analyse. Contrairement à regex, il vous permet d'analyser la chaîne un tout petit morceau à la fois. Vous l'initialisez avec une chaîne et il conserve un index de la distance parcourue le long de la chaîne; Cet index est toujours son point de référence et toutes les commandes que vous lui donnez sont relatives à ce point. Vous lui dites, "ok, donnez-moi le prochain morceau de caractères dans cet ensemble" ou "donnez-moi l'entier que vous trouvez dans la chaîne", et ceux-ci commencent à l'index actuel et avancent jusqu'à ce qu'ils trouvent quelque chose qui ne fonctionne pas rencontre. Si le tout premier caractère ne correspond pas déjà, la méthode renvoie NON et l'index ne s'incrémente pas.

Le code dans le premier exemple scanne "(123) 456-7890" pour les caractères décimaux, ce qui échoue déjà à partir du tout premier caractère, donc l'appel à scanCharactersFromSet: intoString: laisse le strippedString passé seul et retourne NO; Le code ignore totalement la vérification de la valeur de retour, laissant la chaîne strippedString non attribuée. Même si le premier caractère était un chiffre, ce code échouerait, car il ne renverrait que les chiffres qu'il trouve jusqu'au premier tiret ou paren ou autre.

Si vous voulez vraiment utiliser NSScanner, vous pouvez mettre quelque chose comme ça dans une boucle, et continuer à vérifier une valeur de retour NO, et si vous obtenez cela, vous pouvez incrémenter le scanLocation et scanner à nouveau; et vous devez également vérifier isAtEnd et yada yada yada. En bref, mauvais outil pour le travail. La solution de Michael est meilleure.

Jack Nutting
la source
0

Pour ceux qui recherchent une extraction de téléphone, vous pouvez extraire les numéros de téléphone d'un texte à l'aide de NSDataDetector, par exemple:

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
la source
0

J'ai créé une catégorie sur NSString pour simplifier cette opération courante.

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
la source
0

Je pense que le meilleur moyen actuellement est:

phoneNumber.replacingOccurrences(of: "\\D",
                               with: "",
                            options: String.CompareOptions.regularExpression)
AlKozine
la source
0

Si vous cherchez simplement à récupérer les nombres de la chaîne, vous pouvez certainement utiliser des expressions régulières pour les analyser. Pour faire des regex en Objective-C, consultez RegexKit . Éditer: Comme le souligne @Nathan, l'utilisation de NSScanner est un moyen beaucoup plus simple d'analyser tous les nombres d'une chaîne. Je n'étais absolument pas au courant de cette option, alors des accessoires à lui pour l'avoir suggérée. (Je n'aime même pas utiliser les regex moi-même, donc je préfère les approches qui n'en nécessitent pas.)

Si vous souhaitez formater les numéros de téléphone pour l'affichage, il vaut la peine de jeter un œil à NSNumberFormatter . Je vous suggère de lire cette question SO connexe pour obtenir des conseils à ce sujet. N'oubliez pas que les numéros de téléphone sont formatés différemment selon l'emplacement et / ou les paramètres régionaux.

Quinn Taylor
la source
Oh les heures douloureuses que j'ai passées à développer de bons formateurs et analyseurs de numéros de téléphone. Les fils liés sont un bon début, mais le cas général du formatage des numéros de téléphone globaux pour l'affichage est un long chemin, et comme indiqué dans les fils liés, Apple ne vous donne aucun accès aux formateurs de numéros de téléphone du carnet d'adresses, et sont très incohérent dans la façon dont les numéros de téléphone sont présentés à partir de l'API du carnet d'adresses. La seule chose plus difficile que de formater un numéro de téléphone pour l'affichage est de déterminer si deux numéros de téléphone sont égaux. Au moins, la question du PO est le plus simple des problèmes.
Rob Napier
Je pense que ces liens vers le formatage des numéros de téléphone sont trompeurs à moins que vous ne soyez satisfait d'une implémentation primitive centrée sur les États-Unis. Au lieu d'un formateur de numéro de téléphone localisé approprié d'Apple, le seul moyen de le faire correctement est de copier les modèles de formatage de l'appareil (UIPhoneFormats.plist sous OS 2.x) et de reproduire les modèles vous-même en fonction des paramètres régionaux de l'utilisateur. C'est une tâche non triviale.
Nathan de Vries
C'est pourquoi j'ai mentionné la localisation des formateurs de nombres. Je n'ai pas prétendu publier une forme de solution complète pour cela - c'est une discussion beaucoup plus longue et il serait plus logique d'en faire une question SO distincte.
Quinn Taylor
0

Swift 5

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

Basé sur la réponse de Jon Vogel ici, il s'agit d'une extension Swift String avec quelques tests de base.

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

Et quelques tests prouvant au moins les fonctionnalités de base:

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 == "")
    }
}

Cela répond à la question du PO, mais il peut être facilement modifié pour laisser des caractères liés au numéro de téléphone comme ",; * # +"

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

];

Rester simple!

Ryyst
la source
3
cela ne réduira que ces caractères du début et de la fin.
raidfive