2012-04-27 5 views
7

Ich habe das (was ich glaube zu sein) negativ vorwärtsgerichtete Behauptung<@> *(?!QQQ), dass ich passend zu erwarten, wenn die getestete Zeichenfolge ein <@> durch eine beliebige Anzahl von Räumen (Null einschließlich) gefolgt ist und dann nicht gefolgt von QQQ.Negativ vorwärtsgerichtete Behauptung mit dem * Modifikator in Perl

Wenn jedoch die getestete Zeichenfolge <@> QQQ ist, stimmt der reguläre Ausdruck überein.

Ich verstehe nicht, warum dies der Fall ist, und würde jede Hilfe in dieser Angelegenheit schätzen.

Hier ist ein Testskript

use warnings; 
use strict; 

my @strings = ('something <@> QQQ', 
       'something <@> RRR', 
       'something <@>QQQ' , 
       'something <@>RRR'); 


print "$_\n" for map {$_ . " --> " . rep($_) } (@strings); 



sub rep { 

    my $string = shift; 

    $string =~ s,<@> *(?!QQQ),at w/o ,; 
    $string =~ s,<@> *QQQ,at w/ QQQ,; 

    return $string; 
} 

Dieser druckt

something <@> QQQ --> something at w/o QQQ 
something <@> RRR --> something at w/o RRR 
something <@>QQQ --> something at w/ QQQ 
something <@>RRR --> something at w/o RRR 

Und ich würde die erste Zeile something <@> QQQ --> something at w/ QQQ zu erwarten haben.

Antwort

10

Es passt, weil Null in "jeder Zahl" enthalten ist. Daher entsprechen keine Leerzeichen, gefolgt von einem Leerzeichen "einer beliebigen Anzahl von Leerzeichen, denen kein Q folgt".

Sie sollten eine weitere Lookahead-Assertion hinzufügen, die als erstes nach Ihren Leerzeichen kein Leerzeichen ist. Versuchen Sie, diese (nicht getestet):

<@> *(?!QQQ)(?!) 

ETA Seiten Anmerkung: die quantifier bis + Ändern haben nur hätte geholfen, wenn es genau ein Raum ist; Im allgemeinen Fall kann die Regex immer einen Platz weniger nehmen und daher erfolgreich sein. Regexes wollen übereinstimmen und werden sich nach hinten beugen, um dies auf jede mögliche Weise zu tun. Alle anderen Überlegungen (am weitesten links, am längsten usw.) treten in den Hintergrund - wenn sie mehr als einen Weg finden können, bestimmen sie, welcher Weg gewählt wird. Aber das Matching gewinnt immer, wenn es nicht passt.

+3

'(? = \ S)' sollte sein '(? = [^])' (Falls das nächste Zeichen ein Tab ist). Eigentlich sollte es '(?!)' Sein (falls es das Ende der Zeichenkette ist). – ikegami

+0

Danke für den Fang und Bearbeiten, @ikegami. –

7
$string =~ s,<@> *(?!QQQ),at w/o ,; 
$string =~ s,<@> *QQQ,at w/ QQQ,; 

Ein Problem von Ihnen hier ist, dass Sie die beiden Regexes separat anzeigen. Sie bitten zuerst, die Zeichenfolge ohne zu ersetzen und dann die Zeichenfolge durch QQQ zu ersetzen. Dies überprüft in gewisser Weise das Gleiche zweimal. Zum Beispiel: if (X==0) { ... } elsif (X!=0) { ... }. Mit anderen Worten, kann der Code besser geschrieben:

unless ($string =~ s,<@> *QQQ,at w/ QQQ,) { 
    $string =~ s,<@> *,at w/o,; 
} 

Sie müssen immer mit dem * quantifier vorsichtig sein. Da es null oder mehr Male übereinstimmt, kann es auch der leeren Zeichenfolge entsprechen, was im Grunde bedeutet: Es kann jede Stelle in einer beliebigen Zeichenfolge übereinstimmen.

Eine negative Look-Around-Assertion hat eine ähnliche Qualität in dem Sinne, dass sie nur eine einzige Sache finden muss, die sich unterscheidet, um zu passen. In diesem Fall entspricht es dem Teil "<@> " als <@> + kein Leerzeichen + Leerzeichen, wobei Leerzeichen natürlich "nicht" QQQ ist. Sie befinden sich mehr oder weniger in einer logischen Sackgasse, weil der * Quantifizierer und das negative Look-ahead sich gegenseitig widersprechen.

Ich glaube, der richtige Weg, dies zu lösen, ist die Regexe zu trennen, wie ich oben gezeigt habe. Es hat keinen Sinn, die Möglichkeit zuzulassen, dass beide Regexes ausgeführt werden.

Für theoretische Zwecke, eine Arbeits Regex, die beide Anzahl der Leerzeichen erlaubt, und eine negative Vorausschau müsste verankert werden. Ähnlich wie Mark Reed hat sich gezeigt. Dieser könnte der einfachste sein.

Der Unterschied ist, dass jetzt die Leerzeichen und Qs miteinander verankert sind, während vorher sie separat übereinstimmen konnten. Um nach Hause zu dem Punkt des * quantifier zu fahren, und auch ein kleines Problem der Entfernung von zusätzlichen Räumen zu lösen, können Sie verwenden:

<@> *(?! *QQQ) 

Dies funktioniert, weil entweder der quantifiers kann die leere Zeichenkette übereinstimmen. Theoretisch können Sie so viele hinzufügen, wie Sie möchten, und es wird keinen Unterschied machen (außer in der Leistung): / * * * * * * */ ist funktionell äquivalent zu / */. Der Unterschied besteht darin, dass Leerzeichen, die mit Qs kombiniert sind, möglicherweise nicht vorhanden sind.

+0

+1 für detaillierte Erklärung von '*' – flies

4

Die Regex-Engine wird zurückverfolgt, bis sie eine Übereinstimmung findet oder bis das Finden einer Übereinstimmung unmöglich ist. In diesem Fall wurde die folgende Übereinstimmung gefunden:

      +--------------- Matches "<@>". 
         | +----------- Matches "" (empty string). 
         | |  +--- Doesn't match " QQQ". 
         | |  | 
         --- ---- --- 
'something <@> QQQ' =~ /<@> [ ]* (?!QQQ)/x 

Alles, was Sie tun müssen, ist Dinge zu mischen. Ersetzen

/<@>[ ]*(?!QQQ)/ 

mit

/<@>(?![ ]*QQQ)/ 

Oder man kann es machen, so dass die Regex nur alle Felder übereinstimmen:

/<@>[ ]*+(?!QQQ)/ 
/<@>[ ]*(?![ ]|QQQ)/ 
/<@>[ ]*(?![ ])(?!QQQ)/ 

PS — Spaces sind schwer zu sehen, so dass ich [ ] um sie sichtbarer zu machen. Es wird sowieso weg optimiert.

+0

die Ergänzung von '+' behebt das Spiel, aber ich kann nicht sagen, warum. – flies

+0

warten Sie, ich denke, ich habe es. '[] * +' stellt sicher, dass alle verfügbaren Leerzeichen gegriffen werden, auch wenn es das Match unterbricht, während '[] *' so viele wie möglich aufnehmen kann, ohne das Match zu unterbrechen. – flies

+0

@flies, Weil '" "= ~/* + /' kann nur '" "' zusammenbringen. Es wird nicht zurückverfolgt, um mit "" übereinzustimmen, so dass es das Match '/ * /' nicht mehr finden kann. – ikegami

Verwandte Themen