Подстановочный знак соответствия - PullRequest
11 голосов
/ 19 января 2010

Какой самый эффективный алгоритм сопоставления строк с подстановочными знаками? Я спрашиваю только об идее, необязательно указывать фактический код.

Я думаю, что такой алгоритм может быть построен с сортированными массивами суффиксов, которые могут дать производительность O (log (n)).

Я прав?

Отредактировано:

Я имею в виду такие узоры, как "A*B", "*sip*" или "A?B", где звездочка означает любое количество символов, а вопросительный знак означает один символ.

Ответы [ 8 ]

4 голосов
/ 20 января 2010

Здесь есть бумага, покрывающая самые быстрые варианты http://swtch.com/~rsc/regexp/regexp1.html в частности, он позволяет избежать наивных алгоритмов, которые становятся патологически медленными при использовании длинных паттернов.

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

2 голосов
/ 26 января 2014

Я искал простой алгоритм сопоставления с подстановочными знаками, который работает за полиномиальное время. Например. этот простой, но не выполняется за полиномиальное время, когда шаблон содержит много звездочек (*): http://www.codeproject.com/Articles/188256/A-Simple-Wildcard-Matching-Function Ниже приведен код, который использует динамическое программирование для уменьшения сложности времени до O (n * m), где n - длина текста, а m - длина шаблона.

#include <string>
#include <vector>
#include <algorithm>
using namespace std;

const int UNKNOWN = -1;
const int NOMATCH = 0;
const int MATCHES = 1;

class Wildcard {
    string _text;
    string _pattern;
    vector<vector<int>> _mf;
    int F(int n, int m) {
        if (_mf[n][m] >= 0) return _mf[n][m];
        if (n == 0 && m == 0) {
            _mf[n][m] = MATCHES;
            return _mf[n][m];
        }
        if (n > 0 && m == 0) {
            _mf[n][m] = NOMATCH;
            return _mf[n][m];
        }
        // m > 0
        int ans = NOMATCH;
        if (_pattern[m - 1] == '*') {
            ans = max(ans, F(n, m-1));
            if (n > 0) {
                ans = max(ans, F(n - 1, m));
            }
        }
        if (n > 0) {
            if (_pattern[m - 1] == '?' || _pattern[m - 1] == _text[n - 1]) {
                ans = max(ans, F(n - 1, m - 1));
            }
        }
        _mf[n][m] = ans;
        return _mf[n][m];
    }
public:
    bool match(string text, string pattern) {
        _text = text;
        _pattern = pattern;
        _mf.clear();
        for (int i = 0; i <= _text.size(); i++) {
            _mf.push_back(vector<int>());
            for (int j = 0; j <= _pattern.size(); j++) {
                _mf[i].push_back(UNKNOWN); // not calculated
            }
        }
        int ans = F(_text.size(), _pattern.size());
        return ans == MATCHES;
    }
};
2 голосов
/ 19 января 2010

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

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

1 голос
/ 19 марта 2014

Если ваш шаблон может содержать только * подстановочные знаки, тогда тривиальная реализация выполняется максимально быстро. Главное, что нужно понять в этом случае, это то, что вам нужно искать каждую карту только один раз (карта = отрезок между звездами).

Вот реализация (поддерживающая только * групповые символы):

#include <cstddef>
#include <cstring>
#include <algorithm>
#include <string>
#include <vector>
#include <iostream>

using namespace std;

class wildcard_pattern {
public:
    explicit wildcard_pattern(const string& text);

    bool match(const char* begin, const char* end) const;

    bool match(const char* c_str) const;

private:
    string m_text;

    struct card {
        size_t m_offset, m_size;
        card(size_t begin, size_t end);
    };

    // Must contain at least one card. The first, and the last card
    // may be empty strings. All other cards must be non-empty. If
    // there is exactly one card, the pattern matches a string if, and
    // only if the string is equal to the card. Otherwise, the first
    // card must be a prefix of the string, and the last card must be
    // a suffix.
    vector<card> m_cards;
};


