Usuń wszystkie oprócz liczb z NSString


157

Mam NSString (numer telefonu) z nawiasami i łącznikami, ponieważ niektóre numery telefonów są sformatowane. Jak usunąć wszystkie znaki z wyjątkiem liczb z ciągu?

Odpowiedzi:


375

Stare pytanie, ale co powiesz na:

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

Rozbija ciąg źródłowy na zestawie niecyfrowych, a następnie składa je ponownie, używając pustego separatora ciągu. Nie tak wydajne jak wybieranie znaków, ale znacznie bardziej zwarte w kodzie.


6
Dzięki! Dla innych początkujących możesz stworzyć własny niestandardowy zestaw NSCharacterSet, wykonującNSCharacterSet *myCharSet = [NSCharacterSet characterSetWithCharactersInString:@"charactersGoHere"]
guptron

1
Wielkie dzięki ! Tak dla mojej ciekawości, czy masz pomysł, dlaczego NSString *pureNumbers = [pureNumbers stringByTrimmingCharactersInSet: [NSCharacterSet decimalDigitCharacterSet] invertedSet]nie działa?
Thomas Besnehard

1
@Tommecpe stringByTrimmingCharactersInSet usuwa tylko początek i koniec ciągu, więc nie ma wpływu na pierwszy niepasujący znak ani przed ostatnim niepasującym znakiem.
simonobo

chcę zachować tylko cyfry i alfabet, jak mogę to zrobić?
Jacky

1
@Jacky w powyższym przykładzie zamieniłbyś na [NSCharacterSet decimalDigitCharacterSet]inny, który zawiera tylko cyfry i litery. Można skonstruować jedną poprzez tworzenie NSMutableCharaterSeti przechodzącej decimalDigitCharacterSet, uppercaseLetterCharacterSeta lowercaseLetterCharacterSetdo formUnionWithCharacterSet:. Zauważ, że letterCharacterSetobejmuje to również znaki, stąd użycie małych i wielkich liter.
kadam,

75

Nie ma potrzeby używania biblioteki wyrażeń regularnych, jak sugerują inne odpowiedzi - wywoływana jest klasa, której szukasz NSScanner. Jest używany w następujący sposób:

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"

EDYTOWAĆ: Zaktualizowałem kod, ponieważ oryginał został spisany na czubku mojej głowy i pomyślałem, że wystarczy, aby skierować ludzi we właściwym kierunku. Wygląda na to, że ludzie szukają kodu, który mogą po prostu skopiować i wkleić bezpośrednio do swojej aplikacji.

Zgadzam się również, że rozwiązanie Michaela Pelz-Shermana jest bardziej odpowiednie niż używanie NSScanner, więc warto się temu przyjrzeć.


+1 Dobra odpowiedź, która bezpośrednio dotyczy pytania. Zredagowałem moją odpowiedź, aby poprzeć to podejście, ale pozostawiam drugą połowę bez zmian, ponieważ jest nadal pomocna), co rozwiązuje problem drugiej strony formatowania numeru telefonu do wyświetlenia. (Następnie, czy możesz zostawić konstruktywny komentarz podczas głosowania w dół, jeśli tylko dla późniejszych czytelników?)
Quinn Taylor

4
Warto wiedzieć, że istnieje metoda + decimalDigitCharacterSet klasy NSCharacterSet, która podaje wszystkie cyfry dziesiętne. Jest to nieco inne niż zestaw list Natana, ponieważ zawiera wszystkie symbole reprezentujące liczby dziesiętne, w tym na przykład cyfry arabsko-indyjskie (١٢٣٤٥ itp.). W zależności od aplikacji może to czasami stanowić problem, ale generalnie jest to dobre lub neutralne i trochę krótsze do wpisania.
Rob Napier

