针对多个正则表达式有效地查询一个字符串
Posted
技术标签:
【中文标题】针对多个正则表达式有效地查询一个字符串【英文标题】:Efficiently querying one string against multiple regexes 【发布时间】:2010-09-16 14:48:09 【问题描述】:假设我有 10,000 个正则表达式和一个字符串,我想找出字符串是否与其中任何一个匹配并获取所有匹配项。 最简单的方法是针对所有正则表达式逐一查询字符串。有没有更快、更有效的方法呢?
编辑: 我试过用 DFA (lex) 代替它 这里的问题是它只会给你一个单一的模式。如果我有一个字符串“hello”和模式“[H|h]ello”和“.0,20ello”,DFA 只会匹配其中一个,但我希望它们都命中。
【问题讨论】:
通常人们会尽量避免歧义,因此有一些标准可以在可能匹配的模式中进行选择。通常选择最长的最左边的匹配。如果你有这个需求,你可能不得不自己写一些东西 在回答相关问题时遇到了这个问题。由于这个问题是在 08 年提出的,因此我发布了一个开源 Java 库,您可以使用它来构建一个 DFA,如果您愿意,它会为您提供所有匹配的表达式:mtimmerm.github.io/dfalex 【参考方案1】:我们必须在我曾经开发过的产品上这样做。答案是将所有正则表达式一起编译成Deterministic Finite State Machine(也称为确定性有限自动机或DFA)。然后,DFA 可以逐个字符地遍历您的字符串,并在其中一个表达式匹配时触发“匹配”事件。
优点是运行速度快(每个字符只比较一次),如果添加更多表达式也不会变慢。
缺点是自动机需要一个庞大的数据表,而且有很多类型的正则表达式不被支持(例如,反向引用) .
我们使用的那个是当时我们公司的一个 C++ 模板 nut 手工编码的,所以很遗憾,我没有任何 FOSS 解决方案可以为您指明方向。但是,如果您使用“DFA”搜索正则表达式或正则表达式,您会发现可以为您指明正确方向的内容。
【讨论】:
FSA 绝对是必经之路! 这已经由我们实现了,但不幸的是,您提到的缺点是我们正在寻找不同解决方案的原因。【参考方案2】:这就是词法分析器的工作方式。
正则表达式被转换为单个非确定性自动机 (NFA),并可能转换为确定性自动机 (DFA)。
生成的自动机将尝试一次匹配所有正则表达式,并在其中一个上成功。
这里有许多工具可以为您提供帮助,它们被称为“词法分析器生成器”,并且有适用于大多数语言的解决方案。
你没有说你使用的是哪种语言。对于 C 程序员,我建议看看re2c 工具。当然,传统的(f)lex 始终是一种选择。
【讨论】:
自动生成一个 lex 文件就可以了——如果你走这条路,我希望你是你的 make 系统的大师 ;-) 伟大的观察!这得到了我的投票。我确信有一种更简单的方法可以重用词法分析器代码,而无需生成源文件。【参考方案3】:我过去遇到过类似的问题。我使用了类似于the one suggested by akdom的解决方案。
我很幸运,因为我的正则表达式通常有一些子字符串必须出现在它匹配的每个字符串中。我能够使用简单的解析器提取这些子字符串,并使用 Aho-Corasick 算法在 FSA 中对它们进行索引。然后使用该索引快速消除所有与给定字符串不匹配的正则表达式,只留下几个正则表达式进行检查。
我在 LGPL 下将代码作为 Python/C 模块发布。见esmre on Google code hosting。
【讨论】:
您似乎已将代码移至 GitHub:github.com/wharris/esmre。只是想我会让每个人都知道;)【参考方案4】:Martin Sulzmann在这个领域做了不少工作。 他有 a HackageDB project 简要解释了 here 其中使用 partial derivatives 似乎是为此量身定做的。
使用的语言是 Haskell,因此如果需要的话,将很难翻译成非功能性语言(我认为翻译成许多其他 FP 语言仍然相当困难)。
代码不是基于转换成一系列自动机然后组合它们,而是基于对正则表达式本身的符号操作。
此外,代码非常具有实验性,Martin 不再是教授,而是从事“有报酬的工作”(1),因此可能不感兴趣/无法提供任何帮助或意见。
-
开个玩笑 - 我喜欢教授,聪明的人越少努力工作,我获得报酬的机会就越大!
【讨论】:
我接受这个答案,因为我已经经历了所有其他路线并且失败了(是的,我确实实施了所有其他解决方案,并发现它们在许多领域都存在不足)。我已经承担了将库从 Haskell 移植到 C++ 的项目。以后可能会开源它。这在以后可能不会真正奏效,但这似乎很有希望并且在理论上是合理的。 祝你在港口好运。我可以看到它很受欢迎!让我们知道它是否有效 @shridhar lyer:对进展有什么想法吗?我也遇到了类似的情况【参考方案5】:10,000 正则表达式是吗? Eric Wendelin's 层次结构的建议似乎是个好主意。您是否考虑过将这些正则表达式的庞大性简化为类似于树结构的东西?
作为一个简单的例子:所有需要一个数字的正则表达式都可以从一个正则表达式中分支出来,所有的正则表达式都不需要另一个分支。通过这种方式,您可以将实际比较的次数减少到沿树的路径,而不是每次比较都以 10,000 次为单位。
这需要将提供的正则表达式分解为流派,每个流派都有一个共享测试,如果测试失败,将排除它们。通过这种方式,理论上您可以显着减少实际比较的次数。
如果您必须在运行时执行此操作,您可以解析给定的正则表达式并将它们“归档”为预定义的类型(最容易做到)或当时生成的比较类型(不太容易做到)。
这个解决方案不会真正帮助您将“hello”与“[H|h]ello”和“.0,20ello”进行比较的示例。一个可能有用的简单情况是:如果您有 1000 个测试,只有在字符串中的某处存在“ello”并且您的测试字符串是“goodbye”时才会返回 true;您只需要对“ello”进行一次测试,就知道需要它的 1000 次测试将不起作用,因此,您不必进行这些测试。
【讨论】:
任何可以自动执行此操作的库?这不能手动处理。正则表达式不是硬编码的。 我不知道有什么库可以做到这一点,但是您可以编写一些东西来解析正则表达式并“归档”您在可测试用例下拥有的那些。即使您只能非常广泛地执行此操作,您也可以通过排除大量不适合的内容来显着缩短执行时间。 esmre [code.google.com/p/esmre/] 是一个 Python/C 库,可以自动执行类似的操作。 +1:非常喜欢层次结构的想法。当您处理这些相对较大的数字时,您可能可以通过某种方式对它们进行分类。 通过构建“树”结构;您基本上是实现 DFA/NFA 的一步。你最好使用真正的(非常快的)DFA/NFA 匹配器,而不是拥有两个世界的复杂性。【参考方案6】:您需要有某种方法来确定给定的正则表达式与另一个正则表达式相比是否具有“附加性”。创建各种正则表达式“层次结构”,让您确定某个分支的所有正则表达式都不匹配
【讨论】:
【参考方案7】:如果您正在考虑“10,000 个正则表达式”,您需要改变您的流程。如果不出意外,请考虑“要匹配的 10,000 个目标字符串”。然后寻找为处理“大量目标字符串”情况而构建的非正则表达式方法,例如 Aho-Corasick 机器。不过,坦率地说,在使用哪台机器之前,似乎有些东西在这个过程中更早地偏离了轨道,因为 10,000 个目标字符串听起来更像是数据库查找,而不是字符串匹配。
【讨论】:
【参考方案8】:您可以将它们组合成 20 个一组。
(?=(regex1)?)(?=(regex2)?)(?=(regex3)?)...(?=(regex20)?)
只要每个正则表达式都有零个(或至少相同数量的)捕获组,您就可以查看捕获的内容以了解哪些模式匹配。
如果 regex1 匹配,则捕获组 1 将具有匹配的文本。如果不是,那就是undefined
/None
/null
/...
【讨论】:
【参考方案9】:Aho-Corasick 是我的答案。
我有 2000 个类别的事物,每个类别都有要匹配的模式列表。字符串长度平均约为 100,000 个字符。
主要警告:要匹配的模式都是语言模式,而不是正则表达式模式,例如'cat'
与 r'\w+'
.
我使用的是 python,所以使用了https://pypi.python.org/pypi/pyahocorasick/。
import ahocorasick
A = ahocorasick.Automaton()
patterns = [
[['cat','dog'],'mammals'],
[['bass','tuna','trout'],'fish'],
[['toad','crocodile'],'amphibians'],
]
for row in patterns:
vals = row[0]
for val in vals:
A.add_word(val, (row[1], val))
A.make_automaton()
_string = 'tom loves lions tigers cats and bass'
def test():
vals = []
for item in A.iter(_string):
vals.append(item)
return vals
在我的 2000 个类别上运行 %timeit test()
,每个类别大约有 2-3 条轨迹,_string
的长度约为 100,000
让我 2.09 ms
与 631 ms
连续运行 re.search()
快 315 倍! 。
【讨论】:
非常真实! Aho-Corasick 确实是经过实践检验的解决方案。而@will-harris esmre 也是基于这个强大的精细数据结构【参考方案10】:如果您使用的是真正的正则表达式(与形式语言理论中的正则语言相对应的表达式,而不是一些类似 Perl 的非正则表达式),那么您很幸运,因为正则语言在联合下是封闭的.在大多数正则表达式语言中,管道 (|) 是联合。所以你应该可以构造一个字符串(代表你想要的正则表达式)如下:
(r1)|(r2)|(r3)|...|(r10000)
括号用于分组,不匹配。匹配此正则表达式的任何内容都至少匹配您的原始正则表达式之一。
【讨论】:
我希望在现实世界的语言中真的有这样的正则表达式实现。不幸的是,perl 搞砸了,其他人都复制了它们...... - 所以这在几乎任何正则表达式引擎中都不起作用。 如果你这样做了,有没有一种有效的方法来提取匹配的正则表达式?想象一下,您必须遍历所有 10000 个组,寻找一个非零的组...【参考方案11】:我会说这是一个真正的解析器的工作。中点可能是Parsing Expression Grammar (PEG)。它是模式匹配的高级抽象,一个特点是您可以定义整个语法而不是单个模式。有一些高性能实现通过将语法编译成字节码并在专用 VM 中运行来工作。
免责声明:我只知道LPEG,它是Lua 的库,(对我来说)掌握基本概念并不容易。
【讨论】:
PEG 比所需的要强大得多(也慢得多)。一组正则语言的并集是正则的;即一个普通的旧 NFA 就足够了。【参考方案12】:如果您只需要知道哪些正则表达式匹配,我建议您使用 Intel 的 Hyperscan。它是为此目的而建造的。如果您需要采取的行动更复杂,您还可以使用 ragel。虽然它产生一个单一的 DFA 并可能导致许多状态,因此是一个非常大的可执行程序。 Hyperscan 采用混合 NFA/DFA/自定义方法进行匹配,可以很好地处理大量表达式。
【讨论】:
【参考方案13】:我几乎建议编写一个“由内而外”的正则表达式引擎——其中“目标”是正则表达式,“术语”是字符串。
但是,您迭代尝试每一个的解决方案似乎要容易得多。
【讨论】:
【参考方案14】:您可以将正则表达式编译为混合 DFA/Bucchi automata,其中每次 BA 进入接受状态时,您都会标记哪个正则表达式规则“命中”。
Bucchi 在这方面有点矫枉过正,但修改 DFA 的工作方式可以解决问题。
【讨论】:
【参考方案15】:尝试将它们组合成一个大的正则表达式?
【讨论】:
这当然值得一试——希望正则表达式编译器能稍微优化一下…… 这通常会慢很多 这实际上是一个非常合理且快速的解决方案 - 如果您使用的是基于 NFA 模拟的直接正则表达式。不幸的是,大多数正则表达式实现都没有——所以这在实践中是行不通的。【参考方案16】:我认为简短的回答是,是的,有一种方法可以做到这一点,并且它是计算机科学所熟知的,但我不记得它是什么。
简短的回答是,您可能会发现您的正则表达式解释器在 |'d 一起时已经有效地处理了所有这些,或者您可能会找到一个可以做到的。如果没有,是时候谷歌搜索字符串匹配和搜索算法了。
【讨论】:
【参考方案17】:我将Ragel 用于离开动作:
action hello ...
action ello ...
action ello2 ...
main := /[Hh]ello/ % hello |
/.+ello/ % ello |
any0,20 "ello" % ello2 ;
字符串“hello”将调用action hello
块中的代码,然后是action ello
块中的代码,最后是action ello2
块中的代码。
它们的正则表达式非常有限,而是首选机器语言,您示例中的大括号仅适用于更通用的语言。
【讨论】:
【参考方案18】:最快的方法似乎是这样的(代码是 C#):
public static List<Regex> FindAllMatches(string s, List<Regex> regexes)
List<Regex> matches = new List<Regex>();
foreach (Regex r in regexes)
if (r.IsMatch(string))
matches.Add(r);
return matches;
哦,你是说最快的代码?那我就不知道了……
【讨论】:
过早的优化等等……看看你最简单的解决方案有多慢,特别是因为实现它的代码很简单。 那么你会建议什么来提高效率? 如果您要测试 10,000 个正则表达式,它会非常慢。您需要一些方法来组合树以获得单遍解析器。以上是关于针对多个正则表达式有效地查询一个字符串的主要内容,如果未能解决你的问题,请参考以下文章