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?
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:
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.
NSString *pureNumbers = [pureNumbers stringByTrimmingCharactersInSet: [NSCharacterSet decimalDigitCharacterSet] invertedSet]
nie działa?
[NSCharacterSet decimalDigitCharacterSet]
inny, który zawiera tylko cyfry i litery. Można skonstruować jedną poprzez tworzenie NSMutableCharaterSet
i przechodzącej decimalDigitCharacterSet
, uppercaseLetterCharacterSet
a lowercaseLetterCharacterSet
do formUnionWithCharacterSet:
. Zauważ, że letterCharacterSet
obejmuje to również znaki, stąd użycie małych i wielkich liter.
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ć.
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:@""];
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 error
gdy próbuję go wydrukować po scanCharactersFromSet:intoString
wywoł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]];
}
}
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.
Oto wersja Swift tego.
import UIKit
import Foundation
var phoneNumber = " 1 (888) 555-5551 "
var strippedPhoneNumber = "".join(phoneNumber.componentsSeparatedByCharactersInSet(NSCharacterSet.decimalDigitCharacterSet().invertedSet))
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: "")
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"
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.
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:@""];
Swift 4.1
var str = "75003 Paris, France"
var stringWithoutDigit = (str.components(separatedBy:CharacterSet.decimalDigits)).joined(separator: "")
print(stringWithoutDigit)
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.
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]);
}
}
}
}
`
Utworzyłem kategorię na NSString, aby uprościć tę typową operację.
@interface NSString (AllowCharactersInSet)
- (NSString *)stringByAllowingOnlyCharactersInSet:(NSCharacterSet *)characterSet;
@end
@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
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.
Szybki 5
let newString = origString.components(separatedBy: CharacterSet.decimalDigits.inverted).joined(separator: "")
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 ",; * # +"
NSString *originalPhoneNumber = @"(123) 123-456 abc";
NSCharacterSet *numbers = [[NSCharacterSet characterSetWithCharactersInString:@"0123456789"] invertedSet];
NSString *trimmedPhoneNumber = [originalPhoneNumber stringByTrimmingCharactersInSet:numbers];
];
Nie komplikuj!
NSCharacterSet *myCharSet = [NSCharacterSet characterSetWithCharactersInString:@"charactersGoHere"]