如何编写一个完全匹配同一字符的 N 次重复(或者,理想情况下是同一组)的表达式?基本上是什么(.)\1{N-1}
,但有一个重要限制:如果主题重复N 次以上,则表达式应该失败。例如,给定N=4
和字符串xxaaaayyybbbbbzzccccxx
,表达式应该匹配aaaa
和cccc
而不是bbbb
。
我不专注于任何特定的方言,随意使用任何语言。请不要发布仅适用于此特定示例的代码,我正在寻找通用解决方案。
使用负前瞻和负后瞻。
这将是正则表达式:(.)(?<!\1.)\1{N-1}(?!\1)
除了 Python 的 re 模块已损坏(请参阅此链接)。
英文翻译:“匹配任何字符。确保在匹配该字符之后,它之前的字符不是那个字符。匹配该字符的 N-1 个重复。确保那些重复之后的字符不是那个字符特点。”
不幸的是,re 模块(和大多数正则表达式引擎)已损坏,因为您不能在后向断言中使用反向引用。Lookbehind 断言必须是恒定长度,并且编译器不够聪明,无法推断它是在使用反向引用时(即使在这种情况下,反向引用是恒定长度的)。我们必须通过这个来处理正则表达式编译器,如下所示:
实际的答案必须更加混乱:r"(.)(?<!(?=\1)..)\1{N-1}(?!\1)"
这可以通过使用(?=\1)..
而不是解决 re 模块中的错误\1.
(这些在大多数情况下是等效的。)这让正则表达式引擎准确地知道后向断言的宽度,因此它可以在 PCRE 和 re 等中工作。
当然,现实世界的解决方案类似于[x.group() for x in re.finditer(r"(.)\1*", "xxaaaayyybbbbbzzccccxx") if len(x.group()) == 4]
我怀疑你想使用负前瞻: (.)\1{N-1}(?!\1)
。
但这就是说……我怀疑最简单的跨语言解决方案就是自己编写而不使用正则表达式。
更新:
^(.)\\1{3}(?!\\1)|(.)(?<!(?=\\2)..)\\2{3}(?!\\2)
更普遍地为我工作,包括从字符串开头开始的匹配。
Perl 的正则表达式引擎不支持可变长度的lookbehind,所以我们必须慎重考虑。
sub runs_of_length {
my($n,$str) = @_;
my $n_minus_1 = $n - 1;
my $_run_pattern = qr/
(?:
# In the middle of the string, we have to force the
# run being matched to start on a new character.
# Otherwise, the regex engine will give a false positive
# by starting in the middle of a run.
(.) ((?!\1).) (\2{$n_minus_1}) (?!\2) |
#$1 $2 $3
# Don't forget about a potential run that starts at
# the front of the target string.
^(.) (\4{$n_minus_1}) (?!\4)
# $4 $5
)
/x;
my @runs;
while ($str =~ /$_run_pattern/g) {
push @runs, defined $4 ? "$4$5" : "$2$3";
}
@runs;
}
几个测试用例:
my @tests = (
"xxaaaayyybbbbbzzccccxx",
"aaaayyybbbbbzzccccxx",
"xxaaaa",
"aaaa",
"",
);
$" = "][";
for (@tests) {
my @runs = runs_of_length 4, $_;
print qq<"$_":\n>,
" - [@runs]\n";
}
输出:
“xxaaaayybbbbbzzccccxx”: - [aaaa][cccc] “aaaayybbbbbzzccccxx”: - [aaaa][cccc] “xxaaaa”: - [aaaa] “啊啊”: - [aaaa] “”: - []
这是一个有趣的谜题,但如果这样的结构出现在生产代码中,你厌恶正则表达式的同事可能会不高兴。
很容易给正则表达式增加太多负担并试图让它们做所有事情,而几乎所有事情都可以!
使用正则表达式查找由单个字符组成的所有子字符串,然后分别检查它们的长度,如下所示:
use strict;
use warnings;
my $str = 'xxaaaayyybbbbbzzccccxx';
while ( $str =~ /((.)\2*)/g ) {
next unless length $1 == 4;
my $substr = $1;
print "$substr\n";
}
输出
aaaa
cccc
>>> import itertools
>>> zz = 'xxaaaayyybbbbbzzccccxxaa'
>>> z = [''.join(grp) for key, grp in itertools.groupby(zz)]
>>> z
['xx', 'aaaa', 'yyy', 'bbbbb', 'zz', 'cccc', 'xx', 'aa']
N==4
从那里您可以很容易地遍历列表并检查场合,如下所示:
>>> [item for item in z if len(item)==4]
['cccc', 'aaaa']
在Java中,我们可以像下面的代码那样做
String test ="xxaaaayyybbbbbzzccccxx uuuuuutttttttt";
int trimLegth = 4; // length of the same characters
Pattern p = Pattern.compile("(\\w)\\1+",Pattern.CASE_INSENSITIVE| Pattern.MULTILINE);
Matcher m = p.matcher(test);
while (m.find())
{
if(m.group().length()==trimLegth) {
System.out.println("Same Characters String " + m.group());
}
}
这在python中怎么样?
def match(string, n):
parts = []
current = None
for c in string:
if not current:
current = c
else:
if c == current[-1]:
current += c
else:
parts.append(current)
current = c
result = []
for part in parts:
if len(part) == n:
result.append(part)
return result
使用各种大小的字符串进行测试:
match("xxaaaayyybbbbbzzccccxx", 6) = []
match("xxaaaayyybbbbbzzccccxx", 5) = ["bbbbb"]
match("xxaaaayyybbbbbzzccccxx", 4) = ['aaaa', 'cccc']
match("xxaaaayyybbbbbzzccccxx", 3) = ["yyy"]
match("xxaaaayyybbbbbzzccccxx", 2) = ['xx', 'zz']
第一个循环基本上将文本分成几部分,如下所示:[“xx”、“aaaa”、“yyy”、“bbbbb”、“zz”、“cccc”、“xx”]。然后第二个循环测试这些部分的长度。最后,该函数仅返回具有当前长度的部分。我不是最擅长解释代码的人,所以如果需要,任何人都可以自由地加强这个解释。
无论如何,我认为这会做!
为什么不让正则表达式引擎做它最擅长的事情——找到最长的相同符号字符串,然后自己检查长度?
在 Perl 中:
my $str = 'xxaaaayyybbbbbzzccccxx';
while($str =~ /(.)\1{3,}/g){
if(($+[0] - $-[0]) == 4){ # insert here full match length counting specific to language
print (($1 x 4), "\n")
}
}