wildcard_pattern::wildcard_pattern(const string& text):
    m_text(text)
{
    size_t pos = m_text.find('*');
    if (pos == string::npos) {
        m_cards.push_back(card(0, m_text.size()));
        return;
    }
    m_cards.push_back(card(0, pos));
    ++pos;
    for (;;) {
        size_t pos_2 = m_text.find('*', pos);
        if (pos_2 == string::npos)
            break;
        if (pos_2 != pos)
            m_cards.push_back(card(pos, pos_2));
        pos = pos_2 + 1;
    }
    m_cards.push_back(card(pos, m_text.size()));
}

bool wildcard_pattern::match(const char* begin, const char* end) const
{
    const char* begin_2 = begin;
    const char* end_2   = end;

    size_t num_cards = m_cards.size();
    typedef string::const_iterator str_iter;

    // Check anchored prefix card
    {
        const card& card = m_cards.front();
        if (size_t(end_2 - begin_2) < card.m_size)
            return false;
        str_iter card_begin = m_text.begin() + card.m_offset;
        if (!equal(begin_2, begin_2 + card.m_size, card_begin))
            return false;
        begin_2 += card.m_size;
    }

    if (num_cards == 1)
        return begin_2 == end_2;

    // Check anchored suffix card
    {
        const card& card = m_cards.back();
        if (size_t(end_2 - begin_2) < card.m_size)
            return false;
        str_iter card_begin = m_text.begin() + card.m_offset;
        if (!equal(end_2 - card.m_size, end_2, card_begin))
            return false;
        end_2 -= card.m_size;
    }

    // Check unanchored infix cards
    for (size_t i = 1; i != num_cards-1; ++i) {
        const card& card = m_cards[i];
        str_iter card_begin = m_text.begin() + card.m_offset;
        str_iter card_end   = card_begin + card.m_size;
        begin_2 = search(begin_2, end_2, card_begin, card_end);
        if (begin_2 == end_2)
            return false;
        begin_2 += card.m_size;
    }

    return true;
}

inline bool wildcard_pattern::match(const char* c_str) const
{
    const char* begin = c_str;
    const char* end   = begin + strlen(c_str);
    return match(begin, end);
}

inline wildcard_pattern::card::card(size_t begin, size_t end)
{
    m_offset = begin;
    m_size   = end - begin;
}


int main(int, const char* argv[])
{
    wildcard_pattern pat(argv[1]);
    cout << pat.match(argv[2]) << endl;
}
0 голосов
/ 25 января 2013

O (n log m) правильно.См. http://www.cs.bris.ac.uk/Publications/Papers/2000602.pdf

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

0 голосов
/ 19 января 2010

Вы можете преобразовать свой шаблонный запрос в регулярное выражение и использовать его для сопоставления; RE всегда можно преобразовать в DFA (дискретный конечный автомат), и они эффективны (линейное время) и имеют небольшую постоянную.

0 голосов
/ 19 января 2010

В зависимости от подстановочного «языка», я (вероятно) просто напишу компилятор подстановочных знаков-> regexp и использую (обычно предоставляемый) механизм регулярных выражений для выполнения фактического сопоставления. Это немного лениво, но если нет реальной проблемы с производительностью, делающей это таким образом, это будет достаточно быстро.

0 голосов
/ 19 января 2010

Производительность будет зависеть не только от длины строки для поиска, но также от количества (и типа) символов подстановки в строке запроса. Если вам разрешено использовать *, которое соответствует любому количеству символов, вплоть до всего документа, включая, и вы можете иметь любое количество звездочек, это наложит некоторые ограничения на то, что можно получить.

Если вы можете определить совпадение некоторой строки foo за время O (f (n)), то запрос foo_0*foo_1*foo_2*...*foo_m займет время O (m * f (n)), где m - это число * Подстановочные знаки.

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