提问人:Sam 提问时间:9/17/2020 最后编辑:Sam 更新时间:3/1/2021 访问量:899
正则表达式查找不连续的重复单词(即在字符串中多次出现)
Regex to find inconsecutive duplicate words (i.e. occurs more than once in a string)
问:
什么是正则表达式,它查找字符串中出现多次(不一定连续出现)的所有单词的所有实例?
例如,在字符串中:
如果土拨鼠可以夹木头,土拨鼠可以夹多少木头? 如果土拨鼠可以夹木头,土拨鼠会夹掉他能夹住的所有木头。
它会找到每个重复单词的实例;在上面的示例中,它将找到以下单词:。"wood","could","a","woodchuck","chuck","if"
我在互联网上搜索了这样的正则表达式,但无济于事。有人会认为这就是 SO 上关于“使用正则表达式查找副本”的所有问题都会讨论的内容,但它们都只讨论相邻的单词,例如“the”。
答:
也许下面的演示代码很容易理解
use strict;
use warnings;
use feature 'say';
my $text = do { local $/, <DATA> };
my(%count,@found,$regex);
$count{$_}++ for split '[ .,?]', $text;
$count{$_} > 1 && push @found, $_ for keys %count;
$regex = join('|',@found);
say 'Before: ' . $text;
$text =~ s/\b($regex)\b/<$1>/g;
say 'After: ' . $text;
__DATA__
How much wood could a woodchuck chuck if a woodchuck could chuck wood? A woodchuck would chuck all the wood he could chuck if a woodchuck could chuck wood.
输出
Before: How much wood could a woodchuck chuck if a woodchuck could chuck wood? A woodchuck would chuck all the wood he could chuck if a woodchuck could chuck wood.
After: How much <wood> <could> <a> <woodchuck> <chuck> <if> <a> <woodchuck> <could> <chuck> <wood>? A <woodchuck> would <chuck> all the <wood> he <could> <chuck> <if> <a> <woodchuck> <could> <chuck> <wood>.
评论
key
<>
您将需要以下内容:
\b\w+\b
(?: (?= .* \b(\1)\b )
| (?<= \b\1\b .* \1 )
)
(确保它可以匹配您正在使用的引擎中的任何角色。根据需要进行调整。.
您还没有指定正则表达式引擎,但我想不出任何支持可变宽度后视的引擎。[1] 然而,这是实现你想要的所必需的。
它的速度也非常慢,在单词方面有 O(N^2) 时间。[2]
好的,有人展示了可变长度后视:在Perl/PCRE中实际上是可能的!他们使用递归一次后退一个字符。玩得开心。
人们通常会使用两个通道,一个用于查找重复项,另一个用于“查找”。
my %seen;
my @dups = grep ++$seen{$_} == 2, $file =~ /\w+/g;
my $alt = join "|", @dups;
$file =~ s/\b($alt)\b/<$&>/g;
就单词而言,这是 O(N)。
从技术上讲,从 Perl 5.30 开始,lookbehinds “可以处理 1 到 255 个字符的可变长度作为实验性功能”。对于OP来说,这太小了,OP在现已删除的评论中谈到了GB。
想象一下,你有一个包含 N 个单词的文档,都是不同的。
- 单词 1 需要与后面的 N-1 个单词和前面的 0 个单词进行比较。
- 单词 2 需要与后面的 N-2 个单词和前面的 1 个单词进行比较。
- ...
- 单词 N-1 需要与后面的 1 个单词和前面的 N-2 个单词进行比较。
- 单词 N 需要与后面的 0 个单词和前面的 N-1 个单词进行比较。
O( (N-1)+0 + (N-2)+1 + ... + 1+(N-2) + 0+(N-1) ) = O( [ (N-1)+(N-2)+...+1+0 ] + [ 0+1+...+(N-2)+(N-1) ] ) = O( [ 0+1+...+(N-2)+(N-1) ] * 2 ) = O( 1+...+(N-2)+(N-1) ) # Constant factors irrelevant in O() = O( (N-1) * ((N-1)+1) / 2 ) # 1+2+..x == x*(x+1)/2 = O( (N-1) * N / 2 ) = O( (N-1) * N ) # Constant factors irrelevant in O() = O( N^2 - N ) = O( N^2 ) # An N term is subsumed by a N^2 term
评论
.*
匹配可变数量的字符,是后视。可变宽度后视的示例也是如此。(?<=...)
(?<=\b\1\b.*\b\1\b)
使用正则表达式完成关键部分的一种方法是使用允许在正则表达式内执行代码的功能来构建频率哈希(映射、字典)。然后,可以使用它来选择重复的单词。
仅使用正则表达式完成所有操作,而不使用任何语言或工具,实际上是不可能的(除非使用递归,如果支持的话)。本文的其余部分在正则表达式功能的上下文中采用了编程语言的基础知识,该功能允许在正则表达式中执行代码。
我认为大多数引擎可用的一个功能是运行代码以准备替换字符串。我使用Perl进行以下简短示例。这里感兴趣的是使用副作用完成的,当然不是最佳的(通常会导致代码看起来很笨拙)。
要么运行正常替换并放回匹配的单词
$string =~ s/(\w+)/++$freq{$1}; $1/eg;
或者使用“非破坏性”替换,它不会更改目标(如果匹配失败,则返回更改的字符串或原始字符串)
my $toss = $string =~ s/(\w+)/++$freq{$1}/egr;
如上所述,任务不需要返回的字符串。在这两种情况下,当这不是我们实际需要的时,我们会对每个单词进行替换。
然后打印频率大于 1 的键(字)
foreach my $word (keys %freq) { say $word if $freq{$word} > 1 }
正则表达式匹配每个字符类的“单词”;根据您的需要进行调整。\w
总而言之,由于这对于正则表达式来说是一项棘手的任务,我建议将字符串拆分为单词并使用您的语言功能计算重复项,而不是推送正则表达式。任何有价值的语言都可以相当优雅和有效地做到这一点。
使用 Perl,另一种方法是使用嵌入式代码结构,它允许代码在匹配的部分中运行。据我所知,除了一些库之外,其他语言没有。
可以像这样在匹配部分运行代码
my @matches = $string =~ /(\w+)(?{++$freq{$1}})/g;
其中构造将执行嵌入式代码,此处构建频率哈希。使用此功能(安全)需要仔细阅读文档。(?{code})
以上内容包含所有内容,对所述问题不感兴趣;它在这里用于将正则表达式的匹配运算符放在“列表上下文”中,以便通过修饰符继续搜索字符串。@matches
/g
评论
下一个:验证混淆令牌
评论
\b(\w+)\b(?=.*\1)