Удалить все, кроме номеров из NSString


157

У меня есть NSString (номер телефона) с некоторыми скобками и дефисами, так как некоторые номера телефонов отформатированы. Как бы я удалил все символы, кроме чисел, из строки?

Ответы:


375

Старый вопрос, но как насчет:

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

Он взрывает исходную строку на множестве нецифровых чисел, а затем собирает их, используя пустой разделитель строк. Не так эффективно, как выбор символов, но гораздо компактнее в коде.


6
Спасибо! Для других новичков вы можете создать свой собственный набор NSCharacterSet, выполнивNSCharacterSet *myCharSet = [NSCharacterSet characterSetWithCharactersInString:@"charactersGoHere"]
Guptron

1
Большое спасибо! Просто для моего любопытства, у вас есть идея, почему NSString *pureNumbers = [pureNumbers stringByTrimmingCharactersInSet: [NSCharacterSet decimalDigitCharacterSet] invertedSet]не работает?
Томас Беснехард

1
@Tommecpe stringByTrimmingCharactersInSet удаляет только начало и конец строки, поэтому она не влияет после первого несовпадающего символа или до последнего несоответствующего символа.
Симонобо

я хочу сохранить только цифры и алфавит, как я могу это сделать?
Джеки,

1
@Jacky в приведенном выше примере вы должны заменить [NSCharacterSet decimalDigitCharacterSet]другой, который содержит только цифры и буквы. Вы можете создать один, создавая NSMutableCharaterSetи передавая decimalDigitCharacterSet, uppercaseLetterCharacterSetи lowercaseLetterCharacterSetк formUnionWithCharacterSet:. Обратите внимание, что также letterCharacterSetвключает в себя метки, следовательно, использование строчных и прописных версий.
Кадам

75

Нет необходимости использовать библиотеку регулярных выражений, как подсказывают другие ответы - вызывается класс, который вам нужен NSScanner. Используется следующим образом:

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"

РЕДАКТИРОВАТЬ: Я обновил код, потому что оригинал был написан на моей голове, и я подумал, что этого будет достаточно, чтобы направить людей в правильном направлении. Кажется, что люди после кода, они могут просто скопировать и вставить прямо в свое приложение.

Я также согласен с тем, что решение Майкла Пельца-Шермана более уместно, чем использование NSScanner, так что вы можете взглянуть на это.


+1 Хороший ответ, который напрямую касается вопроса. Я отредактировал свой ответ, чтобы поддержать этот подход, но я оставляю вторую половину как есть, так как она все еще полезна), которая решает проблему обратной стороны форматирования номера телефона для отображения. (Далее, можете ли вы оставить конструктивный комментарий при понижении голосов, если только для более поздних читателей?)
Куинн Тейлор

4
Может быть полезно знать, что есть метод + decimalDigitCharacterSet NSCharacterSet, который даст вам все десятичные цифры. Это немного отличается от установленных списков Натана, потому что оно включает в себя все символы, представляющие десятичные числа, включая, например, арабско-индийские цифры (١٢٣٤٥ и т. Д.). В зависимости от вашего приложения, это может иногда быть проблемой, но обычно это либо хорошо, либо нейтрально, и немного короче, чтобы напечатать.
Роб Нейпир

Я уверен, что этот ответ на самом деле не работает и не является правильным подходом к проблеме. Если вы на самом деле попробуете код, как показано (сначала добавив @ перед первым параметром в NSLog, сделав его строкой objc), вы обнаружите, что он либо печатает <null>, либо вылетает. Зачем? Смотрите мой ответ ниже.
Джек Наттинг

Не нужно другого ответа - для этого и нужны комментарии. Я обновил решение, включая ссылку на решение Майкла Пельца-Шермана.
Натан де Врис

4
Это очень сложно.
ryyst

63

Принятый ответ является излишним для того, что просят. Это намного проще:

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

