有效地查询一个字符串与多个正则表达式

时间:2008-10-10 20:42:17

标签: regex algorithm pcre

假设我有10,000个正则表达式和一个字符串,我想知道字符串是否与其中任何一个匹配并获得所有匹配。 执行此操作的简单方法是仅针对所有正则表达式逐个查询字符串。有更快,更有效的方法吗?

编辑: 我试过用DFA替换它(lex) 这里的问题是它只会给你一个单一的模式。如果我有一个字符串“hello”和模式“[H | h] ello”和“。{0,20} ello”,DFA将只匹配其中一个,但我希望它们都能击中。

18 个答案:

答案 0 :(得分:12)

我过去遇到过类似的问题。我使用了类似于the one suggested by akdom的解决方案。

我很幸运,因为我的正则表达式通常有一些必须出现在它匹配的每个字符串中的子字符串。我能够使用简单的解析器提取这些子串,并使用Aho-Corasick算法在FSA中对它们进行索引。然后使用该索引快速消除所有与给定字符串不匹配的正则表达式,只留下几个正则表达式进行检查。

我在LGPL下发布了Python / C模块的代码。请参阅esmre on Google code hosting

答案 1 :(得分:11)

我们必须在我曾经工作过的产品上这样做。答案是将所有正则表达式编译成Deterministic Finite State Machine(也称为确定性有限自动机或 DFA )。然后可以在字符串上逐个字符地遍历DFA,并且只要其中一个表达式匹配,就会触发“匹配”事件。

优点是否运行得很快(每个字符仅进行一次比较),如果添加更多表达式,则不会变慢。

缺点是自动机需要巨大的数据表,并且有许多类型的正则表达式不受支持(例如,反向引用)

我们使用的那个是当时我们公司的C ++模板螺母手工编码的,所以不幸的是我没有任何FOSS解决方案可以指向你。但是如果你使用“ DFA ”进行谷歌正则表达式或正则表达式,你会找到能指向正确方向的东西。

答案 2 :(得分:11)

这是词法分子的工作方式。

正则表达式被转换为单个非确定性自动机(NFA),并可能在确定性自动机(DFA)中进行转换。

生成的自动机将尝试一次匹配所有正则表达式,并将在其中一个上成功。

有许多工具可以帮助你,它们被称为“词法生成器”,并且有适用于大多数语言的解决方案。

您没有说您使用的是哪种语言。对于C程序员,我建议您查看re2c工具。当然传统的(f)lex总是一种选择。

答案 3 :(得分:9)

Martin Sulzmann在这个领域做了很多工作。 他a HackageDB project解释了使用here似乎是为此量身定制的partial derivatives

使用的语言是Haskell,因此如果需要的话,将非常难以翻译成非函数式语言(我认为翻译到许多其他FP语言仍然会非常困难)。

代码不是基于转换为一系列自动机然后组合它们,而是基于对正则表达式本身的符号操作。

此外,该代码非常具有实验性,Martin不再是教授,而是“有收益的工作”(1),因此可能不感兴趣/无法提供任何帮助或意见。


  1. 这是一个笑话 - 我喜欢教授,聪明的人尝试工作的机会越少,我获得报酬的机会就越多!

答案 4 :(得分:7)

10,000 regexen呃? Eric Wendelin's层次结构的建议似乎是一个好主意。你有没有想过将这些regexen的巨大程度降低到像树结构这样的东西?

作为一个简单的例子:所有需要数字的regexen都可以从一个正则表达式检查分支,所有regexen都不需要一个另一个分支。通过这种方式,您可以将实际比较的数量减少到沿树的路径,而不是在10,000中进行每次比较。

这将需要分解提供给类型的regexen,每个类型都有一个共享测试,如果失败则会排除它们。通过这种方式,理论上可以显着减少实际比较的数量。

如果你必须在运行时这样做,你可以解析你给定的正则表达式,并将它们“归档”为预定义的类型(最容易做的)或那时生成的比较类型(不那么容易)。 / p>

将“hello”与“[H | h] ello”和“。{0,20} ello”进行比较的例子并不能真正得到这个解决方案的帮助。这可能有用的一个简单情况是:如果你有1000个测试,如果“ello”存在于字符串中的某个位置并且你的测试字符串是“goodbye”,那么它只会返回true。你只需要对“ello”进行一次测试,并且知道需要它的1000次测试将不起作用,因此,你不必这样做。

答案 5 :(得分:6)

