2012-04-10 6 views
7

Wie schreibt man in Perl einen regulären Ausdruck, der pro String nur bis zu N Übereinstimmungen ersetzt?Nur bis zu N Übereinstimmungen in einer Zeile ersetzen

Ie., ich bin auf der Suche nach einem Mittelweg zwischen s/aa/bb/; und s/aa/bb/g;. Ich möchte mehrere Ersetzungen erlauben, aber nur bis zu N-mal.

Antwort

4

Ich kann mir drei zuverlässige Möglichkeiten vorstellen. Die erste besteht darin, alles nach dem N-ten Match durch sich selbst zu ersetzen.

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

Das ist nicht sehr effizient, wenn es weit mehr als N Übereinstimmungen gibt. Dazu müssen wir die Schleife aus der Regex-Engine entfernen. Die nächsten beiden Methoden sind Möglichkeiten, dies zu tun.

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

Und dieses Mal in-place:

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

Sie könnten für andere Muster so etwas wie

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

aber dieser Ansatz wird nicht zu tun versucht sein, und/oder Ersatzausdrücke.

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

+1 für das Problem mit' s /.../ .../für 1..N. Aber das Beispiel hat einen kleinen Fehler, "aaaa" würde "bbba" und nicht "bbbb" werden. – Qtax

+0

@Qtax, Thanks.Ich wollte Hubert Schölnast nicht ablehnen, da er neu ist und seine Antwort funktioniert tatsächlich für die spezifische Frage, aber ich bezweifle, dass das OP wirklich mit 'aa' und' bb' arbeitet. Also habe ich geklärt, warum seine Lösung hier fragil ist. – ikegami

+0

@ikegami Es ist eine lange Zeit her, seit ich Perl gemacht habe, aber wenn Sie verfolgen könnten, welche Position in der Zeichenfolge Sie sind und starten Sie die Regex-Suche von dort aus, würde es das Problem beheben mit 's /.../ .../für 1..N'. Obwohl es ein bisschen hässlich wäre. –

1

Was Sie wollen, ist in regulären Ausdrücken nicht möglich. Aber man kann den Austausch in einer for-Schleife setzen:

my $i; 
my $aa = 'aaaaaaaaaaaaaaaaaaaa'; 
for ($i=0;$i<4;$i++) { 
    $aa =~ s/aa/bb/; 
} 
print "$aa\n"; 

Ergebnis:

bbbbbbbbaaaaaaaaaaaa

+2

'für meinen $ i (0 .. 3)' ist der Perl-Weg, es zu schreiben. Ein hübscherer Weg ist '$ aa = ~ s/aa/bb/für 1 .. 3'. – TLP

+0

Vielleicht setzen Sie ein 'last, außer $ aa = ~ ...' da für Effizienz – mob

+3

Beachten Sie, dass dieser Ansatz für einige Paare von Such-und Ersatz-Ausdruck fehlschlagen kann, z. '$ aa = ~ s/aa/ba /;' – ikegami

1

Sie können die /e Flagge, die die rechte Seite als Ausdruck auswertet:

my $n = 3;  
$string =~ s/(aa)/$n-- > 0 ? "bb" : $1/ge; 
+1

Ich werde diesen Syntaxfehler für Sie beheben: '$ n -> 0' (; – Qtax

1

Hier ist eine Lösung, die die/e Modifier, mit dem Sie Perl-Code verwenden, können die Ersatzzeichenfolge zu generieren:

 
    my $count = 0; 
    $string =~ s{ $pattern } 
       { 
       $count++; 
       if ($count < $limit) { 
        $replace; 
       } else { 
        $&; # faking a no-op, replacing with the original match. 
       } 
       }xeg; 

Mit Perl 5.10 oder später können Sie die $ fallen & (das hat seltsame Leistung Komplikationen) und verwenden Sie $ {^ MATCH} über den/p Modifikator

 
    $string =~ s{ $pattern } 
       { 
       $count++; 
       if ($count < $limit) { 
        $replace; 
       } else { 
        ${^MATCH}; 
       } 
       }xegp; 

Es ist schade, man kann nicht einfach das tun, aber Sie können nicht:

 
    last if $count >= $limit; 

Verwandte Themen