Самый быстрый способ получить массив объектов NSRange для всех заглавных букв в строке NSString? - PullRequest
4 голосов
/ 11 января 2011

Мне нужны объекты NSRange для положения каждой заглавной буквы в заданной строке NSString для ввода в метод для пользовательского атрибутного класса строк.

Существует, конечно, немало способов сделать это, например rangeOfString: options: с NSRegularExpressionSearch или с помощью RegexKitLite, чтобы получить каждое совпадение отдельно при обходе строки.

Каким был бы самый быстрый подход для выполнения этой задачи?

Ответы [ 4 ]

13 голосов
/ 11 января 2011

Самый простой способ, вероятно, использовать -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 представленных здесь, сохраняя при этом правильную поддержку юникода (обратите внимание, я не пробовал компилировать это):

// 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];
}
2 голосов
/ 11 января 2011

При использовании RegexKitLite 4.0+ со средой выполнения, которая поддерживает блоки, это может быть очень быстрым:

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} говорит: «Сопоставьте все символы со свойством Unicode« Letter », которые«Верхний регистр».

Опция 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) { /* ... */ }
1 голос
/ 11 января 2011

Это в некоторой степени зависит от размера строки, но самый быстрый способ, который я могу придумать (примечание: безопасность интернационализации не гарантирована или даже не ожидается! Применима ли концепция прописных букв, скажем, на японском?):

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 () буфер диапазона обратно до размера, который вы фактически использовали (может потребоваться сохранить количество диапазонов, начавших это делать)

0 голосов
/ 11 января 2011

функция, такая как isupper* в сочетании с -[NSString characterAtIndex:], будет достаточно быстрой.

* isupper является примером - она ​​может или не может подходить для вашего ввода.

...