2012-08-23 9 views
15

Me disculpo si esto se publica en alguna parte, pero mi búsqueda superficial no encontró nada.Inconsistencia entre las expresiones regulares de sed y python

Mientras que hace un poco de programación Python me di cuenta de que el siguiente comando:

re.sub("a*((ab)*)b", r"\1", "aabb") 

devuelve la cadena vacía. Pero un comando equivalente en sed:

echo "aabb" | sed "s/a*\(\(ab\)*\)b/\1/" 

devuelve ab.

Tiene sentido para mí que la directiva "a *" al comienzo de la expresión regular de python coincida con a, haciendo que "(ab) *" coincida cero veces, pero no tengo idea de cómo viene sed con ab. ¿Alguien sabe cuál es la diferencia entre los dos motores de expresiones regulares que causa esto? Creo que ambos combinan las estrellas codiciosamente de forma predeterminada, pero se me ocurrió que sed podría coincidir desde la derecha en lugar de la izquierda. Cualquier idea sería muy apreciada.

+0

alguna parte leí que "SED/awk utiliza un AFD" y "Python/Perl/Java utilizan un NFA". Cambia la semántica con alternancias (retroceso?) ... ¿podría estar relacionado? –

+1

@pst: Tal vez te estoy malinterpretando, pero parece que cualquier enfoque basado en retroceso usaría un DFA; el efecto de usar un NFA sería eliminar la necesidad de retroceder, ya que todas las ramas se examinan simultáneamente. Entonces, esperaría que Perl/Python/Java/etc. para usar un DFA. ¿Es posible que leas lo contrario de lo que escribiste: tal vez lees que sed/awk usa un NFA y Perl/Python/Java, etc. usa un DFA? – ruakh

+1

@pst: Y * explicaría * el comportamiento observado, si sed/awk utiliza un NFA, y luego elija la forma de coincidencia que proporcionó la coincidencia más larga. En este caso, permitir '\ (\ (ab \) * \)' hacer coincidir 'ab' produce una coincidencia global más larga,' aabb', y hacer coincidir la cadena vacía, ya que esto último significaría que la expresión regular como un todo solo coincidiría con 'aab'. – ruakh

Respuesta

2

Rompecabezas interesante que has construido. Según lo que he leído, los motores de expresiones regulares de python y sed están basados ​​en la biblioteca de expresiones regulares de Henry Spencer (como la de perl), que se basa en el rastreo.(Desafortunadamente no puedo encontrar el artículo en el que estoy basando esto).

De todos modos, esto es no algo que se supone que es un detalle de implementación: el comportamiento de Python va en contra del estándar POSIX, lo que requiere RE para (a) coinciden en el punto más temprana posible, y (b) que coincida con la mayor longitud posible cadena que comienza en ese punto. (Consulte man 7 regex (en Linux) para esto y mucho más.)

Para encontrar la coincidencia más larga, un motor de expresiones retrospectivas ("NFA-type") debe seguir examinando alternativas después de encontrar una coincidencia. Entonces, no es sorprendente que los implementadores reduzcan las esquinas. Obviamente, el comportamiento de Python no es conforme, ya que no puede encontrar la coincidencia más larga. De acuerdo con la página de manual de sed, sed no siempre se ajusta, "por razones de rendimiento". Pero, obviamente, este caso es correcto.

Por cierto, los comandos no son totalmente equivalentes: re.sub llevará a cabo un cambio tantas veces como sea posible, mientras sed de s/a/b/ sólo se llevará a cabo la versión que once.The sed debería haber sido:

echo "aabb" | sed "s/a*\(\(ab\)*\)b/\1/g" 

Esto explica por qué obtenemos la cadena vacía en python: la RE coincide con aab la primera vez y la b restante la segunda vez, eliminando cada parte (ya que todo coincide con a* y la final de la expresión regular). Esto se puede ver por la siguiente variante:

>>> re.sub("a*((ab)*)b", r"X\1Y", "aabb") 
'XYXY' 
+0

Perl hace lo mismo que Python en este caso: '$ perl -E '$ _ =" aabb "; s/a * ((ab) *) b/<\1>/g; print $ _, "\ n"; ''result' <><> 'por cierto. no codicioso '... s/a *? ...' resultado ''. – hynekcer

+0

¿Y qué? Plain '*' es codicioso en los tres motores. Es una coincidencia que la versión no codiciosa arroje el mismo resultado en este caso. – alexis

+0

Cool.FWIW, decirle a python que coincida una vez, para que los dos comandos sean equivalentes, no parece afectar si se usa el grupo coincidente ('re.sub (" a * ((ab) *) b "," [\ 1] "," aabb ") ->" [] b "'), pero ese es un buen punto. – maths

4

Python y sed son codiciosos por defecto, pero ... Python regex intenta evaluar de izquierda a derecha en todas las circunstancias, a pesar de que debe hacer una traza inversa al estado anterior si la rama que se intenta no puede continuar haciendo coincidir Sed regex, por el contrario, se optimizan antes de la evaluación con el fin de evitar una traza inversa innecesaria, mediante la reescritura de la expresión regular a una forma más determinista. Por lo tanto, el patrón opcional combinado "aab" probablemente se pruebe antes que la "a" simple porque se intenta primero la cadena más específica posible.

patrón Python coincide con el "aabb" cadena dos veces como "aab" "b" + (marcada entre "< >")

>>> re.sub("a*((ab)*)b", r"<\1>", "aabb") 
'<><>' 

mientras sed partidos todo el "aabb" por una sustitución:

$ echo "aabb" | sed "s/a*\(\(ab\)*\)b/<\1>/" 
<ab> 

El algoritmo de retroceso de Pyge regex se explica bien en regex howto - Repeating Things en dos párrafos introducidos por las palabras "Un ejemplo paso a paso ...". Hace IMO exactamente lo que se describe regex docs: "A medida que se explora la cadena objetivo, las RE se separan por '|' se intentan de izquierda a derecha. "

Demostración

El orden de "(| a | bis)" por cierto. "(AA | a |)" es respetado por Python

>>> re.sub("(?:|a|aa)((ab)*)b", r"<\1>", "aabb") 
'<ab>' 
>>> re.sub("(?:aa|a|)((ab)*)b", r"<\1>", "aabb") 
'<><>' 

pero esta orden es ignorado por sed dado que sed optimiza expresiones regulares. La coincidencia de "aab" + "b" se puede reproducir eliminando la opción "a" del patrón.

$ echo "aabb" | sed "s/\(\|a\|aa\)\(\(ab\)*\)b/<\2>/g" 
<ab> 
$ echo "aabb" | sed "s/\(aa\|a\|\)\(\(ab\)*\)b/<\2>/g" 
<ab> 
$ echo "aabb" | sed "s/\(aa\|\)\(\(ab\)*\)b/<\2>/g" 
<><> 

Editar: Me eliminado todo sobre DFA/NFA porque no puedo demostrarlo a partir de textos actuales.

Cuestiones relacionadas