Инвертированный индекс: найти фразу в наборе документов

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

у меня большая база данных текста, и я держу индекс, который говорит мне, для каждого слова, в каком файле он (IDdoc), и где в файле он (position). (Слова могут быть во многих файлах и во многих местах в одном файле.)

таким образом, я сохраняю вектор для каждого слова:

vector<pair<IDdoc,position>> occurences_of_word;

(вектор отсортирован по IDdoc и затем по положению, в порядке возрастания.)

у меня есть

3 ответов


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

#include <vector>
#include <map>
#include <set>
#include <iostream>
#include <string>

typedef std::string IDdoc;
typedef int position;

typedef std::pair<IDdoc,position> Occurrence;
typedef std::vector<Occurrence> OccurrencesOfWord;
typedef std::map<std::string /*word*/, OccurrencesOfWord> Dictionary;
typedef std::set<IDdoc> Matches;

bool findMatchesForPhrase(const std::string& phrase, const Dictionary& dictionary, Matches& matches)
{
    size_t pos = 0;
    size_t len = 0;
    while (pos < phrase.length()) {
        size_t end = phrase.find(' ', pos);
        size_t len = ((end == phrase.npos) ? phrase.length() : end) - pos;
        std::string word(phrase, pos, len);
        pos += len + 1; // to skip the space.

        // ignore words not in the dictionary.
        auto dictIt = dictionary.find(word);
        if (dictIt == dictionary.end())
            continue;

        auto& occurrences = dictIt->second; // shortcut/alias,.
        for (auto& occurIt : occurrences) {
            // Add all the IDdoc's of this occurence to the set.
            matches.insert(occurIt.first);
        }
    }

    return !matches.empty();
}

void addToDictionary(Dictionary& dict, const char* word, const char* doc, int position)
{
    dict[word].push_back(std::make_pair(std::string(doc), position));
}

int main(int argc, const char** argv)
{
    std::string phrase("pizza is life");
    Dictionary dict;

    addToDictionary(dict, "pizza", "book1", 10);
    addToDictionary(dict, "pizza", "book2", 30);
    addToDictionary(dict, "life", "book1", 1);
    addToDictionary(dict, "life", "book3", 1);
    addToDictionary(dict, "goat", "book4", 99);

    Matches matches;
    bool result = findMatchesForPhrase(phrase, dict, matches);

    std::cout << "result = " << result << std::endl;
    for (auto& ent : matches) {
        std::cout << ent << std::endl;
    }

    return 0;
}

онлайн демо это: http://ideone.com/Zlhfua


продолжение адрес ваших изменений:

