4

Мы скоро приступим к разработке нового мобильного приложения. Это конкретное приложение будет использоваться для интенсивного поиска текстовых полей. Любые предложения от группы в целом о том, какой механизм базы данных лучше всего подходит для разрешения этих типов поиска на мобильной платформе?Полнотекстовый поиск по мобильному устройству?

Особенности включают Windows Mobile 6, и мы будем использовать .Net CF. Кроме того, некоторые текстовые поля будут содержать от 35 до 500 символов. Устройство будет работать в двух разных режимах: пакетном и WiFi. Конечно, для Wi-Fi мы можем просто отправить запросы на полномасштабный механизм БД и просто вернуть результаты обратно. Этот вопрос сосредоточен вокруг «пакетной» версии, в которой будет размещена база данных, загружаемая с информацией о флэш-памяти/съемной карте памяти устройств.

Во всяком случае, я знаю, что SQLCE имеет базовое индексирование, но вы не попадаете в настоящие фантастические индексы стиля «полного текста», пока не получите полную версию, которая, конечно же, недоступна на мобильном телефоне Платформа.

Пример того, что данные будут выглядеть следующим образом:

«фартук плотника регулируемый кожаный контейнер карман талии аппаратные пояса» и т.д. и т.п.

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

Любые предложения/советы?

+0

Все еще ищут ответы на этот вопрос. – 2008-11-22 02:12:20

ответ

5

Совсем недавно у меня была такая же проблема. Вот что я сделал:

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

public class SmallItem 
{ 
    private int _sku; 
    public int Sku 
    { 
     get { return _sku; } 
     set { _sku = value; } 
    } 

    // Size of max description size + 1 for null terminator. 
    private char[] _description = new char[36]; 
    public char[] Description 
    { 
     get { return _description; } 
     set { _description = value; } 
    } 

    public SmallItem() 
    { 
    } 
} 

После создания этого класса, вы можете создать массив (на самом деле я использовал список в моем случае) эти объекты и использовать его для поиска в вашем приложении. Инициализация этого списка занимает немного времени, но вам нужно только беспокоиться об этом при запуске. В основном просто запустите запрос в своей базе данных и возьмите данные, необходимые для создания этого списка.

После того, как у вас есть список, вы можете быстро просмотреть его, ища любые слова, которые вы хотите. Поскольку он содержит, он также должен найти слова в словах (например, сверло вернет сверло, сверло, сверла и т. Д.). Чтобы сделать это, мы написали домашнюю, неуправляемую функцию C#. Он принимает строковый массив слов (так что вы можете искать более одного слова ... мы используем его для поиска «И» ... описание должно содержать все слова, переданные в ... «ИЛИ» в настоящее время не поддерживается в этом примере). По мере поиска по списку слов он строит список идентификаторов, которые затем передаются обратно вызывающей функции. После того, как у вас есть список идентификаторов, вы можете легко запустить быстрый запрос в своей базе данных, чтобы вернуть полномасштабные объекты на основе быстрого индексированного идентификационного номера. Следует отметить, что мы также ограничиваем максимальное количество возвращаемых результатов. Это можно было бы снять. Это просто удобно, если кто-то вводит что-то вроде «e» в качестве условия поиска. Это даст много результатов.

Вот пример обычая Содержит функции:

