通配符字符串匹配

时间:2010-01-19 12:33:30

标签: algorithm

什么是最有效的通配符字符串匹配算法?我只询问一个想法,没有必要提供实际的代码。

我认为这样的算法可以使用已排序的后缀数组构建,这可以产生O(log(n))的性能。

我说错了吗?

编辑:

我指的是"A*B""*sip*""A?B"等模式,其中星号表示任意数量的符号,问号表示单个符号。

9 个答案:

答案 0 :(得分:4)

这里有一篇论文介绍了最快的选项 http://swtch.com/~rsc/regexp/regexp1.html 特别是它允许你避免在使用长模式时变得病态慢的天真算法。

它涵盖了通用正则表达式,但您可以将实现限制为所需的子集。

答案 1 :(得分:2)

嗯,我认为正常的模式匹配规则适用于此处。通常,由于您拥有数据流和短模式,因此您不需要实现比线性更有效的方法。但是,模式越长,优化的空间就越大。

你有什么样的通配符?单字符通配符(例如正则表达式中的.)或多字符通配符(例如.*)?有限制吗?什么是预期的模式长度,您是否对要检查的数据进行随机或串行访问?

答案 2 :(得分:2)

我一直在寻找一种在多项式时间内运行的简单通配符匹配算法。例如。这个很简单,但是当模式包含许多星(*)时,它不会在多项式时间内运行: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;
    }
};

答案 3 :(得分:1)

如果您的模式只能包含*通配符,则可以尽可能快地执行。在这种情况下要实现的主要事情是,您只需要查找每张卡片一次(卡片=星星之间的片段)。

这是一个实现(仅支持*通配符):

#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;
}

答案 4 :(得分:0)

性能不仅取决于要搜索的字符串的长度,还取决于查询字符串中通配符的数量(和类型)。如果您被允许使用匹配任意数量字符的*,包括整个文档,并且您可以拥有任意数量的星标,这将对可能获得的内容设置一些限制。

如果你可以在O(f(n))时间内确定匹配某个字符串foo,那么查询foo_0*foo_1*foo_2*...*foo_m将需要O(m * f(n))时间,其中m是*通配符的数量。

答案 5 :(得分:0)

根据通配符“语言”,我(可能)只需编写一个通配符&gt; regexp编译器并使用(通常提供的)regexp引擎进行实际匹配。这有点懒,但除非有这样做的实际性能问题,否则它足够快。

答案 6 :(得分:0)

您可以将通配符查询转换为正则表达式并使用它来匹配; RE总是可以转换为DFA(离散有限自动机),那些是有效的(线性时间)和一个小常数。

答案 7 :(得分:0)

O(n log m)是正确的。见http://www.cs.bris.ac.uk/Publications/Papers/2000602.pdf

希望这会有所帮助......

答案 8 :(得分:0)

这是C语言中的一个简单实现,它利用指针并尝试对字符串进行单次遍历,并在可能的情况下通配符模式在一般简单情况下获得最大的效率。请注意,它还避免了用于字符串的任何函数,例如“ length()”(可能因语言而异),这些函数可能会遍历字符串并增加不必要的计算时间。

#include <stdio.h>

_Bool wildcard_strcmp(char *line, char *pattern)
{
    _Bool wildcard = 0;
    char *placeholder;

    do
    {
        if ((*pattern == *line) || (*pattern == '?'))
        {
            line++;
            pattern++;
        }
        else if (*pattern == '*')
        {
            if (*(++pattern) == '\0')
            {
                return 1;
            }
            wildcard = 1;
        }
        else if (wildcard)
        {
            if (pattern == placeholder)
            {
                line++;
            }
            else
            {
                pattern = placeholder;
            }
        } 
        else
        {
            return 0;
        }
    } while (*line);

    if (*pattern == '\0')
    {
        return 1;
    }
    else
    {
        return 0;
    }
}

int main()
{
    char string[200] = "foobarfoobar";
    char pattern[200] = "fo?*barfoo*";

    if (wildcard_strcmp(string, pattern))
    {
        printf("Match\n");
    }
    else
    {
        printf("No Match\n");
    }

    return 0;
}