поиск NSArray содержит NSDictionary с эффективностью времени

Я знаю, что уже задано много вопросов, однако я задаю еще один вопрос, чтобы поймать. У меня есть массив, огромное количество (тысячи записей) данных в формате NSDictionary, я пытаюсь выполнить поиск по ключу в словарях в массив.

Я выполняю поиск в методе источника данных UITextField - (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string; ,

Мои требования к поиску находятся внутри целых строк,

примеры строк,

aaa, abeb, abcd, abbec, как строки

поисковый поток,

если a вернет мне все строки,

если aa возвращает только aaa,

если ab верни abeb, abcd, abbec лайк,

важно, если это cd, то возвращает только abcd

Я пробовал это, используя эти способы,

Использование предикатов NSP

NSLog(@"start search at : %@",[NSDate date]);
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"Name contains[cd] %@", matchString];
searchArray = [[meadArray filteredArrayUsingPredicate:predicate] mutableCopy];
NSLog(@"Search found count = %d",searchArray.count);
[tableCheck reloadData];
NSLog(@"End search at : %@",[NSDate date]);

другой способ - через Итерацию,

NSLog(@"start search at : %@",[NSDate date]);
for (NSDictionary *word in arrayNames)
{
    if ([matchString length] == 0)
    {
        [searchArray addObject:word];
        continue;
    }
    NSRange lastRange = [[[word valueForKey:@"Name"] uppercaseString] rangeOfString:upString];

    if ( lastRange.location != NSNotFound)
    {
        if(range.location == 0 || lastRange.location == 0)
        {
            [searchArray addObject:word];
        }
    }
}    
NSLog(@"End search at : %@",[NSDate date]);

Оба метода работают нормально, и результаты соответствуют моим ожиданиям, но ТОЛЬКО В СИМУЛЯТОРЕ! когда я тестирую то же самое на устройстве, это занимает около 1 / 2 / 3 секунды в соответствии с расширением поиска, скажем, сначала, если я набираю, a это заняло 3 секунды, для aa это заняло около 2 секунд и так далее. IT LOOKS CLUMSY ON DEVICE, ANY PRESSED KEY WILL BE REMAIN HIGHLIGHTED UNTIL SEARCH NOT DONE.

Есть ли способ, которым я могу выполнить еще более быстрый поиск, используя тот же метод, который я использую, или любые другие альтернативы!

Обновление 1

Также пробовал с CFArrayBSearchValues. Он возвращает только индекс для строки поиска, но я хочу что-то, что возвращает все совпадающие строки.

unsigned index = (unsigned)CFArrayBSearchValues((CFArrayRef)meadArray, CFRangeMake(0, CFArrayGetCount((CFArrayRef)meadArray)), (CFStringRef)matchString,(CFComparatorFunction)CFStringCompare, NULL);

Обновление 2

Согласно комментарию Alladinian, я выполнил операцию поиска в фоновом потоке, да, теперь его пользовательский интерфейс не заблокирован, но поиск все еще слишком медленный. Что я делаю, так это выполняю селектор с некоторой задержкой, скажем, 0,25 секунд, также отменяя все предыдущие вызовы селектора, а затем выполняя поиск в фоновом режиме, а также перезагружая таблицу в основном потоке. Это работает так: если я набираю символ с некоторой задержкой, он работает хорошо, но если я набираю целое слово сразу, он загружает/обновляет таблицу в соответствии с нажатыми символами, наконец, он покажет мне фактический вывод, занимает 3- 4 секунды для показа фактического содержания.

Любое предложение или помощь высоко ценится!


person Hemang    schedule 07.03.2013    source источник


Ответы (5)


Ваш поиск идет медленнее, чем нужно. searchString.length не следует проверять в цикле, это нужно делать снаружи. valueForKey: это очень общий метод, который позволяет вам получить доступ к любым видам различных путей ключей — objectForKey намного быстрее! Затем ваша проверка на совпадение с учетом регистра всегда переводит все слово в верхний регистр. Это не только неверно, когда ваши пользователи вводят несколько более интересных поисковых строк, но также замедляет работу и интенсивно использует память (если есть тысячи строк, вы создаете тысячи автоматически освобождаемых объектов). Вместо этого используйте rangeOfString:options:.

Наконец, вы можете попробовать методы NSArray enumerateObjectsUsingBlock: или enumerateObjectsWithOptions:usingBlock:, которые позволяют выполнять перечисление в нескольких потоках.

