2012-04-27 6 views
7

ho la (quello che credo di essere) negativo lookahead<@> *(?!QQQ) che mi aspetto per abbinare se la stringa testato è un <@> seguito da qualsiasi numero di spazi (a zero compreso) e quindi non seguito da QQQ.negativo asserzione che guarda avanti con l'* modificatore in Perl

Tuttavia, se la stringa testata è <@> QQQ, l'espressione regolare corrisponde.

Non riesco a capire perché questo è il caso e gradirei qualsiasi aiuto in merito.

Ecco uno script di test

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; 
} 

Questo stampa

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 

E mi sarei aspettato la prima linea ad essere something <@> QQQ --> something at w/ QQQ.

risposta

10

Corrisponde perché lo zero è incluso in "qualsiasi numero". Quindi nessuno spazio, seguito da uno spazio, corrisponde a "qualsiasi numero di spazi non seguiti da una Q".

Dovresti aggiungere un'altra affermazione lookahead che la prima cosa dopo i tuoi spazi non è di per sé uno spazio. Prova questo (non testata):

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

ETA Nota a margine: la modifica della quantificatore a + avrebbe aiutato solo quando c'è esattamente uno spazio; nel caso generale, la regex può sempre prendere uno spazio in meno e quindi avere successo. I regex vogliono abbinare e si piegheranno all'indietro per farlo in ogni modo possibile. Tutte le altre considerazioni (più a sinistra, più lunghe, ecc.) Passano in secondo piano - se può corrispondere a più di un modo, determinano il modo in cui viene scelto. Ma l'abbinamento vince sempre sul non abbinamento.

+3

'(? = \ S)' dovrebbe essere '(? = [^])' (Nel caso in cui il carattere successivo sia una scheda). In realtà dovrebbe essere '(?!)' (Nel caso sia la fine della stringa). – ikegami

+0

Grazie per la cattura e modifica, @ikegami. –

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

Uno dei tuoi problemi qui è che stai visualizzando le due regex separatamente. Prima è necessario sostituire la stringa senza QQQ e quindi sostituire la stringa con QQQ. Questo in realtà sta controllando la stessa cosa due volte, in un certo senso. Ad esempio: if (X==0) { ... } elsif (X!=0) { ... }. In altre parole, il codice può essere scritto meglio:

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

devi sempre stare attenti con il * quantificatore. Poiché corrisponde a zero o più volte, può anche corrispondere alla stringa vuota, che in pratica significa: può corrispondere a qualsiasi posizione in qualsiasi stringa.

Un'asserzione di ricerca negativa ha una qualità simile, nel senso che è necessario trovare solo una singola cosa che differisce per corrispondere. In questo caso, corrisponde alla parte "<@> " come <@> + senza spazio + spazio, dove lo spazio è ovviamente "non" QQQ. Sei più o meno in una situazione di stallo logico qui, perché il quantificatore * e il look-ahead negativo si contrappongono.

Credo che il modo corretto per risolvere questo problema sia separare le espressioni regolari, come ho mostrato sopra. Non ha senso consentire la possibilità di esecuzione di entrambe le espressioni regex.

Tuttavia, a fini teorici, una regex di lavoro che consenta di avere un numero qualsiasi di spazi, e con un look-ahead negativo, deve essere ancorata. Molto simile a Mark Reed ha mostrato. Questo potrebbe essere il più semplice.

<@>(?! *QQQ)  # Add the spaces to the look-ahead 

La differenza è che ora gli spazi e le Q sono ancorati l'uno all'altro, mentre prima potevano essere abbinati separatamente. Per guidare a casa il punto del * quantificatore, e anche risolvere un problema minore di rimozione di spazi aggiuntivi, è possibile utilizzare:

<@> *(?! *QQQ) 

Ciò funzionerà perché uno dei quantificatori possono corrispondere alla stringa vuota. In teoria, è possibile aggiungerne quanti ne volete e non farà alcuna differenza (tranne che nelle prestazioni): / * * * * * * */ è funzionalmente equivalente a / */. La differenza qui è che gli spazi combinati con Q possono non esistere.

+0

+1 per la spiegazione dettagliata di '*' – flies

4

Il motore regex tornerà indietro fino a quando non trova una corrispondenza o finché non è impossibile trovare una corrispondenza. In questo caso, ha trovato la seguente corrispondenza:

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

Tutto ciò che devi fare è mescolare le cose. Sostituire

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

con

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

Oppure si può fare in modo che il regex corrisponderà solo tutti gli spazi:

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

PS — Gli spazi sono difficili da vedere, per cui uso [ ] per renderli più visibili. Viene comunque ottimizzato via.

+0

l'aggiunta di '+' corregge la corrispondenza, ma non so perché. – flies

+0

aspetta, penso di averlo capito. '[] * +' assicura che tutti gli spazi disponibili vengano afferrati anche se interrompe la corrispondenza, mentre '[] *' catturerà il maggior numero possibile senza interrompere la corrispondenza. – flies

+0

@flies, perché '" "= ~/* + /' può corrispondere solo a "" "'. Non eseguirà il backtrack in modo che corrisponda a "" ", quindi non può più trovare la corrispondenza'/*/'. – ikegami

Problemi correlati