搜索文本中多个字符串之一的有效算法?

Posted

技术标签:

【中文标题】搜索文本中多个字符串之一的有效算法?【英文标题】:efficient algorithm for searching one of several strings in a text? 【发布时间】:2011-04-03 09:06:41 【问题描述】:

我需要搜索传入的不是很长的文本片段以查找给定字符串的出现。字符串在整个会话中都是不变的,并且并不多(~10)。额外的简化是没有任何字符串包含在任何其他字符串中。

我目前正在使用与 str1 | str2 | ... 匹配的 boost 正则表达式。这个任务的性能很重要,所以我想知道我是否可以改进它。并不是说我的编程比 boost 家伙更好,但也许专门的实现比一般的更有效。

由于字符串长时间保持不变,我可以预先构建一个数据结构,例如状态转换表。

例如,如果字符串是abcxbcycz,并且我目前已经阅读了abc,那么我应该处于组合状态,这意味着you're either 3 chars into string 1, 2 chars into string 2 or 1 char into string 1。然后阅读x next 会将我移动到string 1 matched 状态等,除xyz 之外的任何字符都将移动到初始状态,我不需要退回到b

感谢任何想法或参考。

【问题讨论】:

您使用的是预编译的正则表达式对象吗? 我不知道 boost:但大多数语言都使用正则表达式。使用正则表达式构建用于解析文本的有限状态机的等价物,因此非常高效。 请发布您正在使用的正则表达式。那里可能有改进的余地。 @John Dibling:如前所述,我只是用 OR 运算符连接字符串:str1 | str2 | ... 正如@maxschlepzig 建议的那样,您应该使用Aho-Corasick 算法,该算法对于这种情况是最佳的,并且在O( |str_1| + ... + |str_n| ) 预处理时间加上O( | text| ) 查找所有匹配项 【参考方案1】:

Regex 引擎初始化预计会有一些开销, 所以如果不涉及真正的正则表达式, C - memcmp() 应该没问题。

如果你能说出文件大小并给出一些 具体用例,我们可以建立一个基准 (我认为这很有趣)。

有趣:memcmp explorations 和 timing differences

问候

rbo

【讨论】:

memcmp 文章的最后:news.ycombinator.com/item?id=607954 这表明 Boyer Moore 击败了优化的 memcmp... 我可能不会建议 memcmp in C++,我们有一个std::string 类毕竟有一个find 方法,不需要处理这些讨厌的东西。 @Matthieu:好点子,我想在符合 O.P. 条件的场景中进行测试——如果他能详细描述一下的话。【参考方案2】:

看看Suffix Tree。

【讨论】:

【参考方案3】:

总是有Boyer Moore

【讨论】:

【参考方案4】:

除了 Rabin-Karp-Algorithm 和 Knuth-Morris-Pratt-Algorithm,我的算法书建议使用 Finite State Machine 进行字符串匹配。对于每个搜索字符串,您都需要构建这样一个有限状态机。

【讨论】:

您这么说是因为您已经阅读了 BOOST 的源代码,还是有其他原因您知道 BOOST 不会按照其用户文档建议的方式进行深度优先非确定性匹配?【参考方案5】:

查看Aho–Corasick string matching algorithm!

【讨论】:

【参考方案6】:

我一直在查看答案,但似乎没有一个非常明确……而且主要归结为几个链接。

这里让我感兴趣的是您的问题的独特性,到目前为止所公开的解决方案根本没有利用我们正在大海捞针中同时寻找几个针这一事实。

我肯定会看看 KMP / Boyer Moore,但我不会盲目地应用它们(至少如果你有时间的话),因为它们是为单针量身定制的,而且我很漂亮确信我们可以利用我们有多个字符串并使用自定义状态机(或 BM 的自定义表)一次检查所有字符串这一事实。

当然,它不太可能改善大 O(Boyer Moore 对每个字符串运行 3n,因此无论如何它都是线性的),但您可能会在常数因子上有所收获。

【讨论】:

【参考方案7】:

看看这个:http://www.boost.org/doc/libs/1_44_0/libs/regex/doc/html/boost_regex/configuration/algorithm.html

递归/非递归区别的存在非常强烈地表明 BOOST 不一定是线性时间离散有限状态机。因此,您很有可能可以针对您的特定问题做得更好。

最佳答案很大程度上取决于你有多少干草堆和一根针的最小尺寸。如果最小的针比几个字符长,你可能会比通用的正则表达式库做得更好。

基本上所有的字符串搜索都是通过在当前位置(光标)测试匹配来工作的,如果没有找到,则将光标向右滑动再试一次。

Rabin-Karp 从您正在搜索的字符串(或多个字符串)中构建一个 DFSM,以便将测试和光标运动组合在一个操作中。但是,Rabin-Karp 最初是为单针设计的,因此如果一个匹配项可能是另一个匹配项的正确前缀,则需要支持回溯。 (记住,当你想重用你的代码时。)

另一种策略是尽可能将光标向右滑动多个字符。 Boyer-Moore 就是这样做的。它通常是为单针制造的。构建一个包含所有字符的表格以及它们出现在针中的最右边的位置(如果有的话)。现在,将光标定位在 len(needle)-1。表格条目将告诉您 (a) 可能找到针的光标向左偏移量,或 (b) 您可以将光标 len(needle) 向右移动更远。

当您有不止一根针时,您的桌子的构造和使用会变得更加复杂,但它仍然可能会为您节省一个数量级的探针。你可能仍然想要创建一个 DFSM,但不是调用通用搜索方法,而是调用 dos_this_DFSM_match_at_this_offset()。

另一种策略是一次测试超过 8 位。有一个垃圾邮件杀手工具可以一次查看 32 位机器字。然后它会执行一些简单的哈希码以将结果放入 12 位,然后查看表格以查看是否有命中。每个模式都有四个条目(从模式开始的偏移量为 0、1、2 和 3),然后尽管表中有数千个模式,但它们只测试主题中每个 32 位字的一个或两个行。

所以一般来说,是的,当针数不变时,你可以比正则表达式更快。

【讨论】:

【参考方案8】:

在 Flex 和 Bison 工具的支持下,您可以使用非常流行的 Lex 和 Yacc 工具来做到这一点。 您可以使用 Lex 获取字符串的标记。 将您的预定义字符串与从 Lexer 返回的标记进行比较。 找到匹配项后,执行所需的操作。 有许多网站描述了 Lex 和 Yacc。 一个这样的网站是http://epaperpress.com/lexandyacc/

【讨论】:

以上是关于搜索文本中多个字符串之一的有效算法?的主要内容,如果未能解决你的问题,请参考以下文章

shell命令之一天一见:grep

使用 PHP 在一个字符串中搜索多个标记的最有效方法是啥?

for循环中的多个选择字符串以分隔文件

如何在多个文件中搜索字符串并在 Excel 或 Powershell 中的 csv 中返回带有行号/文本的文件名

寻找最小pangrammatic窗口的有效算法?

在给定字符串中搜索字符集的最快算法