检查一个正则表达式是否涵盖另一个正则表达式

Check if one regex covers another regex

本文关键字:正则表达式 是否 另一个 一个 检查      更新时间:2023-10-16

我正在尝试实现文本聚类算法。该算法通过将类似的原始文本行替换为正则表达式来对它们进行聚类,并聚合与每个正则表达式匹配的模式数量,以便提供输入文本的整洁摘要,而不是显示输入文本中的重复模式。在这次尝试中,我遇到了需要查找一个正则表达式是否覆盖另一个正则表达式。

假设我们关心带有"*"和"+"通配符的正则表达式,即"*"表示字母表的零次或多次出现,而"+"表示字母表的出现一次或多次。还假定字符集为 ASCII。

例如:

1. AB covers AB
      This is straightforward.
2. ABC* covers ABC
      Because ABC* can generate: ABC, ABCC, ABCCC etc.
3. A*B+C* covers AB+C*
      Because A*B+C* can generate ABBC, AABBC, AABBCC etc. which covers
      all strings generated by AB+C*.
4. A+M+BC* covers AMM+B+C+M+BC*
      Similar to case [3] above.

基本上,我正在寻找以下方法的有效实现,该方法告诉strA(可能包含正则表达式(是否覆盖strB(可能包含正则表达式(。请注意,还应该有一种方法可以转义输入字符串 strA 和 strB 中的正则表达式字符 '*' 和 '+'。

C++方法签名:

bool isParentRegex(const string& strA, const string& strB)

我的想法是,实现需要递归方法,它可能有点复杂。但是我很想知道我是否可以重用现有的实现而不是重新发明轮子,或者是否有任何其他直接的方法可以做到这一点。

考虑到您提出的简单正则表达式语法,解决方案相当微不足道。

以更复杂的例子为例,A+M+BC* covers AMM+B+C+M+BC*您可以将其重写为A{1,}M{1,}B{1,1}C{0,}封面A{1,1}M{2,}B{1,}C{1,}M{1,}B{1,1}C{0,}

这导致我们得出一个简单的规则:R1涵盖R2如果所有符号都以相同的顺序出现,则所有R1的下限都小于或等于R2的下限,并且R1的上限大于或等于R2的上限。

现在,简单的规则有一个小问题。 AB*C涵盖了AC,即可选符号有可能出现在R1而不是R2中。您可以通过在 R2 中插入一个{0,0}来解决这个问题,当 R1 中有一个(可选(符号没有出现在 R2 中的等效位置时。例如 AB*C确实涵盖了AB{0,0}C

"可选符号"规则是一种优化。如果R1中的符号不是可选的,R1肯定不会涵盖R2。例如 AB+C不包括AC.因此,无需插入B{0,0}。但是如果你这样做,你会发现A{1,1}B{1,}C{1,1}没有涵盖A{1,1}B{0,0}C{1,1},因为B (1( 的R1下限大于B (0( 的R2下限

我会做一些事情,比如实现一个函数,用于从给定的正则表达式中查找最小的 DFA。让我们假设

DFA GetMinimalDFA(Regex r1(就是这样做的。

bool isParentRegex(Regex r1, Regex r2) {
    DFA a = GetMinimalDFA(r1);
    DFA b = GetMinimalDFA(Regex.OR(r1,r2))
    return a.Equals(b);
}

在Perl中,这将非常简单。第一步是通过将A+更改为AA*A*A更改为AA*,并将A*A*更改为A*来规范化每个正则表达式:

sub normalize_regex($)
{
    local $_ = shift;
    s/(.)+/$1$1*/g;
    1 while s/(.)*1(?!*)/$1$1*/g or s/(.*)1/$1/g;
    return $_;
}
第二步是将第一个正则表达式从与字符串本身匹配的正则表达式

转换为与这些字符串匹配的规范化正则表达式的 Perl-正则表达式;例如,AA*B 将转换为 ^AA**?B$ ,意思是"字符串开头,后跟 A,后跟零个或多个 A,可选后跟星号,后跟 B, 后跟字符串结尾":

sub regex_to_metaregex($)
{
    local $_ = shift;
    s/(.)(*?)/$2 ? "Q$1E*(Q$1E\*)?" : "Q$1"/eg;
    return qr/^$_$/;
}

第三步不需要解释:

sub does_regex1_cover_regex2($$)
{
    my ($r1, $r2) = @_;
    $r1 = regex_to_metaregex normalize_regex $r1;
    $r2 = normalize_regex $r2;
    return scalar $r2 =~ m/$r1/;
}

这将为您的案例 #1–3 返回一个 true 值。但是,它为您的案例 #4 返回一个 false 值,因为除非我真的错过了什么,否则A+M+BC*不会涵盖AMM+B+C+M+BC*

请注意,还应该有一种方法可以转义输入字符串 strA 和 strB 中的正则表达式字符 '*' 和 '+'。

在上面的代码中,我并不担心这一点,但是由于您只担心ASCII,因此预处理步骤可以通过将它们转换为ASCII范围之外的单个字符来处理*含义*+含义+\含义

sub process_escapes($)
{
    local $_ = shift;
    s/\\/x80/g;
    s/\+/x81/g;
    s/\*/x82/g;
    s/x80/\/g;
    return $_;
}

(尽管这显然相当黑客(。

在C++中,你可以使用相同的方法——有一些库可以实现 Perl 正则表达式的所有必要功能——尽管显然这需要更多的工作。

请检查这个perl模块源代码,但请记住它不适用于所有正则表达式(因为它将导致解决停止问题。