【问题标题】:Check if one regex covers another regex检查一个正则表达式是否覆盖另一个正则表达式
【发布时间】:2012-04-10 21:27:29
【问题描述】:

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

假设我们只关注带有“*”和“+”通配符的正则表达式,即“*”表示字母表出现零次或多次,“+”表示出现1次或多次的字母表。还假设字符集是 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)

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

【问题讨论】:

标签: c++ regex data-mining cluster-analysis


【解决方案1】:

在 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$1\E*(\Q$1\E\\*)?" : "\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 返回一个真值。但是,它会为您的案例 #4 返回一个错误值,因为除非我真的错过了什么,否则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 正则表达式的所有必要功能的库 - 尽管显然需要更多的工作。

【讨论】:

  • 您能详细介绍一下用于 C++ 项目的 Regex perl 模块吗?
【解决方案2】:

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

举个更复杂的例子,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。当 R1 中有一个(可选)符号没有出现在 R2 中的等效位置时,您可以通过在 R2 中插入一个 {0,0} 来解决这个问题。例如。 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 下限

【讨论】:

  • 我认为这比你说的要棘手;考虑R1 是A{1,}B{0,}A{1,} 和R2 是A{2,} 的情况。为了在正确的位置插入B{0,0},您需要向上拆分A{2,},这需要在R1 中进行一些前瞻。
  • 我没有考虑A{2,} 作为输入;问题仅陈述了A* 和A+。所以你的情况可能是A+B*A+ 与A+A+,然后很清楚你将在哪里插入B{0,0}。也可能是A+B*A+ 与A*A+A+A*(后者也等于A{2,}),然后确实不太清楚。
  • 我不是说A{2,} 作为输入,我的意思是你重写的结果。 (哦,或者你的意思是B{0,0} 的插入会发生在在 重写期间?我一直把它想象为第二步。)但即使在A+B*A+ 与A+A+ 中也是如此不太清楚在哪里插入B{0,0},除非您进行某种前瞻或回溯以考虑both第一个A+对应A+A+的可能性和它只对应一个A+的可能性。
  • 而且...如果我有一个具有其功能的 ECMA-Regex 怎么办?您的解决方案不完整。
【解决方案3】:

我会做一些事情,比如实现一个从给定正则表达式中找到最小 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);
}

【讨论】:

  • 最小的 DFA?我不确定这是否有效:最小的 DFA 是唯一的吗?而且,计算 minimal DFA 不一定比原始问题更简单。 (事实上​​,我猜这更难。)(编辑:实际上,DFA minimisation 是一个已知问题,所以可能不是)
  • 典型的 Comp.Sci。回答,不过:“我不知道这个问题的解决方案,但我可以证明它和另一个问题一样难”:P 但是,是的,Wiki 链接显示有一个独特的最小 DFA。不过,DFA.Equals(DFA) 是另一个硬函数。
  • @MSalters,哦,是的,我应该阅读它而不是略读它。 :)
【解决方案4】:

请检查this perl module source,但请记住它不适用于所有正则表达式(因为它会导致解决halting problem。

【讨论】:

  • 停机问题适用于图灵机:正则语言(用正则表达式描述)比TM弱得多,所以这个问题有一个通用的解决方案是完全可行的。
  • Perl“正则表达式”不是正式意义上的常规语言。见this earlier question。但是,是的,即使是那些“非常规正则表达式”仍然较弱。
猜你喜欢
  • 2013-09-14
  • 2010-10-04
  • 2021-11-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-07-13
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多