while(i < SIZE_VECTOR_ONE  && j < SIZE_VECTOR_TWO)
{
    if (ID_doc_one < ID_doc_two)
    {
        ID_doc_one = v1[++i].first;

допустим, что "SIZE_VECTOR 1" равен 1. Это означает, что в векторе есть один элемент, элемент[0]. Если ID_doc_one 0 и ID_doc_two 1, то

if (0 < 1) {
    ID_doc_one = v1[1].first;

что является недопустимым. Возможно, вам лучше использовать итераторы или указатели:

while (oneIt != v1.end() && twoIt != v2.end()) {
    if (oneIt->first < twoIt->first) {
        ++oneIt;
        continue;
    } else if (*twoIt < *oneIt) {
        ++twoIt;
        continue;
    }
    // same documentId in both lists, snag positions.
    ...
}

далее, это выглядит немного сломан:

    else {
    }   // To avoid "out of range" errors <-- but also ends the "else"
        if (i < SIZE_VECTOR_ONE - 1)
            ID_doc_one = v1[++i].first;
        if (j < SIZE_VECTOR_TWO - 1)
            ID_doc_two = v2[++j].first;
    }

и мне интересно, что произойдет, если у вас один и тот же документ, но несколько позиций?

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

    WordPosition_t pos_one = v1[i].second;
    WordPosition_t pos_two = v2[j].second;

    // The words make a phrase!  Return pos_two for the next intersection finding step
    if (pos_one + 1 == pos_two)

кажется, гораздо яснее написать это так, как вы могли бы сказать "(если второе слово находится в позиции после первого слова):

    WordPosition_t posFirstWord = v1[i].second;
    WordPosition_t posSecondWord = v2[j].second;

    // The words make a phrase!  Return pos_two for the next intersection finding step
    if (posSecondWord == posFirstWord + 1)

эта следующая часть была немного запутанной, так как оба предложения, по-видимому, предназначались для увеличения i и j и обновления ID_doc_one и two, имело бы смысл поднять эту часть в общий раздел после блока if, но снова else {} трудно сказать, что вы вообще-то, делали.

    if (pos_one + 1 == pos_two)
    {
        intersection.push_back(make_pair(ID_doc_one,pos_two));
        ID_doc_one = v1[++i].first;
        ID_doc_two = v2[++j].first;
    }

    else {
    }   // To avoid "out of range" errors
        if (i < SIZE_VECTOR_ONE - 1)
            ID_doc_one = v1[++i].first;
        if (j < SIZE_VECTOR_TWO - 1)
            ID_doc_two = v2[++j].first;
    }

когда вы сопоставляете оба массива, вы всегда хотите увеличить как i, так и j, это не условие, я также не уверен, почему вы используете pos_two, так как фраза была фактически найдена в pos_one?

вот как бы я это написал:

#include<iostream>
#include<map>
#include<vector>
#include<string>

typedef std::string         Word_t;
typedef unsigned int        WordPosition_t;
typedef unsigned int        IDdocument_t;

typedef std::pair<IDdocument_t, WordPosition_t> DocumentPosition_t;
typedef std::vector<DocumentPosition_t> WordReferences_t;

WordReferences_t _intersect_two_words(const WordReferences_t& v1, const WordReferences_t& v2)
{
    // all the locations where the words occur one after the other.
    WordReferences_t intersection;

    auto firstIt = v1.begin();
    auto secondIt = v2.begin();
    while (firstIt != v1.end() && secondIt != v2.end())
    {
        if (firstIt->first < secondIt->first)
        {
            ++firstIt;
            continue;
        }
        // find the second word in the same document and AFTER the first word.
        if (secondIt->first < firstIt->first || secondIt->second < firstIt->second + 1)
        {
            ++secondIt;
            continue;
        }
        // first word wasn't just before the second, it's not a phrase.
        if (secondIt->second > firstIt->second + 1)
        {
            ++firstIt;
            continue;
        }
        // We found a phrase.
        intersection.emplace_back(*firstIt);
        ++firstIt;
        ++secondIt;
    }

    return intersection;
}

int main()
{
    WordReferences_t v1, v2;
    v1.push_back(std::make_pair(10, 5));
    v1.push_back(std::make_pair(10, 25));
    v1.push_back(std::make_pair(11, 10));
    v1.push_back(std::make_pair(12, 1));
    v1.push_back(std::make_pair(12, 11));
    v1.push_back(std::make_pair(12, 21));
    v1.push_back(std::make_pair(12, 31));
    v1.push_back(std::make_pair(15, 11));
    v1.push_back(std::make_pair(100, 1));
    v1.push_back(std::make_pair(100, 11));
    v1.push_back(std::make_pair(100, 21));
    v1.push_back(std::make_pair(101, 11));
    v1.push_back(std::make_pair(102, 11));
    v1.push_back(std::make_pair(102, 13));
    v1.push_back(std::make_pair(102, 14));
    v1.push_back(std::make_pair(103, 11));
    v1.push_back(std::make_pair(103, 13));

    v2.push_back(std::make_pair(10, 11));
    v2.push_back(std::make_pair(12, 10));
    v2.push_back(std::make_pair(12, 40));
    v2.push_back(std::make_pair(16, 11));
    v2.push_back(std::make_pair(100, 12)); // match
    v2.push_back(std::make_pair(101, 12)); // match
    v2.push_back(std::make_pair(101, 13));
    v2.push_back(std::make_pair(101, 14));
    v2.push_back(std::make_pair(102, 12)); //match
    v2.push_back(std::make_pair(103, 1));
    v2.push_back(std::make_pair(103, 10));
    v2.push_back(std::make_pair(103, 12)); // match
    v2.push_back(std::make_pair(103, 15));

    auto intersection = _intersect_two_words(v1, v2);
    for (auto entry : intersection)
    {
        std::cout << entry.first << ", " << entry.second << "+" << (entry.second + 1) << std::endl;
    }

    return 0;
}

живой пример:http://ideone.com/XRfhAI


Я не знаю, является ли это наиболее эффективным, но вы можете начать с документов / позиций words[0]. Тогда иди в words[1] и найти пересекающиеся документы с позиции равной words[0].position + words[0].length + 1 для тех же документов. Затем аналогично повторите остальную часть words. Это должно сузиться довольно быстро для более длинных фраз?


Как вы заявили, структура данных, которую вы используете, на самом деле является полным перевернутым индексом, как указано в Википедии:

существует два основных варианта инвертированных индексов: инвертированный индекс уровня записи (или инвертированный индекс файла или просто инвертированный файл) содержит список ссылок на документы для каждого слова. перевернутый индекс уровня слова (или полный перевернутый индекс или перевернутый список) дополнительно содержит позиции каждого слова в документе.[2] последняя форма предлагает больше функциональности (например, поиск фраз), но требует больше времени и пространства для создания.

Это, как говорится, вы также можете попытаться создать индекс фразы:

http://ww2.cs.mu.oz.au / ~jz / fulltext / acmtois04.pdf

(см. Рисунок 2 в качестве демонстрации).

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