问题 将多个正则表达式组合成一个自动机


假设我有一个正则表达式列表(从外部源读取 - 文件,数据库等)。我想检查字符串匹配哪些正则表达式。

我可以通过所有这些正则表达式创建迭代并匹配它们,但列表可能是一个巨大的,这是一个关键的操作。

我可以将所有这些正则表达式合并为一个(在它们之间),但问题是我只能识别第一个匹配的正则表达式,而不是全部。

另一个想法可能是为所有这些正则表达式创建一个自动机,并用相应正则表达式的索引标记最终状态。我在看 http://cs.au.dk/~amoeller/automaton/,一个似乎能够使用正则表达式和自动机的库,但不确定它可以扩展来解决我的问题。

你还有其他建议吗?

为了澄清一些评论,我添加了一个代码示例:

import java.util.regex.Matcher;
import java.util.regex.Pattern;


public class PatternTest {
    public static void main(String[] args) {
        Pattern p = Pattern.compile("(a(?:b|c)a)|((?:a|b)ba)|(ab(?:a|c))");     
        Matcher m = p.matcher("aba");
        System.out.println(m.matches());
        System.out.println(m.groupCount());
        for (int i = 0, n = m.groupCount(); i < n; i++) {
            System.out.println(m.group(i));
        }
    }
}

将打印出来

true
3
aba
aba
null

正如您所看到的,只有第一组匹配,我没有找到匹配其他两组的方法。

更多发现 - 使用上面的自动机库,问题将减少到以下几点:如果连接两个或多个自动机,如何识别哪个原始自动机对应的最终状态?


10593
2018-03-08 15:47


起源

您是否考虑过为每个''ed表达式添加命名组?你可以检查哪些匹配。 - Michael W
这听起来像是你对Java的选择。在Perl tho它会更容易。您可以只交替所有表达式,并在每个表达式的末尾(称为 X)例如添加 (?{ $matched{X} = 1 })(?!)。这标志着表达 X 匹配,然后失败匹配,允许其他表达式匹配。 (为了优化它,您还可以将每个表达式放在原子捕获组中。) - Qtax
@MichaelW:是的,我也考虑过这一点。问题是Java中的regexp只匹配第一个组(命名或未命名)。 - Adrian Ber
@Qtax:问题是我是否可以在Java中做这样的事情。 - Adrian Ber
如果使用自动机实现正则表达式,则算法复杂度将取决于自动机的深度。将它们组合将导致复杂性取决于这些正则表达式的最大长度,而不像通过迭代它们将导致总和。 - Adrian Ber


答案:


dk.brics.automaton 并不直接支持这一点,但您可以概括自动机(以及相关的自动机操作)的表示来区分不同类型的接受状态。首先,将一个int字段添加到  class并在设置'accept'时使用此字段。


2
2018-03-09 15:24





我实现了基于dk.brics.automaton的解决方案,你可以在这里找到它。 https://github.com/fulmicoton/multiregexp


5
2017-10-20 17:14





对于确定的答案(如果有的话),我们需要更多信息,例如:

  1. 你说正则表的列表是巨大的;你可以说得更详细点吗?成千上万的?百万?数十亿甚至数十亿?

  2. 谁写了这些正则表达式,他们知道他们在做什么吗?是否正确测试了正则表达式(正确性  性能)在被添加到列表之前?

  3. 在您的示例代码中,您使用了 matches() 方法,需要正则表达式来描述整个字符串。就好像正则表达式一样
    \A(?:(a(?:b|c)a)|((?:a|b)ba)|(ab(?:a|c)))\z
    哪个匹配 "aba" 但不是 "aaba" 要么 "abaa"。如果您在使用Java之前已在其他工具或语言中使用了正则表达式,则此行为可能会让您感到惊讶。传统上,如果正则表达式描述任何字符串,则字符串一直被称为“匹配”正则表达式  在字符串中,甚至是零长度子字符串。要在Java中获得该行为,您必须使用Matcher find() 方法。

  4. 是否有任何常见因素可以从列表中的所有正则表达式中提取出来,例如最小或最大长度,公共子串或共享字符子集?例如,任何与您的一个样本模式匹配的字符串也必须匹配 [abc]{3}。如果有,你可能想要在严重匹配开始之前运行基于它们的过滤器(可能是正则表达式,也许不是)。 (如果你使用的是Perl,我不会建议这样做,这已经是像这样优化的choc-a-bloc,但是Java并不自豪地接受一点帮助。)

但我觉得很安全,建议你使用单独的正则表达式而不是将它们连成一个。 Frankenregex不一定表现更好,故障排除将是一场噩梦!您可以预编译所有Pattern对象,并且可以提前创建Matcher对象并将其重用于所有匹配,如下所示:

m.reset(s).usePattern(p);

这是一个 演示。我无法做出任何保证(一方面,你仍然受到编写正则表达式的人的支配),但如果解决方案成为可能,我认为这是最有希望的方法。


3
2018-03-09 15:12



很好的答案。也许是因为这是我的想法,但添加的演示很好,我了解了我之前没有考虑过的重置(x)功能。 - Omertron