获取NSString中所有大写字母的NSRange对象数组的最快方法?

时间:2011-01-11 01:06:04

标签: ios iphone objective-c nsstring nsrange

我需要NSRange对象作为给定NSString中每个大写字母的位置,以输入自定义属性字符串类的方法。

当然有很多方法可以实现这一点,例如rangeOfString:options:使用NSRegularExpressionSearch或使用RegexKitLite在遍历字符串时单独获取每个匹配。

完成此任务的最快绩效方法是什么?

4 个答案:

答案 0 :(得分:13)

最简单的方法可能是将-rangeOfCharacterFromSet:options:range:[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];
}

答案 1 :(得分:2)

将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进行匹配的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) { /* ... */ }

答案 2 :(得分:1)

这在某种程度上取决于字符串的大小,但是我能想到的绝对最快的方式(注意:国际化安全性无法保证,甚至没有预期!大写的概念是否适用于日语?)是:< / p>

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()将范围缓冲区调回到你实际使用的大小(可能需要保持开始的范围计数)

答案 3 :(得分:0)

isupper *与-[NSString characterAtIndex:]一起使用的功能将非常快。

* isupper就是一个例子 - 它可能适合你的输入,也可能不适合你。