поиск 25 000 слов в тексте - PullRequest
       33

поиск 25 000 слов в тексте

17 голосов
/ 30 сентября 2008

Мне нужно найти вхождения ~ 25 000 слов в тексте. Какой алгоритм / библиотека наиболее подходит для этой цели?

целевой язык - C ++

Ответы [ 12 ]

15 голосов
/ 30 сентября 2008

Однажды я использовал алгоритм Бойера-Мура, и он был довольно быстрым.

Бойер-Мур не способен эффективно искать много слов. На самом деле для этого существует очень эффективный алгоритм, называемый алгоритмом Ву-Манбера. Я выложу справочную реализацию. Заметьте, однако, что я сделал это некоторое время назад только для образовательных целей. Следовательно, реализация на самом деле не подходит для непосредственного использования, и ее также можно сделать более эффективной.

Он также использует stdext::hash_map от Dinkumware STL. Подстановка с std::tr1::unordered_map или соответствующей реализацией.

Это объяснение алгоритма содержится в сценарии лекции из лекции в Свободном университете в Берлине, которую проводит Кнут Рейнерт.

Оригинальная бумага также онлайн (только что нашла ее снова), но мне не особо нравится представленный там псевдокод.

#ifndef FINDER_HPP
#define FINDER_HPP

#include <string>

namespace thru { namespace matching {

class Finder {
public:
    virtual bool find() = 0;

    virtual std::size_t position() const = 0;

    virtual ~Finder() = 0;

protected:
    static size_t code_from_chr(char c) {
        return static_cast<size_t>(static_cast<unsigned char>(c));
    }
};

inline Finder::~Finder() { }

} } // namespace thru::matching

#endif // !defined(FINDER_HPP)

#include <vector>
#include <hash_map>

#include "finder.hpp"

#ifndef WUMANBER_HPP
#define WUMANBER_HPP

namespace thru { namespace matching {

class WuManberFinder : public Finder {
public:

    WuManberFinder(std::string const& text, std::vector<std::string> const& patterns);

    bool find();

    std::size_t position() const;

    std::size_t pattern_index() const;

private:

    template <typename K, typename V>
    struct HashMap {
        typedef stdext::hash_map<K, V> Type;
    };

    typedef HashMap<std::string, std::size_t>::Type shift_type;
    typedef HashMap<std::string, std::vector<std::size_t> >::Type hash_type;

    std::string const& m_text;
    std::vector<std::string> const& m_patterns;
    shift_type m_shift;
    hash_type m_hash;
    std::size_t m_pos;
    std::size_t m_find_pos;
    std::size_t m_find_pattern_index;
    std::size_t m_lmin;
    std::size_t m_lmax;
    std::size_t m_B;
};

} } // namespace thru::matching

#endif // !defined(WUMANBER_HPP)

#include <cmath>
#include <iostream>

#include "wumanber.hpp"

using namespace std;

namespace thru { namespace matching {

WuManberFinder::WuManberFinder(string const& text, vector<string> const& patterns)
    : m_text(text)
    , m_patterns(patterns)
    , m_shift()
    , m_hash()
    , m_pos()
    , m_find_pos(0)
    , m_find_pattern_index(0)
    , m_lmin(m_patterns[0].size())
    , m_lmax(m_patterns[0].size())
    , m_B()
{
    for (size_t i = 0; i < m_patterns.size(); ++i) {
        if (m_patterns[i].size() < m_lmin)
            m_lmin = m_patterns[i].size();
        else if (m_patterns[i].size() > m_lmax)
            m_lmax = m_patterns[i].size();
    }

    m_pos = m_lmin;
    m_B = static_cast<size_t>(ceil(log(2.0 * m_lmin * m_patterns.size()) / log(256.0)));

    for (size_t i = 0; i < m_patterns.size(); ++i)
        m_hash[m_patterns[i].substr(m_patterns[i].size() - m_B)].push_back(i);

    for (size_t i = 0; i < m_patterns.size(); ++i) {
        for (size_t j = 0; j < m_patterns[i].size() - m_B + 1; ++j) {
            string bgram = m_patterns[i].substr(j, m_B);
            size_t pos = m_patterns[i].size() - j - m_B;

            shift_type::iterator old = m_shift.find(bgram);
            if (old == m_shift.end())
                m_shift[bgram] = pos;
            else
                old->second = min(old->second, pos);
        }
    }
}

bool WuManberFinder::find() {
    while (m_pos <= m_text.size()) {
        string bgram = m_text.substr(m_pos - m_B, m_B);
        shift_type::iterator i = m_shift.find(bgram);
        if (i == m_shift.end())
            m_pos += m_lmin - m_B + 1;
        else {
            if (i->second == 0) {
                vector<size_t>& list = m_hash[bgram];
                // Verify all patterns in list against the text.
                ++m_pos;
                for (size_t j = 0; j < list.size(); ++j) {
                    string const& str = m_patterns[list[j]];
                    m_find_pos = m_pos - str.size() - 1;
                    size_t k = 0;

                    for (; k < str.size(); ++k)
                        if (str[k] != m_text[m_find_pos + k])
                            break;

                    if (k == str.size()) {
                        m_find_pattern_index = list[j];
                        return true;
                    }
                }
            }
            else
                m_pos += i->second;
        }
    }

    return false;
}

size_t WuManberFinder::position() const {
    return m_find_pos;
}

size_t WuManberFinder::pattern_index() const {
    return m_find_pattern_index;
}

} } // namespace thru::matching

