2012-04-27 8 views
7

Tengo el (lo que creo que es) afirmación de búsqueda negativa hacia delante<@> *(?!QQQ) que espero coincidir si la cadena de prueba es un <@> seguido de cualquier número de espacios (cero incluidos) y luego no seguido de QQQ.afirmación de búsqueda negativa hacia delante con el modificador * en Perl

Sin embargo, si la cadena probada es <@> QQQ, la expresión regular coincide.

No veo por qué este es el caso y agradecería cualquier ayuda en este asunto.

Aquí es un script de prueba

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

Esto imprime

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 

Y yo habría esperado que la primera línea que se something <@> QQQ --> something at w/ QQQ.

Respuesta

10

Coincide porque cero está incluido en "cualquier número". Entonces, ningún espacio, seguido de un espacio, coincide con "cualquier cantidad de espacios no seguidos por una Q".

Debe agregar otra afirmación de búsqueda anticipada de que lo primero después de sus espacios no es un espacio en sí mismo. Prueba esto (no probado):

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

ETA Nota al margen: El cambio del cuantificador a + habría ayudado sólo cuando hay exactamente un espacio; en el caso general, la expresión regular siempre puede tomar un espacio menos y, por lo tanto, tener éxito. Regexes quiere coincidir, e inclinarse hacia atrás para hacerlo de cualquier manera posible. Todas las demás consideraciones (la más a la izquierda, la más larga, etc.) quedan relegadas; si puede coincidir con más de una, determinan qué camino se elige. Pero igualar siempre gana sin igualar.

+3

'(? = \ S)' debe ser '(? = [^])' (En caso de que el siguiente carácter sea una pestaña). En realidad, debería ser '(?!)' (En caso de que sea el final de la cadena). – ikegami

+0

Gracias por la captura y edición, @ikegami. –

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

Uno de sus problemas aquí es que está viendo las dos expresiones rectas por separado. Primero solicite reemplazar la cadena sin QQQ, y luego reemplazar la cadena con QQQ. En realidad, esto es verificar lo mismo dos veces, en cierto sentido. Por ejemplo: if (X==0) { ... } elsif (X!=0) { ... }. En otras palabras, el código puede ser mejor escrito:

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

Siempre hay que tener cuidado con el * cuantificador. Dado que coincide con cero o más veces, también puede coincidir con la cadena vacía, lo que básicamente significa que puede coincidir con cualquier lugar de cualquier cadena.

Una afirmación de búsqueda negativa tiene una calidad similar, en el sentido de que solo necesita encontrar una sola cosa que difiera para coincidir. En este caso, coincide con la parte "<@> " como <@> + sin espacio + espacio, donde el espacio es, por supuesto, "no" QQQ. Estás más o menos en un callejón sin salida lógico aquí, porque el cuantificador * y el contador negativo de anticipación entre sí.

Creo que la forma correcta de resolver esto es separar las expresiones regulares, como mostré anteriormente. No tiene sentido permitir la posibilidad de que se ejecuten ambas expresiones regulares.

Sin embargo, para fines teóricos, una expresión regular en funcionamiento que permita tanto cualquier cantidad de espacios, y una mirada negativa hacia adelante necesitarían anclarse. Al igual que Mark Reed ha demostrado. Este podría ser el más simple.

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

La diferencia es que ahora los espacios y Qs están ancladas entre sí, mientras que antes de que pudieran coincidir por separado. Para pasar el mensaje de la * cuantificador, y también resolver un problema menor en eliminar los espacios adicionales, puede utilizar:

<@> *(?! *QQQ) 

Esto funciona porque ninguno de los cuantificadores puede coincidir con la cadena vacía. Teóricamente, puede agregar tantas de ellas como desee, y no hará ninguna diferencia (excepto en el rendimiento): / * * * * * * */ es funcionalmente equivalente a / */. La diferencia aquí es que los espacios combinados con Qs pueden no existir.

+0

+1 para una explicación detallada de '*' – flies

4

El motor regex retrocederá hasta que encuentre una coincidencia, o hasta que encuentre una coincidencia imposible. En este caso, encontró la siguiente coincidencia:

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

Todo lo que necesita hacer es mezclar las cosas. Reemplazar

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

con

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

O puede que sea así, el texto sólo igualará todos los espacios:

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

PS — espacios son difíciles de ver, por lo que utilizan [ ] para hacerlos más visibles. Se optimiza de todos modos.

+0

la adición de '+' corrige la coincidencia, pero no puedo decir por qué. – flies

+0

espera, creo que lo tengo. '[] * +' asegura que todos los espacios disponibles sean capturados aunque rompa la coincidencia, mientras que '[] *' captará todos los que puedan sin romper la coincidencia. – flies

+0

@ moscas, porque '" "= ~/* + /' solo puede coincidir con '" "'. No retrocederá para hacer coincidir '" "', por lo que ya no podrá encontrar la coincidencia '/ * /'. – ikegami

Cuestiones relacionadas