我需要 NSRange 对象来获取给定 NSString 中每个大写字母的位置,以便输入到自定义属性字符串类的方法中。
当然有很多方法可以实现这一点,例如 rangeOfString:options: 使用 NSRegularExpressionSearch 或使用 RegexKitLite 在遍历字符串时分别获取每个匹配项。
完成这项任务的最快执行方法是什么?
我需要 NSRange 对象来获取给定 NSString 中每个大写字母的位置,以便输入到自定义属性字符串类的方法中。
当然有很多方法可以实现这一点,例如 rangeOfString:options: 使用 NSRegularExpressionSearch 或使用 RegexKitLite 在遍历字符串时分别获取每个匹配项。
完成这项任务的最快执行方法是什么?
最简单的方法可能是使用-rangeOfCharacterFromSet:options:range:
with [NSCharacterSet uppercaseLetterCharacterSet]
。通过修改每次调用的搜索范围,您可以很容易地找到所有大写字母。像下面这样的东西可以给你一个所有范围的 NSArray(编码为 NSValues):
- (NSArray *)rangesOfUppercaseLettersInString:(NSString *)str {
NSCharacterSet *cs = [NSCharacterSet uppercaseLetterCharacterSet];
NSMutableArray *results = [NSMutableArray array];
NSRange searchRange = NSMakeRange(0, [str length]);
NSRange range;
while ((range = [str rangeOfCharacterFromSet:cs options:0 range:searchRange]).location != NSNotFound) {
[results addObject:[NSValue valueWithRange:range]];
searchRange = NSMakeRange(NSMaxRange(range), [str length] - NSMaxRange(range));
}
return results;
}
请注意,这不会将相邻范围合并为一个范围,但这很容易添加。
这是基于 NSScanner 的替代解决方案:
- (NSArray *)rangesOfUppercaseLettersInString:(NSString *)str {
NSCharacterSet *cs = [NSCharacterSet uppercaseLetterCharacterSet];
NSMutableArray *results = [NSMutableArray array];
NSScanner *scanner = [NSScanner scannerWithString:str];
while (![scanner isAtEnd]) {
[scanner scanUpToCharactersFromSet:cs intoString:NULL]; // skip non-uppercase characters
NSString *temp;
NSUInteger location = [scanner scanLocation];
if ([scanner scanCharactersFromSet:cs intoString:&temp]) {
// found one (or more) uppercase characters
NSRange range = NSMakeRange(location, [temp length]);
[results addObject:[NSValue valueWithRange:range]];
}
}
return results;
}
与上一个不同,这个确实将相邻的大写字符合并到一个范围内。
编辑:如果您正在寻找绝对速度,这可能是此处介绍的 3 个中最快的,同时仍保留正确的 unicode 支持(注意,我没有尝试编译它):
// returns a pointer to an array of NSRanges, and fills in count with the number of ranges
// the buffer is autoreleased
- (NSRange *)rangesOfUppercaseLettersInString:(NSString *)string count:(NSUInteger *)count {
NSMutableData *data = [NSMutableData data];
NSUInteger numRanges = 0;
NSUInteger length = [string length];
unichar *buffer = malloc(sizeof(unichar) * length);
[string getCharacters:buffer range:NSMakeRange(0, length)];
NSCharacterSet *cs = [NSCharacterSet uppercaseLetterCharacterSet];
NSRange range = {NSNotFound, 0};
for (NSUInteger i = 0; i < length; i++) {
if ([cs characterIsMember:buffer[i]]) {
if (range.location == NSNotFound) {
range = (NSRange){i, 0};
}
range.length++;
} else if (range.location != NSNotFound) {
[data appendBytes:&range length:sizeof(range)];
numRanges++;
range = (NSRange){NSNotFound, 0};
}
}
if (range.location != NSNotFound) {
[data appendBytes:&range length:sizeof(range)];
numRanges++;
}
if (count) *count = numRanges;
return [data bytes];
}
将 RegexKitLite 4.0+ 与支持 Blocks 的运行时一起使用,这可能非常简单:
NSString *string = @"A simple String to TEST for Upper Case Letters.";
NSString *regex = @"\\p{Lu}";
[string enumerateStringsMatchedByRegex:regex options:RKLNoOptions inRange:NSMakeRange(0UL, [string length]) error:NULL enumerationOptions:RKLRegexEnumerationCapturedStringsNotRequired usingBlock:^(NSInteger captureCount, NSString * const capturedStrings[captureCount], const NSRange capturedRanges[captureCount], volatile BOOL * const stop) {
NSLog(@"Range: %@", NSStringFromRange(capturedRanges[0]));
}];
正则表达式\p{Lu}
说“匹配所有字符与 'Letter' 的 Unicode 属性也是'大写'”。
该选项RKLRegexEnumerationCapturedStringsNotRequired
告诉 RegexKitLite 它不应该创建NSString
对象并通过capturedStrings[]
. 这节省了相当多的时间和内存。唯一传递给块的是NSRange
匹配 via 的值capturedRanges[]
。
这有两个主要部分,第一个是 RegexKitLite 方法:
[string enumerateStringsMatchedByRegex:regex
options:RKLNoOptions
inRange:NSMakeRange(0UL, [string length])
error:NULL
enumerationOptions:RKLRegexEnumerationCapturedStringsNotRequired
usingBlock:/* ... */
];
...第二个是作为参数传递给该方法的块:
^(NSInteger captureCount,
NSString * const capturedStrings[captureCount],
const NSRange capturedRanges[captureCount],
volatile BOOL * const stop) { /* ... */ }
这在一定程度上取决于字符串的大小,但我能想到的绝对最快的方法(注意:国际化安全不能保证,甚至不能预期!大写的概念甚至适用于日语吗?)是:
1) 获取指向字符串的原始 C 字符串的指针,如果它足够小,最好在堆栈缓冲区中。CFString 有这方面的功能。阅读 CFString.h 中的注释。
2) malloc() 一个足够大的缓冲区,可以为字符串中的每个字符保存一个 NSRange。
3) 像这样的东西(完全未经测试,写入此文本字段,请原谅错误和拼写错误)
NSRange *bufferCursor = rangeBuffer;
NSRange range = {NSNotFound, 0};
for (int idx = 0; idx < numBytes; ++idx) {
if (isupper(buffer[idx])) {
if (range.length > 0) { //extend a range, we found more than one uppercase letter in a row
range.length++;
} else { //begin a range
range.location = idx;
range.length = 1;
}
}
else if (range.location != NSNotFound) { //end a range, we hit a lowercase letter
*bufferCursor = range;
bufferCursor++;
range.location = NSNotFound;
}
}
4) realloc() 范围缓冲区回落到你实际使用的大小(可能需要保持范围的计数开始这样做)
诸如isupper
* 之类的函数与 with 结合起来-[NSString characterAtIndex:]
会非常快。
*isupper 是一个例子——它可能适合也可能不适合您的输入。