2
(В настоящее время) принятый ответ более или менее идентичен этому, но был опубликован 13 месяцами ранее.
Калеб

В то время, когда я ответил на это, у этого не было этого ответа. Хотя кажется, что текущий ответ уже был предложен, и я пропустил его: web.archive.org/web/20101115214033/http://stackoverflow.com/…
Yacine Filali

30

Это здорово, но код не работает для меня на iPhone 3.0 SDK.

Если я определю strippedString, как вы показываете здесь, я получаю BAD ACCESS errorпри попытке распечатать его послеscanCharactersFromSet:intoString вызова.

Если я сделаю это так:

NSMutableString *strippedString = [NSMutableString stringWithCapacity:10];

Я получаю пустую строку, но код не падает.

Вместо этого мне пришлось прибегнуть к старому доброму C:

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

Я бегу 3.0, и это работает для меня. Более популярный ответ от Вриса не сработал.
Neo42,

Ответ номер один не будет работать для меня. Сканер останавливается, как только достигает () или - Этот ответ отлично работает !! Старый добрый C !! Спасибо
Джефф

2
Обратите внимание, что для номера телефона должен быть разрешен символ «+».
Prcela

27

Хотя это старый вопрос с рабочими ответами, я пропустил поддержку международного формата . Основанный на решении simonobo, измененный набор символов включает в себя знак плюс "+". Международные телефонные номера также поддерживаются этой поправкой.

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

Свифт выражений

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

Что приводит к +12345671000 в качестве общего формата международного телефонного номера.


2
Это лучшее решение в списке, особенно если вам нужен знак плюс для международных телефонных номеров.
UXUiOS

По какой-то причине использование перевернутого набора символов пугает меня настолько, насколько производительность идет. Кто-нибудь знает, если это необоснованный страх?
devios1

Этот работал! Не могли бы вы объяснить, как это работает? @alex
Джайпракаш Дубей

11

Вот Swift-версия этого.

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

Свифт версия самого популярного ответа:

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

Изменить: Синтаксис для Swift 2

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

Изменить: Синтаксис для Swift 3

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

Есть ли способ сохранить десятичный разделенный символ? Точка (или запятая) как функция настроек устройства по умолчанию? Ваше решение устранить все, кроме чисел
Николас

5

Спасибо за пример. У него есть только одна вещь, пропускающая приращение scanLocation в случае, если один из символов в originalString не найден внутри объекта NumberSet. Я добавил оператор else {}, чтобы исправить это.

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

Принимаем только номер мобильного телефона

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

3

Возможно, стоит отметить, что принятый componentsSeparatedByCharactersInSet:и componentsJoinedByString:основанный на ответе не является эффективным с точки зрения памяти решением. Он выделяет память для набора символов, для массива и для новой строки. Даже если это только временные выделения, обработка большого количества строк таким способом может быстро заполнить память.

Подход, ориентированный на память, заключается в работе с изменяемой копией строки на месте. В категории над 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];
}

Профилирование двух подходов показало это, используя примерно на 2/3 меньше памяти.


2

Вы можете использовать регулярное выражение для изменяемой строки:

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

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

1

Создано топовое решение как категория, чтобы помочь с более широкими проблемами:

Интерфейс:

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

реализации внешних:

@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

Использование:

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

1

Свифт 3

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

Это обрезает только нецифровые символы от начала и до конца.
Шебука

1

Свифт 4.1

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

0

Um. Первый ответ кажется мне совершенно неверным. NSScanner действительно предназначен для разбора. В отличие от регулярных выражений, вы разбираете строку по одному крошечному фрагменту за раз. Вы инициализируете его строкой, и он поддерживает индекс того, как далеко вдоль строки он получен; Этот показатель всегда его точка отсчета, и все команды, которые вы даете его по сравнению с этой точкой. Вы говорите: «Хорошо, дайте мне следующий кусок символов в этом наборе» или «Дайте мне целое число, которое вы найдете в строке», и они начинаются с текущего индекса и продвигаются, пока не найдут то, что не соответствие. Если самый первый символ уже не совпадает, то метод возвращает NO, и индекс не увеличивается.