如果您正在考虑“10,000 regexes”,那么您需要改变您的流程。如果不出意外,请考虑“10,000个匹配的目标字符串”。然后寻找用于处理“目标字符串的船载”情况的非正则表达式方法,如Aho-Corasick机器。坦率地说,似乎有些东西在使用过程中要比使用哪台机器早得多,因为10,000个目标字符串听起来更像是数据库查找而不是字符串匹配。

答案 6 :(得分:5)

你需要有一些方法来确定给定的正则表达式是否与另一个正相关。创建各种各样的正则表达式“层次结构”,允许您确定某个分支的所有正则表达式都不匹配

答案 7 :(得分:4)

你可以将它们组合成20个小组。

(?=(regex1)?)(?=(regex2)?)(?=(regex3)?)...(?=(regex20)?)

只要每个正则表达式都具有零(或至少相同数量)的捕获组,您就可以查看捕获的内容以查看哪些模式匹配。

如果regex1匹配,捕获组1将具有匹配的文本。如果不是,则为undefined / None / null / ...

答案 8 :(得分:2)

我会说这是一个真正的解析器的工作。中点可能是Parsing Expression Grammar (PEG)。它是模式匹配的更高级抽象,一个特征是您可以定义整个语法而不是单个模式。有一些高性能的实现可以通过将语法编译成字节码并在专用VM中运行来实现。

免责声明:我唯一知道的是LPEGLua的图书馆,掌握基本概念并不容易(对我来说)。

答案 9 :(得分:2)

Aho-Corasick 是我的答案。

我有2000种类别的东西,每种都有可以匹配的模式列表。字符串长度平均约100,000个字符。

主要警告:要匹配的模式是所有语言模式而非正则表达式模式,例如'cat' vs 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 ms631 ms做了re.search()顺序# ./home/views.py ... def post(self, request, *args, **kwargs): user = request.data['user'] serializer = GetProductSerializer(UserToProduct.objects.filter(user=user).prefetch_related('prod'), many=True) return Response(serializer.data) 快315倍!

答案 10 :(得分:2)

如果您正在使用真正的正则表达式(那些与正式语言理论中的常规语言相对应,而不是某些类似Perl的非常规事物),那么您很幸运,因为常规语言在联合下关闭。在大多数正则表达式语言中,管道(|)是联合的。所以你应该能够构造一个字符串(代表你想要的正则表达式),如下所示:

(r1)|(r2)|(r3)|...|(r10000)

其中括号用于分组,而不是匹配。与此正则表达式匹配的任何内容都至少匹配一个原始正则表达式。

答案 11 :(得分:1)

您可以将正则表达式编译为混合DFA / Bucchi automata,其中每次BA进入接受状态时,您都会标记哪个正则表达式规则“命中”。

Bucchi对此有点过分,但修改DFA的工作方式可以解决问题。

答案 12 :(得分:1)

如果您需要知道哪些正则表达式匹配,我建议使用Intel的Hyperscan。它是为此目的而构建的。如果您需要采取的动作更为复杂,则也可以使用ragel。尽管它会生成一个DFA,并且可能导致许多状态,并因此导致非常大的可执行程序。 Hyperscan采用NFA / DFA /自定义混合方法进行匹配,可以很好地处理大量表达式。

答案 13 :(得分:0)

我几乎建议编写一个“由内而外”的正则表达式引擎 - 其中'目标'是正则表达式,而'术语'是字符串。

然而,似乎您迭代地尝试每一个的解决方案将变得更加容易。

答案 14 :(得分:0)

我使用Ragel执行离开操作:

action hello {...}
action ello {...}
action ello2 {...}
main := /[Hh]ello/  % hello |
        /.+ello/ % ello |
        any{0,20} "ello"  % ello2 ;

字符串"你好"将调用action hello块中的代码,然后调用action ello块中的代码,最后调用action ello2块中的代码。

他们的正则表达式非常有限,而且机器语言是首选,你的例子中的大括号只能使用更通用的语言。

答案 15 :(得分:-1)

尝试将它们组合成一个大的正则表达式吗?

答案 16 :(得分:-1)

我认为简短的回答是肯定的,有一种方法可以做到这一点,并且它是计算机科学众所周知的,我不记得它是什么。

简短的回答是,你可能会发现你的正则表达式解释器已经在一起处理所有这些,或者你可能会找到一个。如果没有,现在是时候进行谷歌字符串匹配和搜索算法了。

答案 17 :(得分:-1)

最快的方法似乎就是这样(代码是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;
}

哦,你的意思是最快的代码?我当时不知道......