indexesOfObjectsPassingTest: или indexesOfObjectsWithOptions:passingTest: получает индексы искомых элементов, что будет быстрее. Это особенно полезно, если вы выполняете еще один поиск более длинной строки, например, ищете abc после поиска ab, потому что существуют методы NSArray, ограничивающие поиск набором индексов.

person gnasher729    schedule 11.03.2014

Я использовал модули полнотекстового поиска sqlite в нескольких проектах, и они работают очень хорошо. Это потребует предоставления вашей собственной скомпилированной версии sqlite, что может быть немного сложно, но весь процесс объясняется здесь.

В зависимости от того, что вам нужно сделать, вы можете захотеть предварительно заполнить эту базу данных или вставить данные по мере их получения, или и то, и другое одновременно. Попытка запрограммировать решение самостоятельно была бы интересным упражнением, но вы бы заново изобретали алгоритмы, которые уже существуют в sqlite FTS.

Если вы выполняете поиск по мере ввода, вы должны убедиться, что вы правильно ставите свои операции в очередь в фоновом режиме и что их можно отменить — есть много способов осуществить это; очень популярный использует NSOperation.

Надеюсь это поможет.

person Andres Kievsky    schedule 10.03.2013

Этот ответ следует за ответом anktastic, я также добавляю свой ответ, потому что кто-то напрямую получит решение, для которого мы приложили много усилий :)

- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string 
{
    //Cancel any previous selector calls
    [NSRunLoop cancelPreviousPerformRequestsWithTarget:self];

        if(string.length > 0)
        {
            NSString *str = [txt1.text substringToIndex:[txt1.text length] - 1];
            //delay is useful in smooth search - if you're performing web service calls for searching on cloud, then you should give delay as per your test
            [self performSelector:@selector(startSearch:) withObject:str afterDelay:0.05f];
        }
    }
}

- (void) startSearch:(NSString *)matchString
{
    //Cancel any previous operation added in queue
    [queue cancelAllOperations];
    //Create new operation
    NSInvocationOperation* operation = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(search:) object:matchString];
    [queue addOperation:operation];
}

- (void) search:(NSString *)matchString
{
    //Performing search operation
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"Name contains[cd] %@", matchString];
    searchArray = [[meadArray filteredArrayUsingPredicate:predicate] mutableCopy];
    //only call again after any previous reload done
    [self performSelectorOnMainThread:@selector(reloadInMainThread) withObject:nil waitUntilDone:YES];
}

- (void) reloadInMainThread 
{
    //Reloading table in main thread for instance search effect
    [tableCheck reloadData];
}
person Hemang    schedule 11.03.2013
comment
Это отлично работает на iOS 7, но, похоже, не работает на iOS 8. - person yoninja; 15.10.2014
comment
Я использовал значение по умолчанию maxConcurrentOperationCount. Пришлось установить его на 1. [_queue setMaxConcurrentOperationCount:1]; Теперь он работает безупречно и на iOS 8. - person yoninja; 15.10.2014

Изменяются ли строки в массиве/словаре? Если нет, я предлагаю вам извлечь все ключи в словаре в один массив. Сделайте это перед поиском, и вам нужно сделать это только один раз.

Если я правильно понял ваш вопрос, мое предложение выглядит так:

NSArray *arrayOfDicts = /*the array with dictionarys you have*/

NSMutableArray *allKeys = [NSMutableArray array]

for (NSDictionary *d in arrayOfDicts) {
     for (NSString *key in d) {
          [allKeys addObject:key];
     }
}

затем найдите массив allKeys.

person CarmeloS    schedule 07.03.2013

Сравнение строк без учета регистра выполняется медленнее, чем сравнение с учетом регистра. Вы можете сохранить uppercaseName как дополнительный ключ в своих словарях (или как дополнительный столбец в таблице sqlite) и изменить предикат на

[NSPredicate predicateWithFormat:@"uppercaseName CONTAINS %@", [matchString uppercaseString]]
person Martin R    schedule 09.03.2013
comment
@jrturton: CONTAINS[c] указывает нечувствительность к регистру (abc и ABC считаются равными), и это должно быть медленнее, чем сравнение с учетом регистра. - Я откатил вашу правку. Пожалуйста, скажите мне, если я что-то совсем не так понял. - person Martin R; 09.03.2013
comment
Извините, я был в полусне. Должен был знать лучше, чем сомневаться в тебе. Я неправильно прочитал ваш ответ и подумал, что вы делаете другую точку зрения. - person jrturton; 09.03.2013