public static int[] Contains(string[] descriptionTerms, int maxResults, List<SmallItem> itemList) 
{ 
    // Don't allow more than the maximum allowable results constant.    
    int[] matchingSkus = new int[maxResults]; 

    // Indexes and counters. 
    int matchNumber = 0; 
    int currentWord = 0; 
    int totalWords = descriptionTerms.Count() - 1; // - 1 because it will be used with 0 based array indexes 

    bool matchedWord; 

    try 
    { 
     /* Character array of character arrays. Each array is a word we want to match. 
     * We need the + 1 because totalWords had - 1 (We are setting a size/length here, 
     * so it is not 0 based... we used - 1 on totalWords because it is used for 0 
     * based index referencing.) 
     * */ 
     char[][] allWordsToMatch = new char[totalWords + 1][]; 

     // Character array to hold the current word to match. 
     char[] wordToMatch = new char[36]; // Max allowable word size + null terminator... I just picked 36 to be consistent with max description size. 

     // Loop through the original string array or words to match and create the character arrays. 
     for (currentWord = 0; currentWord <= totalWords; currentWord++) 
     { 
      char[] desc = new char[descriptionTerms[currentWord].Length + 1]; 
      Array.Copy(descriptionTerms[currentWord].ToUpper().ToCharArray(), desc, descriptionTerms[currentWord].Length); 
      allWordsToMatch[currentWord] = desc; 
     } 

     // Offsets for description and filter(word to match) pointers. 
     int descriptionOffset = 0, filterOffset = 0; 

     // Loop through the list of items trying to find matching words. 
     foreach (SmallItem i in itemList) 
     { 
      // If we have reached our maximum allowable matches, we should stop searching and just return the results. 
      if (matchNumber == maxResults) 
       break; 

      // Loop through the "words to match" filter list. 
      for (currentWord = 0; currentWord <= totalWords; currentWord++) 
      { 
       // Reset our match flag and current word to match. 
       matchedWord = false; 
       wordToMatch = allWordsToMatch[currentWord]; 

       // Delving into unmanaged code for SCREAMING performance ;) 
       unsafe 
       { 
        // Pointer to the description of the current item on the list (starting at first char). 
        fixed (char* pdesc = &i.Description[0]) 
        { 
         // Pointer to the current word we are trying to match (starting at first char). 
         fixed (char* pfilter = &wordToMatch[0]) 
         { 
          // Reset the description offset. 
          descriptionOffset = 0; 

          // Continue our search on the current word until we hit a null terminator for the char array. 
          while (*(pdesc + descriptionOffset) != '\0') 
          { 
           // We've matched the first character of the word we're trying to match. 
           if (*(pdesc + descriptionOffset) == *pfilter) 
           { 
            // Reset the filter offset. 
              filterOffset = 0; 

            /* Keep moving the offsets together while we have consecutive character matches. Once we hit a non-match 
            * or a null terminator, we need to jump out of this loop. 
            * */ 
            while (*(pfilter + filterOffset) != '\0' && *(pfilter + filterOffset) == *(pdesc + descriptionOffset)) 
            { 
             // Increase the offsets together to the next character. 
             ++filterOffset; 
             ++descriptionOffset; 
            } 

            // We hit matches all the way to the null terminator. The entire word was a match. 
            if (*(pfilter + filterOffset) == '\0') 
            { 
             // If our current word matched is the last word on the match list, we have matched all words. 
             if (currentWord == totalWords) 
             { 
              // Add the sku as a match. 
              matchingSkus[matchNumber] = i.Sku.ToString(); 
              matchNumber++; 

              /* Break out of this item description. We have matched all needed words and can move to 
              * the next item. 
              * */ 
              break; 
             } 

             /* We've matched a word, but still have more words left in our list of words to match. 
             * Set our match flag to true, which will mean we continue continue to search for the 
             * next word on the list. 
             * */ 
             matchedWord = true; 
            } 
           } 

           // No match on the current character. Move to next one. 
           descriptionOffset++; 
          } 

          /* The current word had no match, so no sense in looking for the rest of the words. Break to the 
          * next item description. 
          * */ 
          if (!matchedWord) 
           break; 
         } 
        } 
       } 
      } 
     }; 

     // We have our list of matching skus. We'll resize the array and pass it back. 
     Array.Resize(ref matchingSkus, matchNumber); 
     return matchingSkus; 
    } 
    catch (Exception ex) 
    { 
     // Handle the exception 
    } 
} 

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

По идее исполнения, вот что мы обнаружили (делаем следующие шаги):

  • Поиск ~ 171,000 пунктов
  • Создать список всех соответствующих элементов
  • запрос к базе данных, возвращая только совпадающие предметы
  • Сборка полноцветных предметов (аналогично классу SmallItem, но намного больше полей)
  • Заполните набор данных с помощью объектов с полным ударом.

На наших мобильных устройствах весь процесс занимает 2-4 секунды (требуется 2, если мы достигнем предела соответствия, прежде чем мы будем искать все предметы ... занимает 4 секунды, если нам нужно сканировать каждый элемент).

Я также попытался сделать это без неуправляемого кода и с помощью String.IndexOf (и попробовал String.Contains ... имел такую ​​же производительность, как IndexOf, как и должно). Этот путь был намного медленнее ... около 25 секунд.

Я также попытался использовать StreamReader и файл, содержащий строки [Sku Number] | [Description]. Код был похож на неуправляемый пример кода. Этот путь занял около 15 секунд для полного сканирования. Не так уж плохо для скорости, но не очень. У файла и метода StreamReader есть одно преимущество по сравнению с тем, как я показал вам. Файл может быть создан заранее. Как я показал вам, вам требуется память и начальное время загрузки списка при запуске приложения. Для наших 171 000 предметов это занимает около 2 минут. Если вы можете позволить себе дождаться этой начальной загрузки каждый раз, когда приложение запускается (что может быть сделано на отдельном потоке, конечно), то поиск этого пути является самым быстрым способом (который я нашел как минимум).

Надеюсь, что помогает.

PS - Спасибо Dolch за помощь в работе с неуправляемым кодом.

2

Вы можете попробовать Lucene.Net. Я не уверен, насколько хорошо он подходит для мобильных устройств, но он объявлен как «высокопроизводительная полнофункциональная текстовая поисковая библиотека».

http://incubator.apache.org/lucene.net/ http://lucene.apache.org/java/docs/

+0

Спасибо за предложение. Я посмотрю на это и посмотрю, что подразумевает порт на C#. Моя единственная проблема заключается в том, что он выглядит неактивным сейчас, поскольку проект был последним крупным выпуском в апреле 2007 года. – 2008-11-10 13:01:25

Смежные вопросы