从 NSString 中删除除数字以外的所有内容

我有一个带括号和连字符的 NSString (电话号码) ,因为一些电话号码是格式化的。如何从字符串中删除除数字以外的所有字符?

78589 次浏览

如果您只是想从字符串中获取数字,那么 可以当然会使用正则表达式来解析它们。要在 Objective-C 中执行正则表达式,请查看 RegexKit。正如@Nathan 所指出的,使用 NSScanner 解析字符串中的所有数字是一种更简单的方法。我完全不知道这个选项,所以赞扬他的建议。(我自己甚至不喜欢使用 regex,所以我更喜欢不需要它们的方法。)

如果您想格式化电话号码以便显示,那么值得一看 NSNumberFormatter。我建议你通读 这个相关的 SO 问题以获得这样做的技巧。请记住,电话号码的格式根据位置和/或地区的不同而不同。

没有必要像其他答案所建议的那样使用正则表达式库——您要使用的类名为 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"

编辑: 我更新了代码,因为原始代码已经在我的脑海中被写下来了,我认为这足以为人们指明正确的方向。人们似乎只需要将代码直接复制粘贴到他们的应用程序中就可以了。

我也同意 Michael Pelz-Sherman 的解决方案比使用 NSScanner更合适,所以您可能想看看这个。

这很棒,但是这些代码在 iPhone 3.0 SDK 上对我不起作用。

如果我像您在这里显示的那样定义 strippedString,那么在 scanCharactersFromSet:intoString调用之后尝试打印它时,我会得到一个 BAD ACCESS error

如果我这样做:

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

嗯。第一个答案在我看来完全错了。NSScanner 实际上是用于解析的。与正则表达式不同,它让您一次解析一小块字符串。你用一个字符串初始化它,它维护一个索引,表示它沿着字符串走了多远; 这个索引总是它的参考点,你给它的任何命令都是相对于这个点的。你告诉它,“好的,给我这个集合中的下一个字符块”或者“给我你在字符串中找到的整数”,这些都从当前索引开始,然后向前移动,直到找到不匹配的字符。如果第一个字符已经不匹配,那么该方法返回 NO,而索引不会增加。

第一个例子中的代码是扫描“(123-RRB-456-7890)十进制字符,从第一个字符开始就已经失败了,所以调用 scanPersontersFromSet: intoString: 只留下传入的 strippedString,并返回 NO; 代码完全忽略检查返回值,留下 strippedString 未分配。即使第一个字符是一个数字,这个代码也会失败,因为它只会返回找到的数字,直到第一个破折号或括号或其他什么。

如果你真的想要使用 NSScanner,你可以把这样的东西放在一个循环中,并不断检查一个 NO 返回值,如果你得到的,你可以增加 ScanLocation 并再次扫描; 你还必须检查 isAtEnd,和 yada yada yada。简而言之,这项工作的错误工具。Michael 的解决方案更好。

老问题了,不如这样:

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

它在非数字集合上爆炸源字符串,然后使用空字符串分隔符重新组合它们。不像挑选字符那样有效,但代码更紧凑。

谢谢你的例子。它只有一个地方缺少 ScanLocation 的增量,以防在数字 CharterSet 对象中找不到 OrigalString 中的一个字符。我已经添加了 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"
NSString *originalPhoneNumber = @"(123) 123-456 abc";
NSCharacterSet *numbers = [[NSCharacterSet characterSetWithCharactersInString:@"0123456789"] invertedSet];
NSString *trimmedPhoneNumber = [originalPhoneNumber stringByTrimmingCharactersInSet:numbers];

];

简单点!

对于被问到的问题,人们普遍接受的答案有些过头了。这个问题要简单得多:

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

虽然这是一个老问题与工作的答案,我错过了 国际格式支持。基于 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作为一个通用的国际电话号码格式。

对于那些搜索电话提取的人,您可以使用 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]);
}
}
}
}

`

我在 NSString 上创建了一个类别来简化这个常见操作。

允许字符串 + 允许字符

@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

将顶级解决方案作为一个类别来帮助解决更广泛的问题:

界面:

@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:@""];

可以对可变字符串使用正则表达式:

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


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

值得注意的是,公认的基于 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。

只接受手机号码

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

这是斯威夫特的版本。

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

最受欢迎答案的快速版本:

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: "")

基于 Jon Vogel 在这里的回答,它是一个 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 == "")
}
}

这回答了 OP 的问题,但是它可以很容易地修改为留下电话号码相关的字符,如“ ,; * # +”

Swift 3

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

我认为目前最好的办法是:

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

Swift 4.1

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

Swift 5

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