Код в первом примере сканирует "(123) 456-7890" на наличие десятичных символов, что уже не выполняется с самого первого символа, поэтому при вызове scanCharactersFromSet: intoString: оставляется переданная strippedString в одиночку и возвращается NO; Код полностью игнорирует проверку возвращаемого значения, оставляя strippedString неназначенным. Даже если бы первый символ был цифрой, этот код потерпел бы неудачу, так как он возвращал бы только те цифры, которые он обнаружил, до первой черты или скобки или чего-то еще.

Если вы действительно хотите использовать NSScanner, вы можете поместить что-то подобное в цикл и продолжать проверять возвращаемое значение NO, а если вы его получите, вы можете увеличивать scanLocation и сканировать снова; и вы также должны проверить isAtEnd и yada yada yada. Короче говоря, неправильный инструмент для работы. Решение Майкла лучше.


0

Для тех, кто ищет извлечение телефона, вы можете извлечь телефонные номера из текста, используя NSDataDetector, например:

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

Я создал категорию на NSString, чтобы упростить эту обычную операцию.

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

Я думаю, что в настоящее время лучший способ это:

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

0

Если вы просто хотите получить числа из строки, вы можете использовать регулярные выражения для их анализа. Для выполнения регулярных выражений в Objective-C, проверьте RegexKit . Редактировать: как указывает @Nathan, использование NSScanner - это гораздо более простой способ анализа всех чисел из строки. Я совершенно не знал об этом варианте, так что подпишите его за предложение. (Я даже не люблю использовать регулярные выражения, поэтому я предпочитаю подходы, которые не требуют их.)

Если вы хотите отформатировать телефонные номера для отображения, стоит взглянуть на NSNumberFormatter . Я предлагаю вам прочитать этот связанный вопрос SO для советов по этому вопросу. Помните, что номера телефонов отформатированы по-разному в зависимости от местоположения и / или локали.


О, болезненные часы, которые я потратил на разработку хороших форматеров и анализаторов телефонных номеров. Связанные темы - хорошее начало, но общий случай форматирования глобальных телефонных номеров для отображения - это долгий путь, и, как отмечалось в связанных ветвях, Apple не предоставляет вам никакого доступа к средствам форматирования телефонных номеров адресной книги, и очень противоречиво в том, как телефонные номера представлены из API адресной книги. Единственное, что сложнее, чем форматирование номера телефона для отображения, - это определение, равны ли два номера телефона. По крайней мере, вопрос ОП - самая легкая из проблем.
Роб Нейпир

Я полагаю, что эти ссылки на форматирование телефонных номеров вводят в заблуждение, если вы не довольны примитивной, ориентированной на США реализацией. Вместо правильного локализованного средства форматирования телефонных номеров от Apple, единственный способ сделать это правильно - скопировать шаблоны форматирования с устройства (UIPhoneFormats.plist в OS 2.x) и воспроизвести шаблоны самостоятельно в зависимости от языкового стандарта пользователя. Это нетривиальная задача.
Натан де Врис

Вот почему я упомянул локализацию числителей. Я не претендовал на публикацию какой-либо формы полного решения для этого - это гораздо более длительное обсуждение, и было бы более разумным сделать его отдельным вопросом SO.
Куинн Тейлор


-1

Основываясь на ответе Джона Фогеля, он представляет собой расширение Swift String вместе с некоторыми базовыми тестами.

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

И некоторые тесты, доказывающие хотя бы базовую функциональность:

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

Это отвечает на вопрос ОП, но его можно легко изменить, чтобы оставить в телефонном номере связанные символы, такие как ",; * # +"


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

];

Будь проще!


3
это только урежет тех персонажей от начала и конца.
raidfive
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.