Advertisement
devidark

Search Suggestions

Sep 15th, 2015
1,507
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C++ 8.31 KB | None | 0 0
  1. /*!
  2.  * \brief  Search suggestions.
  3.  *
  4.  * \author Alexey Medveschek
  5.  * \copy   2015 (C) Go.Mail.Ru
  6.  */
  7.  
  8. #include <iostream>
  9. #include <fstream>
  10. #include <sstream>
  11. #include <map>
  12. #include <vector>
  13. #include <string>
  14.  
  15. typedef unsigned int id_t;           //!< индекс подсказки
  16. typedef std::vector<id_t> idvec_t;   //!< списки (posting lists); будем пересекать при поиске
  17.  
  18. const id_t kInvalidId = (id_t)(~0U); //!< специальное обозначение "пустого" id
  19.  
  20. //-----------------------------------------------
  21. //! Обратный индекс, он же трай, он же узел трая
  22. struct InvertedIndex {
  23.     typedef std::map<char, InvertedIndex> trans_t; //!< таблица переходов
  24.     trans_t trans_;
  25.  
  26.     idvec_t ids_;
  27.  
  28.     //! Добавить слово в обратный индекс
  29.     void add_word(const std::string& word, id_t id) {
  30.         InvertedIndex* node = this;
  31.  
  32.         id_t last_id = kInvalidId;
  33.  
  34.         std::string::const_iterator wi = word.begin(), wend = word.end();
  35.         for (; wi != wend; ++wi) {
  36.             // на каждый символ в каждую ноду добавляем id подсказки
  37.             node = &node->trans_[ *wi ];
  38.             // добавляем только уникальные id
  39.             last_id = (!node->ids_.empty() ? node->ids_[node->ids_.size()-1] : kInvalidId);
  40.             if (last_id != id) {
  41.                 node->ids_.push_back( id );
  42.             }
  43.         }
  44.     }
  45.  
  46.     //! Найти ноду по префиксу; NULL - если строка не найдена
  47.     const InvertedIndex* find(const std::string& prefix) const {
  48.         const InvertedIndex* node = this;
  49.  
  50.         std::string::const_iterator pi = prefix.begin(), pend = prefix.end();
  51.         for (; pi != pend; ++pi) {
  52.             trans_t::const_iterator ni = node->trans_.find( *pi );
  53.             if (ni == node->trans_.end()) {
  54.                 return NULL;
  55.             }
  56.  
  57.             node = &ni->second;
  58.         }
  59.  
  60.         return node;
  61.     }
  62. };
  63.  
  64. InvertedIndex inverted_index;
  65.  
  66. //-----------------------------------------------
  67. //! Прямой индекс
  68. struct Sugg {
  69.     std::string text_;
  70.     unsigned int line_; //!< Вместо веса храним номер строки в файле
  71.     Sugg(const std::string& text, unsigned line): text_(text), line_(line) {}
  72. };
  73.  
  74. std::vector<Sugg> forward_index;
  75.  
  76. //-----------------------------------------------
  77. // Парсит из входного потока подскакзи и добавляет их в индекс.
  78. void build_index(std::istream& in) {
  79.     std::string line, word;
  80.  
  81.     unsigned int line_num = 0;
  82.     while (std::getline(in, line)) {
  83.         // добавляем подсказку в прямой индекс
  84.         const id_t id = forward_index.size();
  85.         forward_index.push_back( Sugg(line, ++line_num) );
  86.  
  87.         // теперь в обратный
  88.         std::stringstream s_line(line);
  89.         while (std::getline(s_line, word, ' ')) {
  90.             if (word.empty()) continue; // пропускаем двойные пробелы
  91.             inverted_index.add_word(word, id);
  92.         }
  93.     }
  94. }
  95.  
  96. //-----------------------------------------------
  97. // "Ленивый" алгоритм пересечения сортированных по неубыванию списков;
  98. // пересекает до получения TOP-K первых результатов.
  99. // Возвращает true, если пересечение не пустое.
  100. bool intersect_sorted_lists(const std::vector<const idvec_t*>& lists, size_t top, idvec_t & res) {
  101.     res.clear();
  102.  
  103.     const size_t lnum = lists.size();
  104.     if (! lnum)         // нет списков для пересечения
  105.         return false;
  106.  
  107.     if (lnum == 1) {    // единственный список
  108.         res = *lists[0];
  109.         top = std::min(top, res.size());
  110.         res.resize( top );
  111.         return (! res.empty());
  112.     }
  113.  
  114.     // подготавливаем пары iterator-end
  115.     std::vector<std::pair<const id_t*, const id_t*> > lptrs(lnum);
  116.     for (size_t i = 0; i < lnum; ++i) {
  117.         const idvec_t & idvec = *lists[i];
  118.  
  119.         if (idvec.empty())      // один из списков пустой
  120.             return false;
  121.  
  122.         lptrs[i].first  = &idvec[0];
  123.         lptrs[i].second = &idvec[0] + idvec.size();
  124.     }
  125.  
  126.     size_t li = 0, found = 0;
  127.     id_t cur_id = *(lptrs[0].first);
  128.  
  129.     // идём, пока один из списков не закончился и пока не набрали нужное кол-во результатов
  130.     while (res.size() < top) {
  131.         // ищем текущий элемент в списке
  132.         while (lptrs[li].first < lptrs[li].second && *lptrs[li].first < cur_id) {
  133.             ++lptrs[li].first;
  134.         }
  135.         // один из списков закончился
  136.         if (lptrs[li].first == lptrs[li].second) {
  137.             break;
  138.         }
  139.        
  140.         const id_t check_id = *(lptrs[li].first);
  141.  
  142.         if (check_id == cur_id) {
  143.             ++found;
  144.             if (found == lnum) {        // искомый элемент нашёлся во всех списках
  145.                 res.push_back( cur_id );
  146.                 // начинаем поиск нового элемента
  147.                 ++lptrs[li].first;
  148.                 if (lptrs[li].first < lptrs[li].second) {
  149.                     cur_id = *lptrs[li].first;
  150.                     found = 1;
  151.                 } else {
  152.                     break;              // один из списков закончился
  153.                 }
  154.             }
  155.         } else {
  156.             // начинаем поиск нового элемента
  157.             cur_id = check_id;
  158.             found = 1;
  159.         }
  160.  
  161.         li = (li + 1) % lnum;           // на след. список
  162.     }
  163.  
  164.     return (! res.empty());
  165. }
  166.  
  167. //-----------------------------------------------
  168. // Поиск подсказок по заданному запросу
  169. // возвращает top первых самых весомых подсказок, вектор пар: подсказка-вес
  170. bool search(const std::string& q, size_t top, std::vector<Sugg>& res) {
  171.     res.clear();
  172.  
  173.     // разбиваем запрос на слова-префиксы, получаем списки для пересечения
  174.     std::vector<const idvec_t*> lists;
  175.  
  176.     std::string word;
  177.     std::stringstream stext(q);
  178.     while (std::getline(stext, word, ' ')) {
  179.         if (word.empty()) continue; // пропускаем двойные пробелы
  180.         const InvertedIndex* node = inverted_index.find(word);
  181.         if (! node)
  182.             return false;
  183.         lists.push_back( &node->ids_ );
  184.     }
  185.  
  186.     if (lists.empty()) return false;
  187.  
  188.     // пересекаем списки
  189.     idvec_t intersection;
  190.     if (! intersect_sorted_lists(lists, top, intersection))
  191.         return false;
  192.  
  193.     // извлекаем данные из прямого индекса
  194.     for (size_t i = 0; i < intersection.size(); ++i) {
  195.         const id_t id = intersection[i];
  196.         res.push_back( forward_index[id] );
  197.     }
  198.  
  199.     return true;
  200. }
  201.  
  202. //-----------------------------------------------
  203. int main(int argc, char* argv[]) {
  204.     if (argc < 2) {
  205.         std::cerr << "Usage: " << argv[0] << " <line_by_line_suggestions_file.txt>" << std::endl;
  206.         return 1;
  207.     }
  208.  
  209.     std::cerr << "Adding suggestions to index..." << std::endl;
  210.     std::ifstream in(argv[1]);
  211.     build_index(in);
  212.     std::cerr << "Successfully added " << forward_index.size() << " suggestions\n";
  213.  
  214.     std::cerr << "Ready. Type any incomplete query:" << std::endl;
  215.  
  216.     std::string query;
  217.     const size_t top = 10;
  218.     std::vector<Sugg> res;
  219.  
  220.     while (std::getline(std::cin, query)) {
  221.         if (! search(query, top, res)) {
  222.             std::cout << "\t<nothing to suggest>\n" << std::endl;
  223.             continue;
  224.         }
  225.  
  226.         std::cout << query << std::endl;
  227.         for (size_t i = 0; i < res.size(); ++i) {
  228.             std::cout << "\t" << res[i].line_ << ". "
  229.                       << res[i].text_ << std::endl;
  230.         }
  231.         std::cout << "\n";
  232.     }
  233.  
  234.     return 0;
  235. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement