如何有效地将输入字符串与多个正则表达式匹配?

时间:2011-08-13 10:27:25

标签: regex rest pattern-matching

一个如何有效地将一个输入字符串与任意数量的正则表达式匹配?

这可能对REST Web服务有用。我们假设我已经为REST Web服务的公共接口提出了许多URL模式:

  • /user/with-id/ {userId}
  • /user/with-id/ {userId} /profile
  • /user/with-id/ {userId} /preferences
  • /users
  • /users/who-signed-up-on/ {date}
  • /users/who-signed-up-between/ {fromDate} /and/ {toDate}
  • ...

其中 {…} 被命名为占位符(如正则表达式捕获组)。

  

注意:这个问题不是关于上面的REST接口是否设计得很好。 (可能不是,但在这个问题的背景下这应该不重要。)

可以假设占位符通常不会出现在模式的最开头(但它们可以)。也可以安全地假设任何字符串都不可能匹配多个模式。

现在,Web服务收到请求。当然,可以将请求的URI与一个URL模式顺序匹配,然后与下一个模式匹配,依此类推;但是,对于必须检查的大量模式,这可能无法很好地扩展。

对此有什么有效的算法吗?

输入

  • 输入字符串
  • 一组“互斥”正则表达式的(即没有输入字符串可能匹配多于一个表达)

输出:

  • 输入字符串与之匹配的正则表达式(如果有)。

5 个答案:

答案 0 :(得分:10)

Aho-Corasick algorithm是一种非常快速的算法,可以将输入字符串与一组模式(实际上是关键字)进行匹配,这些模式在trie中进行预处理和组织,以加速匹配。

这种算法的变体可以支持正则表达式模式(即http://code.google.com/p/esmre/只是为了命名一个),这些可能值得一看。

或者,您可以将URL拆分为块,将它们组织在树中,然后将URL拆分为匹配并一次将树移动一个块。 {userId}可以被认为是通配符,或者匹配某些特定格式(即,是int)。

当你到达一片叶子时,你知道你匹配的是哪个网址

答案 1 :(得分:4)

将多个正则表达式与输入流匹配的标准解决方案是lexer-generator,例如Flex(有很多可用的,通常是每个编程语言的几个)。

这些工具采用一组与“标记”相关联的正则表达式(将标记视为正则表达式匹配的名称),并生成有效的有限状态自动机,以同时匹配所有正则表达式。这是线性时间,输入流的大小非常小;很难要求比这更“快”。你输入一个字符流,它会发出匹配“best”的正则表达式的令牌名称(这处理两个正则表达式可以匹配相同字符串的情况;请参阅词法分析器生成器以获取此定义),并推进流通过什么被认可。因此,您可以反复应用它以匹配一系列令牌的输入流。

不同的词法分析器生成器允许您以不同的方式捕获识别的流的不同位,因此您可以在识别出令牌后选择您关心的部分(例如,对于引号中的文字字符串,您只关心关于字符串内容,而不是引号)。

答案 2 :(得分:3)

如果url结构中存在层次结构,则应使用该层次结构来最大化性能。只有以/ user /开头的url才能匹配前三个中的任何一个,依此类推。

我建议将层次结构存储在与url层次结构对应的树中匹配,其中每个节点与层次结构中的级别匹配。要匹配网址,请针对树的所有根测试网址,其中只有具有“用户”和“用户”的正则表达式的节点。匹配url:s针对这些节点的子节点进行测试,直到在叶节点中找到匹配为止。成功匹配可以作为从根到叶的节点列表返回。可以从成功匹配的节点中获取具有属性值(如{user-id})的命名组。

答案 3 :(得分:1)

使用命名表达式和OR运算符,即“(?P<re1>...)|(?P<re2>...)|...”。

答案 4 :(得分:1)

首先,我认为这个过程没有看到任何好的优化。

但是,如果你有大量的正则表达式,你可能想要对它们进行分区(我不确定这是否在技术上是分区的。)

我告诉你的是:

假设您有20个以user开头的网址:

/user/with-id/X
/user/with-id/X/preferences # instead of preferences, you could have another 10 possibilities like /friends, /history, etc

然后,您还有20个以users开头的网址:

/users/who-signed-up-on
/users/who-signed-up-on-between     #others: /registered-for, /i-might-like, etc

该列表会继续显示/products/companies等,而不是用户。

在这种情况下,您可以使用“多级”匹配

首先,匹配字符串的开头。您将匹配/products/companies/users,一次一个,并忽略其余字符串。这样,您就不必测试所有100种可能性。

在您知道网址以/users开头后,您只能匹配以用户开头的网址。

这样,您可以减少许多不必要的匹配。您将无法匹配所有/procucts种可能性的字符串。