我正在尝试实现文本聚类算法。该算法通过用正则表达式替换它们来聚类相似的原始文本行,并聚合与每个正则表达式匹配的模式的数量,以便提供输入文本的简洁摘要,而不是显示来自输入文本的重复模式。在这次尝试中,我遇到了寻找一个正则表达式是否涵盖另一个正则表达式的需要。
假设我们只关注 关于带有'*'和'+'通配符的正则表达式,即'*'表示零或更多字母出现,'+'表示出现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)
我的想法是实现需要一个递归方法,它可能有点复杂。但我很想知道我是否可以重用现有的实现,而不是重新发明轮子,或者是否还有其他直接的方法。
答案 0 :(得分:4)
考虑到你提出的简单正则表达式语法,解决方案相当简单。
举出更复杂的例子,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
的所有下限都小于或等于{{1}的下限} {} R2
的上限大于或等于R1
的上限。
现在简单规则存在一个小问题。 R2
涵盖AB*C
,即可能会在AC
而非R1
中显示可选符号。您可以通过在R2
中插入{0,0}
来解决此问题,当R1中的(可选)符号未出现在R2
中的等效位置时。例如。 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}
上的R1
下限(1)比{{1}更多} B
上的下限(0)
答案 1 :(得分:2)
我会做一些事情,比如实现一个函数,用于从给定的正则表达式中查找最小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);
}
答案 2 :(得分:2)
在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-regex;例如,AA*B
将转换为^AA*\*?B$
,意思是“字符串开头,后跟A,后跟零或更多A,后跟星号,后跟B,后跟结束-of-字符串“
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,预处理步骤可以处理\*
意味着*
,\+
意味着{{1通过将它们转换为ASCII范围之外的单个字符来表示+
,意思是\\
:
\
(虽然这显然是相当hack)。
在C ++中,您可以使用相同的方法 - 存在实现Perl正则表达式的所有必要功能的库 - 尽管显然它需要更多的工作。
答案 3 :(得分:0)
请检查this perl module source,但请记住它不适用于所有正则表达式(因为它会导致解决halting problem。