使用Perl计算字符串中连续字符的数量

7

我有一个字符串,其中包含多个连续字符序列,例如:

aaabbcccdddd

我希望将其表示为:a3b2c3d4 目前,我想到的是:
#! /usr/bin/perl

$str = "aaabbcccdddd";
$str =~ s/(.)\1+/$1/g;

print $str."\n";

输出:

abcd

它将连续的字符存储在捕获缓冲区中,并仅返回一个字符。但是,我想要一种方法来计算捕获缓冲区中连续字符的数量,然后显示一个字符,后跟该计数,以便它显示输出为a3b2c3d4而不是abcd
上述正则表达式需要做出什么修改?
3个回答

11

看起来需要在替换命令上加上 'execute' 选项,这样替换文本就会被视为 Perl 代码片段:

 $str =~ s/((.)\2+)/$2 . length($1)/ge;

脚本

#!/usr/bin/env perl
use strict;
use warnings;

my $original = "aaabbcccdddd";
my $alternative = "aaabbcccddddeffghhhhhhhhhhhh";

sub proc1
{
    my($str) = @_;
    $str =~ s/(.)\1+/$1/g;
    print "$str\n";
}

proc1 $original;
proc1 $alternative;

sub proc2
{
    my($str) = @_;
    $str =~ s/((.)\2+)/$2 . length($1)/ge;
    print "$str\n";
}

proc2 $original;
proc2 $alternative;

输出

abcd
abcdefgh
a3b2c3d4
a3b2c3d4ef2gh12

你能否拆分这个正则表达式,解释一下它是如何工作的?

我假设有问题的是匹配部分而不是替换部分。

原始正则表达式为:

(.)\1+

这段正则表达式捕获一个单一字符(.),该字符后跟着同一个字符重复出现一次或多次。

修订后的正则表达式与原来相同,但同时还捕获整个模式:

((.)\2+)

第一个左括号开始了整个匹配的捕获;第二个左括号开始捕获单个字符。但是,现在它是第二次捕获,因此原始代码中的 \1 需要变成修订版中的 \2

由于搜索捕获了所有重复字符的字符串,所以替换可以轻松确定模式的长度。


1
如果您可以忍受$&引起的减速,那么以下内容将起作用:
$str =~ s/(.)\1*/$1. length $&/ge;

在上述表达式中将*更改为+,可以使非连续字符保持不变。

正如JRFerguson所提醒的那样,Perl 5.10+提供了一个等效的${^MATCH}变量,它不会影响正则表达式的性能:

$str =~ s/(.)\g{1}+/$1. length ${^MATCH}/pge;

对于 Perl 5.6+,仍然可以避免性能损失:

$str =~ s/(.)\g{1}+/ $1. ( $+[0] - $-[0] ) /ge;

2
Perl 5.10引入${^MATCH}以避免$&的性能损失。请参见perlre - JRFerguson
为了限制性能损失,请在匹配操作中添加'\p'修饰符。然后,${^PREMATCH}${^MATCH}${^POSTMATCH}仅针对当前匹配项捕获,而不是每个匹配项。 - JRFerguson
@Zaid:我认为有必要进一步澄清如何避免性能损失。不过,就个人而言,我仍然很喜欢你的答案。 - JRFerguson
1
似乎有一个连续的“路过踩”非评论者;有人不喜欢你和我提供的解决方案,但没有准备花时间提供自己更好的解决方案或者解释为什么需要踩。这很烦人;这种事情时有发生;我们俩除了彼此同情之外,没什么可以做的。 - Jonathan Leffler
根据 perldoc perlre/p 保证在匹配成功的情况下 ${^PREMATCH}${^MATCH}${^POSTMATCH} 将被定义。这是使用它的一个很好的理由;这是你早期评论的意思吗? - Zaid
显示剩余3条评论

1

JS:

let data = "ababaaaabbbababb";

data.replace(/((.)\2+)/g, (match, p1, p2) =>  {
  data = data.replace(new RegExp(p1, 'g'), p2 + p1.length);
});

console.log(data);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接