Replace only up to N matches on a line

perl, regex

Solution

I can think of three reliable ways. The first is to replace everything after the Nth match with itself.

my $max = 5;
$s =~ s/(aa)/ $max-- > 0 ? 'bb' : $1 /eg;

That's not very efficient if there are far more than N matches. For that, we need to move the loop out of the regex engine. The next two methods are ways of doing that.

my $max = 5;
my $out = '';
$out .= $1 . 'bb' while $max-- && $in =~ /\G(.*?)aa/gcs;
$out .= $1 if $in =~ /\G(.*)/gcs;

And this time, in-place:

my $max = 5;
my $replace = 'bb';
while ($max-- && $s =~ s/\G.*?\Kaa/$replace/s) {
   pos($s) = $-[0] + length($replace);
}

You might be tempted to do something like

my $max = 5;
$s =~ s/aa/bb/ for 1..$max;

but that approach will fail for other patterns and/or replacement expressions.

my $max = 5;
$s =~ s/aa/ba/ for 1..$max;  # XXX Turns 'aaaaaaaa'
                             #     into 'bbbbbaaa'
                             #     instead of 'babababa'

And of course, starting from the beginning of the string every time could be expensive.

Problem

In Perl, how to write a regular expression that replaces only up to N matches per string? I.e., I'm looking for a middle ground between `s/aa/bb/;` and `s/aa/bb/g;`. I want to allow multiple substitutions, but only up to N times.

Original source