Jestem prawie pewien, że ta odpowiedź tak naprawdę nie działa i nie jest właściwym podejściem do problemu. Jeśli faktycznie wypróbujesz kod, jak pokazano (najpierw dodasz @ przed pierwszym parametrem do NSLog, tworząc ciąg objc), zobaczysz, że albo wyświetla <null>, albo ulega awarii. Czemu? Zobacz moją odpowiedź poniżej.
Jack Nutting

Nie potrzeba innej odpowiedzi - po to są komentarze. Zaktualizowałem rozwiązanie, uwzględniając odniesienie do rozwiązania Michaela Pelza-Shermana.
Nathan de Vries

4
To bardzo skomplikowane.
ryyst

63

Zaakceptowana odpowiedź jest przesada, jeśli chodzi o to, o co się pytamy. To jest znacznie prostsze:

NSString *pureNumbers = [[phoneNumberString componentsSeparatedByCharactersInSet:[[NSCharacterSet decimalDigitCharacterSet] invertedSet]] componentsJoinedByString:@""];

2
(Obecnie) zaakceptowana odpowiedź jest mniej więcej identyczna z tą, ale została opublikowana 13 miesięcy wcześniej.
Caleb

W chwili, gdy to odpowiedziałem, nie było tej odpowiedzi. Chociaż wydaje się, że obecna odpowiedź została już zaproponowana i przegapiłem ją: web.archive.org/web/20101115214033/http://stackoverflow.com/…
Yacine Filali

30

To świetnie, ale kod nie działa dla mnie na iPhone 3.0 SDK.

Jeśli zdefiniuję strippedString, tak jak tutaj pokazujesz, pojawia się, BAD ACCESS errorgdy próbuję go wydrukować po scanCharactersFromSet:intoStringwywołaniu.

Jeśli tak robię:

NSMutableString *strippedString = [NSMutableString stringWithCapacity:10];

W końcu otrzymuję pusty ciąg, ale kod nie ulega awarii.

Zamiast tego musiałem uciec się do starego, dobrego C:

for (int i=0; i<[phoneNumber length]; i++) {
    if (isdigit([phoneNumber characterAtIndex:i])) {
        [strippedString appendFormat:@"%c",[phoneNumber characterAtIndex:i]];
    }
}

Używam 3.0 i to działa dla mnie. Bardziej popularna odpowiedź Vriesa nie zadziałała.
Neo42,

Odpowiedź numer jeden nie zadziała dla mnie. Skaner zatrzymuje się, gdy osiągnie () lub - Ta odpowiedź działa świetnie !! Dobry stary C !! Dziękuję
Jeff

2
Pamiętaj tylko, że „+” powinien być dozwolonym znakiem w numerze telefonu.
Prcela

27

Chociaż jest to stare pytanie z działającymi odpowiedziami, brakowało mi obsługi formatu międzynarodowego . Bazując na rozwiązaniu simonobo, zmieniony zestaw znaków zawiera znak plus „+”. Niniejsza poprawka obsługuje również międzynarodowe numery telefonów.

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

Wyrażenia Swift są

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

Co daje +12345671000 jako powszechny międzynarodowy format numeru telefonu.


2
To najlepsze rozwiązanie na liście, zwłaszcza jeśli potrzebujesz znaku plus dla międzynarodowych numerów telefonów.
UXUiOS

Z jakiegoś powodu używanie odwróconego zestawu znaków przeraża mnie, jeśli chodzi o wydajność. Czy zdarza się, że ktoś wie, czy to bezpodstawny strach?
devios1

Ten działał! Czy mógłbyś wyjaśnić, jak działa? @alex
Jayprakash Dubey

11

Oto wersja Swift tego.

import UIKit
import Foundation
var phoneNumber = " 1 (888) 555-5551    "
var strippedPhoneNumber = "".join(phoneNumber.componentsSeparatedByCharactersInSet(NSCharacterSet.decimalDigitCharacterSet().invertedSet))

Swift 2.0: phoneNumber.componentsSeparatedByCharactersInSet (NSCharacterSet.decimalDigitCharacterSet (). InvertedSet) .joinWithSeparator ("")
iluvatar_GR

