NSPredicate против NSString: что лучше / быстрее для поиска суперструн? - PullRequest
4 голосов
/ 01 июня 2011

У меня есть большое количество строк, которые я ищу, чтобы увидеть, существует ли данная подстрока. Кажется, есть два разумных способа сделать это.

Вариант 1: используйте метод NSString rangeOfSubstring и проверьте, существует ли .location:

NSRange range = [string rangeOfSubstring:substring];
return (range.location != NSNotFound);

Вариант 2. Используйте синтаксис NSPredicate CONTAINS:

NSPredicate *regex = [NSPredicate predicateWithFormat:@"SELF CONTAINS %@", substring];
return ([regex evaluateWithObject:string] == YES)

Какой метод лучше, или есть хороший вариант 3, который мне совершенно не хватает? Нет, я не совсем уверен, что я имею в виду под «лучше», но, возможно, я имею в виду быстрее, когда повторяется много, много string с.

Ответы [ 2 ]

18 голосов
/ 01 июня 2011

Вы должны оценить и определить время любого решения, которое использует NSPredicate, потому что по моему опыту NSPredicate может быть очень медленным.

Для простоты я бы использовал простой цикл for(NSString *string in stringsArray) { }. Тело цикла будет содержать простую проверку rangeOfSubstring. Возможно, вы сможете улучшить производительность на несколько процентов, используя CFStringFind(), но вы увидите преимущество только в том случае, если вы будете искать по множеству строк. Преимущество использования CFStringFind() состоит в том, что вы можете избежать (очень небольших) накладных расходов на отправку сообщений Objective-C. Опять же, это обычно только выигрыш, чтобы переключиться на это, когда вы ищете «много» строк (для некоторого всегда изменяющегося значения «много»), и вы всегда должны тестировать, чтобы быть уверенным. Предпочитайте более простой способ Objective-C rangeOfString:, если можете.

Гораздо более сложный подход - использовать функцию ^ Blocks с опцией NSEnumerationConcurrent. NSEnumerationConcurrent - это только подсказка, что вы хотите, чтобы перечисление происходило одновременно, если это возможно, и реализация может игнорировать эту подсказку, если она не может поддерживать одновременное перечисление. Однако ваш стандарт NSArray, скорее всего, будет реализовывать параллельное перечисление. На практике это приводит к разделению всех объектов в NSArray и разделению их по доступным процессорам. Вы должны быть осторожны с тем, как изменить состояние и объекты, к которым имеет доступ блок ^ через несколько потоков. Вот один из возможных способов сделать это:

// Be sure to #include <libkern/OSAtomic.h>

__block volatile OSSpinLock spinLock = OS_SPINLOCK_INIT;
__block NSMutableArray *matchesArray = [NSMutableArray array];

[stringsToSearchArray enumerateObjectsWithOptions:NSEnumerationConcurrent usingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
    NSRange matchedRange = [obj rangeOfString:@"this"];
    if(matchedRange.location != NSNotFound) {
      OSSpinLockLock((volatile OSSpinLock * volatile)&spinLock);
      [matchesArray addObject:obj];
      OSSpinLockUnlock((volatile OSSpinLock * volatile)&spinLock);
    }
  }];

// At this point, matchesArray will contain all the strings that had a match.

Используется облегченный OSSpinLock, чтобы убедиться, что только один поток имеет доступ и обновляет matchesArray одновременно. Вы можете использовать то же самое предложение CFStringFind() сверху здесь.

Кроме того, вы должны знать, что rangeOfString: само по себе не будет соответствовать "границам слов". В приведенном выше примере я использовал слово this, которое будет соответствовать строке A paleolithist walked in to the bar..., даже если оно не содержит слова this.

Самое простое решение этой маленькой складки - использовать регулярное выражение ICU и воспользоваться его функциональностью «расширенного разбиения по словам». Для этого у вас есть несколько вариантов:

  • NSRegularExpression, в настоящее время доступно только на> 4.2 или> 4.3 iOS (я забыл, какой).
  • RegexKit Lite , через RegexKitLite-4.0.tar.bz2
  • NSPredicate, через SELF MATCHES '(?w)\b...\b'. Преимущество этого состоит в том, что он не требует ничего лишнего (то есть RegexKit Lite ) и доступен во всех (?) Версиях Mac OS X и iOS> 3.0.

Следующий код показывает, как использовать расширенные функциональные возможности разбиения по словам в регулярных выражениях ICU через NSPredicate:

NSString *searchForString = @"this";
NSString *regexString = [NSString stringWithFormat:@".*(?w:\\b\\Q%@\\E\\b).*", searchForString];
NSPredicate *wordBoundaryRegexPredicate = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", regexString];
NSArray *matchesArray = [stringsToSearchArray filteredArrayUsingPredicate:wordBoundaryRegexPredicate];

Вы можете сделать поиск нечувствительным к регистру, заменив (?w: в regexString на (?wi:.

Регулярное выражение, если вам интересно, в основном говорит

  • .*(?w:...).* говорит "сопоставить что-либо до и после (?w:...) части" (т.е. мы заинтересованы только в части (?w:...)).
  • (?w:...) говорит: «Включите в скобках расширенную функцию прерывания / поиска слов ICU».
  • \\b...\\b (который на самом деле является всего лишь одним обратным слешем, любой обратный слеш должен быть экранирован, когда он находится внутри строки @""), говорит «Совпадение на границе слова».
  • \\Q...\\E говорит: «Обрабатывайте текст, начинающийся сразу после \Q и до \E, как буквальный текст (подумайте« Цитата »и« Конец »)». Другими словами, любые символы в «цитируемом буквальном тексте» не имеют своего специального регулярного значения.

Причина \Q...\E в том, что вы, вероятно, хотите сопоставить буквенные символы в searchForString. Без этого searchForString будет рассматриваться как часть регулярного выражения. Например, если searchForString было this?, то без \Q...\E было бы не , совпадающее с литеральной строкой this?, но либо thi или this что, вероятно, не то, что вы хотите. :)

2 голосов
/ 01 июня 2011

Случай (n): Если у вас есть массив строк для проверки подстроки, будет лучше использовать NSPredicate.

NSPredicate *regex = [NSPredicate predicateWithFormat:@"SELF CONTAINS %@", substring];
NSArray *resultArray = [originalArrayOfStrings filteredArrayUsingPredicate:regex];

Это вернет массив строк, которые содержат подстроку.

Если вы используете NSRange, в этом случае вам нужно вручную перебрать все строковые объекты массива, и, очевидно, это будет медленнее, чем NSPredicate.

...