高效的字符串匹配算法

时间:2009-03-05 00:28:10

标签: c# algorithm

我正在尝试构建一个有效的字符串匹配算法。这将在高容量环境中执行,因此性能至关重要。

以下是我的要求:

  • 鉴于域名,即www.example.com,确定它是否与条目列表中的“匹配”。
  • 参赛作品可以是绝对匹配,即www.example.com。
  • 参赛作品可能包含通配符,即* .example.com。
  • 通配符条目从最定义的级别开始匹配。例如,* .example.com会匹配www.example.com,example.com和sub.www.example.com。
  • 未嵌入通配符条目,即sub。*。example.com将不是条目。

语言/环境:C#(。Net Framework 3.5)

我考虑将条目(和域查找)拆分为数组,颠倒顺序,然后遍历数组。虽然准确,但感觉很慢。

我考虑过Regex,但我担心将条目列表准确地表示为正则表达式。

我的问题:根据上面列出的描述,找到一个字符串形式的字符串是否匹配字符串列表中的任何一个字符串的有效方法是什么?

14 个答案:

答案 0 :(得分:12)

如果您想要自己滚动,我会将条目存储在树形结构中。有关拼写检查的信息,请参阅my answer to another SO question了解我的意思。

而不是用“。”来标记结构。字符,我只是将每个条目视为一个完整的字符串。任何标记化的实现仍然必须在整个字符集上进行字符串匹配,所以你也可以一次性完成所有这些。

这与常规拼写检查树之间的唯一区别是:

  1. 匹配需要反向完成
  2. 您必须考虑通配符

要解决第2点问题,您只需在测试结束时检查“*”字符。

一个简单的例子:

条目:

*.fark.com
www.cnn.com

树:

m -> o -> c -> . -> k -> r -> a -> f -> . -> *
                \
                 -> n -> n -> c -> . -> w -> w -> w

检查www.blog.fark.com将涉及到树的追踪到第一个"*"。因为遍历在"*"上结束,所以匹配。

检查www.cern.com会在n,n,c,......的第二个“n”上失败。

检查dev.www.cnn.com也会失败,因为遍历以"*"以外的字符结束。

答案 1 :(得分:5)

我会使用正则表达式,只需确保将表达式编译一次(而不是一次又一次地计算)。

答案 2 :(得分:5)

你不需要regexp ..只需反转所有字符串, 摆脱'*',并加上一个标志来表示部分匹配 直到这一点过去。

不知何故,trie或后缀trie看起来最合适。

如果在编译时知道域列表,您可以查看 在'。'标记并使用多个gperf生成的机器。

链接:   google for trie   http://marknelson.us/1996/08/01/suffix-trees/

答案 3 :(得分:3)

我会使用树结构来存储规则,其中每个树节点都包含一个字典。

构造树使得“com”,“net”等是顶级条目,“example”在下一级别,依此类推。你需要一个特殊的标志来注意该节点是一个通配符。

要执行查找,请按周期拆分字符串,然后向后迭代,根据输入导航树。

这看起来与您所考虑的相似,但假设规则不会更改每次运行,使用基于字典的缓存树将比数组列表更快。

此外,我不得不打赌这种方法会比RegEx更快。

答案 4 :(得分:2)

您似乎对您认为有效输入的内容有一套定义明确的规则 - 您可以考虑使用手写的LL parser。这样的解析器相对容易编写和优化。通常你会让解析器输出一个描述输入的树结构 - 我会使用这个树作为匹配例程的输入,该例程使用上面描述的规则执行将树与条目列表匹配的工作。

这是一个article on recursive descent parsers

答案 5 :(得分:1)

假设规则如你所说:文字或以*。

开头

爪哇:

public static boolean matches(String candidate, List<String> rules) {
    for(String rule : rules) {
        if (rule.startsWith("*")) {
            rule = rule.substring(2);
        }
        if (candidate.endsWith(rule)) {
            return true;
        }
    }
    return false;
}

这会扩展到您拥有的规则数量。

编辑:

这里要清楚。

当我说“排序规则”时,我的意思是用规则字符创建一个树。

然后你使用匹配字符串来尝试并遍历树(即如果我有一个xyz字符串,我从x字符开始,看看它是否有一个y分支,然后是一个z子)。

对于“通配符”,我会使用相同的概念,但是“向后”填充它,然后用匹配候选者的背面进行操作。

如果您有很多规则,我会对规则进行排序。

对于非通配符匹配,您迭代每个字符以缩小可能的规则(即,如果它以“w”开头,那么您使用“w”规则等。)

如果它是通配符匹配,则执行完全相同的操作,但是您可以使用“向后规则”列表,并且只需将字符串末尾与规则末尾匹配即可。

答案 6 :(得分:1)

我会尝试trieslongest-prefix matching的组合(用于路由到IP网络)。如果空间是一个问题,Directed Acyclic Word Graphs可能比尝试更合适。

答案 7 :(得分:1)

我将建议树形结构方法的替代方案。使用Burrows-Wheeler转换创建域列表的压缩索引。有关该技术的完整说明,请参阅http://www.ddj.com/architect/184405504?pgno=1

答案 8 :(得分:0)

查看RegExLib

答案 9 :(得分:0)

不确定您的分裂和迭代的想法,但似乎不会很慢:

像你说的那样,将域名分开并反转。存储基本上可以是一棵树。使用哈希表来存储TLD。关键是,例如,“com”,值将是该TLD下的子域的哈希表,迭代广告。

答案 10 :(得分:0)

根据您的要求,我认为您正在考虑从字符串末尾(TLD)到主机名工作。你可能使用正则表达式,但由于你并没有真正使用正则表达式的任何强大功能,我不明白你为什么要付出代价。如果你颠倒了字符串,那么你真的只是在寻找前缀匹配('* .example.com'变成:'是'moc.elpmaxe'我的输入字符串的开头?),这当然不会更明显不需要像regexp那样笨拙的东西。

用于存储条目列表的结构很大程度上取决于列表的大小以及更改的频率...对于一个巨大的稳定列表,树/特里可能是最高性能的;经常变化的列表需要一个易于初始化/更新的结构,等等。没有更多信息,我不愿意建议任何一种结构。

答案 11 :(得分:0)

我想我很想用另一个回答你的问题:你在做什么,你认为你的瓶颈是一些字符串匹配以及简单的字符串比较?肯定会在你的表现分析中列出更高的其他东西吗?

我会首先使用明显的字符串比较测试,这些测试在90%的时间都是正确的,如果它们失败则会回退到正则表达式

答案 12 :(得分:0)

如果它只是匹配字符串,那么你应该看看trie数据结构和算法。较早的答案表明,如果您的所有通配符在开头都是单个通配符,则可以使用一些特定的算法。但是,处理通用通配符的要求意味着,为了快速执行,您将需要生成状态机。

这就是正则表达式库为你做的:“预编译”正则表达式==生成状态机;这允许运行时的实际匹配很快。如果没有非常优化的努力,你不可能获得明显更好的性能。

如果你想自己动手,我可以说,专门为多个通配符编写自己的状态机生成器应该具有教育意义。在这种情况下,您需要了解他们在正则表达式库中使用的算法种类......

答案 13 :(得分:0)

调查KMP(Knuth-Morris-Pratt)或BM(Boyer-Moore)算法。这些允许您比线性时间更快地搜索字符串,但需要进行一些预处理。正如其他人所指出的那样,丢弃领先的星号当然是至关重要的。

这些信息的一个来源是:

KMP:http://www-igm.univ-mlv.fr/~lecroq/string/node8.html

BM:http://www-igm.univ-mlv.fr/~lecroq/string/node14.html