11

Szybka wersja najpopularniejszej odpowiedzi:

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

Edycja: składnia dla języka Swift 2

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

Edycja: składnia dla języka Swift 3

let newString = oldString.components(separatedBy: CharacterSet.decimalDigits.inverted).joined(separator: "")

czy istnieje sposób na zachowanie symbolu rozdzielonego dziesiętnie? Kropka (lub przecinek) jako funkcja domyślnych ustawień urządzenia? Twoje rozwiązanie eliminuje wszystko oprócz liczb
Nicholas,

5

Dzięki za przykład. Brakuje tylko jednej rzeczy inkrementacji scanLocation w przypadku, gdy jeden ze znaków w originalString nie zostanie znaleziony wewnątrz obiektu numbers CharacterSet. Dodałem instrukcję else {}, aby to naprawić.

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"

4

Akceptuje tylko numer telefonu komórkowego

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

3

Warto zauważyć, że zaakceptowano componentsSeparatedByCharactersInSet:icomponentsJoinedByString: oparta na niej odpowiedź nie jest rozwiązaniem zapewniającym oszczędność pamięci. Alokuje pamięć na zestaw znaków, tablicę i nowy ciąg. Nawet jeśli są to tylko tymczasowe przydziały, przetwarzanie w ten sposób wielu ciągów może szybko zapełnić pamięć.

Bardziej przyjaznym dla pamięci podejściem byłoby operowanie na modyfikowalnej kopii łańcucha w miejscu. W kategorii ponad 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];
}

Profilowanie tych dwóch podejść pokazało, że zużywa to około 2/3 mniej pamięci.


2

Możesz użyć wyrażenia regularnego na zmiennym ciągu:

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

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

1

Zbudowano najlepsze rozwiązanie jako kategorię, aby pomóc w rozwiązaniu szerszych problemów:

Berło:

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

Wdrażanie:

@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

Stosowanie:

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

1

Szybki 3

let notNumberCharacters = NSCharacterSet.decimalDigits.inverted
let intString = yourString.trimmingCharacters(in: notNumberCharacters)

Spowoduje to obcięcie tylko znaków niebędących cyframi od początku i końca.
Shebuka

1

Swift 4.1

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

0

Um. Pierwsza odpowiedź wydaje mi się całkowicie błędna. NSScanner jest naprawdę przeznaczony do analizowania. W przeciwieństwie do wyrażenia regularnego, pozwala on analizować ciąg po jednym małym kawałku na raz. Inicjalizuje się go łańcuchem i utrzymuje indeks tego, jak daleko w ciągu otrzymano; Ten indeks jest zawsze jego punktem odniesienia, a wszelkie polecenia, które mu wydajesz, odnoszą się do tego punktu. Mówisz mu: „ok, podaj mi następny fragment znaków w tym zestawie” lub „podaj liczbę całkowitą, którą znajdziesz w ciągu znaków”, a te zaczynają się od bieżącego indeksu i idą do przodu, aż znajdą coś, co nie mecz. Jeśli pierwszy znak już nie pasuje, metoda zwraca NIE, a indeks nie zwiększa się.

Kod w pierwszym przykładzie skanuje „(123) 456-7890” w poszukiwaniu znaków dziesiętnych, co już kończy się niepowodzeniem od pierwszego znaku, więc wywołanie scanCharactersFromSet: intoString: pozostawia przekazany strippedString w spokoju i zwraca NO; Kod całkowicie ignoruje sprawdzanie zwracanej wartości, pozostawiając strippedString nieprzypisany. Nawet jeśli pierwszy znak byłby cyfrą, kod nie powiódłby się, ponieważ zwróciłby tylko cyfry, które znajdzie, aż do pierwszego myślnika, parenu lub czegokolwiek.