Пример использования:

vector<string> patterns;
patterns.push_back("announce");
patterns.push_back("annual");
patterns.push_back("annually");

WuManberFinder wmf("CPM_annual_conference_announce", patterns);

while (wmf.find())
    cout << "Pattern \"" << patterns[wmf.pattern_index()] <<
        "\" found at position " << wmf.position() << endl;
12 голосов
/ 30 сентября 2008

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

11 голосов
/ 30 сентября 2008

A Фильтр Блума может быть вашим лучшим выбором. Вы инициализируете свой фильтр поисковыми терминами, а затем, читая ваш корпус, можете быстро проверить, есть ли каждая работа в фильтре.

Это очень экономно, намного лучше, чем простое хэширование каждого слова: при частоте ложных срабатываний 1% для него требуется всего 9,6 бит на элемент. Частота ложноположительных результатов уменьшается в 10 раз за каждые дополнительные 4,8 бита на элемент. Сравните это с простым хэшированием, которое обычно требует sizeof (int) == 32 бита на элемент.

У меня есть реализация на C # здесь: http://www.codeplex.com/bloomfilter

Вот пример, демонстрирующий его использование со строками:

int capacity = 2000000; // the number of items you expect to add to the filter
Filter<string> filter = new Filter<string>(capacity);
filter.Add("Lorem");
filter.Add("Ipsum");
if (filter.Contains("Lorem"))
    Console.WriteLine("Match!");
5 голосов
/ 30 сентября 2008

если корпус такой большой, попробуйте оптимизировать его следующим образом:

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

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

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

3 голосов
/ 01 мая 2009

Используйте алгоритм Aho-Corasick . Это было сделано для этого приложения. Вам нужно будет прочитать каждую букву в тексте поиска только один раз. Я недавно внедрил и использовал его с отличными результатами.

2 голосов
/ 01 мая 2009

Если текст, который вы ищете, огромен, то, возможно, стоит выполнить некоторую предварительную обработку: собрать 25 000 слов в TRIE.

Отсканируйте к началу первого слова в тексте и начните проходить ИСПОЛЬЗОВАНИЕ, когда вы будете проходить по буквам слова. Если в вашем TRIE нет перехода, перейдите к началу следующего слова и вернитесь к корню TRIE. Если вы достигли конца слова и оказались в конце слова в TRIE, вы нашли совпадение. Повторите для каждого слова в тексте.

Если ваш текст просто большой (а не огромный), то достаточно просто найти каждое слово в хеш-таблице.

2 голосов
/ 30 сентября 2008

Как говорит Хавьер, простейшим решением, вероятно, является хеш-таблица.

В C ++ это может быть реализовано с использованием набора STL. Сначала добавьте 25 000 тестовых слов в набор, а затем просмотрите каждое слово в тексте, используя set.find (current_word), чтобы оценить, входит ли слово в число 25 000 тестовых слов.

set.find является логарифмически быстрым, поэтому 25 000 тестовых слов не должны быть слишком большими. Алгоритм явно линейный по количеству слов в тексте.

0 голосов
/ 11 ноября 2009

Алгоритм Aho-Corasick построен специально для этой цели: поиск сразу нескольких слов.

0 голосов
/ 01 октября 2008

Вы хотите Дерево троичного поиска . Хорошую реализацию можно найти здесь .

0 голосов
/ 01 октября 2008

Вы также можете сортировать текст и список слов в алфавитном порядке. Когда у вас есть два отсортированных массива, вы можете легко найти совпадения по линейному времени.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...