Jeśli naprawdę chciałeś użyć NSScanner, możesz umieścić coś takiego w pętli i dalej sprawdzać, czy nie zwraca zwracanej wartości NO, a jeśli to otrzymasz, możesz zwiększyć wartość scanLocation i skanować ponownie; musisz też sprawdzić isAtEnd i yada yada yada. Krótko mówiąc, niewłaściwe narzędzie do pracy. Rozwiązanie Michaela jest lepsze.


0

Osoby szukające ekstrakcji telefonu mogą wyodrębnić numery telefonów z tekstu za pomocą narzędzia NSDataDetector, na przykład:

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]);
            }
        }
    }
}

`


0

Utworzyłem kategorię na NSString, aby uprościć tę typową operację.

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

0

Myślę, że obecnie najlepszym sposobem jest:

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

0

Jeśli szukasz po prostu chwycić numery od napisu, to mógłby z pewnością używać wyrażeń regularnych do analizowania ich. Aby wykonać regex w Objective-C, sprawdź RegexKit . Edytować: Jak wskazuje @Nathan, użycie NSScanner jest znacznie prostszym sposobem analizowania wszystkich liczb z ciągu. Całkowicie nie byłam świadoma tej opcji, więc dziękuję mu za zasugerowanie tego. (Sam nawet nie lubię używać wyrażeń regularnych, więc wolę podejścia, które ich nie wymagają).

Jeśli chcesz sformatować numery telefonów do wyświetlania, warto przyjrzeć się NSNumberFormatter . Proponuję przeczytać to powiązane pytanie SO, aby uzyskać wskazówki, jak to zrobić. Pamiętaj, że numery telefonów mają różny format w zależności od lokalizacji i / lub regionu.


Och, te bolesne godziny spędziłem na opracowywaniu dobrych formatów numerów telefonów i parserów. Połączone wątki to dobry początek, ale ogólny przypadek formatowania globalnych numerów telefonów do wyświetlania to długa droga i jak zauważono w połączonych wątkach, Apple nie zapewnia żadnego dostępu do formatów numerów telefonów z książki adresowej i są bardzo niespójne w sposobie prezentowania numerów telefonów z interfejsu API książki adresowej. Jedyną trudniejszą rzeczą niż sformatowanie numeru telefonu do wyświetlenia jest określenie, czy dwa numery telefonów są równe. Przynajmniej pytanie PO jest najłatwiejszym z problemów.
Rob Napier,

Uważam, że te linki do formatowania numerów telefonów są mylące, chyba że jesteś zadowolony z prymitywnej implementacji skoncentrowanej na USA. Zamiast odpowiedniego zlokalizowanego programu do formatowania numerów telefonów firmy Apple, jedynym sposobem na poprawne wykonanie tego jest skopiowanie szablonów formatowania z urządzenia (UIPhoneFormats.plist w systemie OS 2.x) i samodzielne odtworzenie szablonów w zależności od ustawień regionalnych użytkowników. To nietrywialne zadanie.
Nathan de Vries

Dlatego wspomniałem o lokalizacji elementów formatujących liczby. Nie udawałem, że publikuję w tej sprawie jakąkolwiek formę kompletnego rozwiązania - to znacznie dłuższa dyskusja i sensowniej byłoby uczynić z tego osobne pytanie SO.
Quinn Taylor

0

Szybki 5

let newString = origString.components(separatedBy: CharacterSet.decimalDigits.inverted).joined(separator: "")

-1

Na podstawie odpowiedzi Jona Vogela jest to rozszerzenie Swift String wraz z kilkoma podstawowymi testami.

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

I kilka testów potwierdzających przynajmniej podstawową funkcjonalność:

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

To odpowiada na pytanie OP, ale można je łatwo zmodyfikować, aby pozostawić znaki związane z numerem telefonu, takie jak ",; * # +"


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

];

Nie komplikuj!


3
spowoduje to tylko obcięcie tych znaków od początku i końca.
raidfive
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.