Convertir expresiones regulares en NFA (mínimo) que acepten el mismo lenguaje es fácil con algoritmos estándar, por ejemplo, el algoritmo de Thompson . Sin embargo, la otra dirección parece ser más tediosa, y a veces las expresiones resultantes son desordenadas.
¿Qué algoritmos existen para convertir NFA en expresiones regulares equivalentes? ¿Hay ventajas con respecto a la complejidad del tiempo o el tamaño del resultado?
Se supone que esta es una pregunta de referencia. Incluya una descripción general de su método, así como un ejemplo no trivial.
Respuestas:
Existen varios métodos para realizar la conversión de autómatas finitos a expresiones regulares. Aquí describiré el que generalmente se enseña en la escuela y que es muy visual. Creo que es el más usado en la práctica. Sin embargo, escribir el algoritmo no es una buena idea.
Método de eliminación del estado
Este algoritmo se trata de manejar el gráfico del autómata y, por lo tanto, no es muy adecuado para los algoritmos, ya que necesita primitivas de gráficos como ... la eliminación del estado. Lo describiré usando primitivas de nivel superior.
La idea clave
La idea es considerar expresiones regulares en los bordes y luego eliminar estados intermedios mientras se mantienen las etiquetas de los bordes consistentes.
El patrón principal se puede ver a continuación en las figuras. El primero tiene etiquetas entre que son expresiones regulares e , f , g , h , i y queremos eliminar q .p,q,r e,f,g,h,i q
Una vez eliminados, componimos juntos (conservando los otros bordes entre p y r, pero esto no se muestra en esto):e,f,g,h,i p r
Ejemplo
Usando el mismo ejemplo que en la respuesta de Raphael :
eliminamos sucesivamente :q2
y luego :q3
entonces todavía tenemos que aplicar una estrella en la expresión de a q 1 . En este caso, el estado final también es inicial, por lo que realmente solo necesitamos agregar una estrella:q1 q1
Algoritmo
L[i,j]
es la expresión regular del lenguaje de a q j . Primero, eliminamos todos los bordes múltiples:Ahora, la eliminación del estado. Supongamos que queremos eliminar el estado :qk
star(ε)=ε
e.ε=e
∅+e=e
∅.e=∅
Ahora, ¿cómo usarlo
remove(k)
? No debe eliminar a la ligera los estados finales o iniciales, de lo contrario, perderá partes del lenguaje.Si solo tiene un estado final y un estado inicial , la expresión final es:q sqf qs
Si tiene varios estados finales (o incluso estados iniciales), entonces no hay una forma simple de fusionarlos, aparte de aplicar el método de cierre transitivo. Por lo general, esto no es un problema a mano, pero es incómodo al escribir el algoritmo. Una solución mucho más simple es enumerar todos los pares y ejecutar el algoritmo en el gráfico (ya eliminado) para obtener todas las expresiones suponiendo que es el único estado inicial es el único final estado, luego haciendo la unión de todos .e s , f s f e s , f(s,f) es,f s f es,f
Esto, y el hecho de que esto está modificando los lenguajes más dinámicamente que el primer método lo hace más propenso a errores al programar. Sugiero usar cualquier otro método.
Contras
Hay muchos casos en este algoritmo, por ejemplo, para elegir qué nodo debemos eliminar, el número de estados finales al final, el hecho de que un estado final también puede ser inicial, etc.
Tenga en cuenta que ahora que el algoritmo está escrito, esto se parece mucho al método de cierre transitivo. Solo el contexto del uso es diferente. No recomiendo implementar el algoritmo, pero usar el método para hacerlo a mano es una buena idea.
fuente
ab
Método
El mejor método que he visto es uno que expresa el autómata como sistema de ecuaciones de lenguajes (regulares) que se pueden resolver. Es particularmente agradable, ya que parece producir expresiones más concisas que otros métodos.
Deje un NFA sin -transitions. Para cada estado , crea la ecuaciónε q iA=(Q,Σ,δ,q0,F) ε qi
donde es el conjunto de estados finales y significa que hay una transición de a etiquetada con . Si lee como o (dependiendo de su definición de expresión regular), verá que esta es una ecuación de expresiones regulares.F qi→aqj qi qj a ∪ + ∣
Para resolver el sistema necesita asociatividad y distributividad de y (concatenación de cadenas), conmutatividad de y Lema de Arden ¹:∪ ⋅ ∪
La solución es un conjunto de expresiones regulares , una para cada estado . describe exactamente esas palabras que pueden ser aceptadas por cuando se inicia en ; por lo tanto, (si es el estado inicial) es la expresión deseada.Qi qi Qi A qi Q0 q0
Ejemplo
En aras de la claridad, denotamos conjuntos singleton por su elemento, es decir, . El ejemplo se debe a Georg Zetzsche.a={a}
Considere este NFA:
[ fuente ]
El sistema de ecuaciones correspondiente es:
Ahora conecta la tercera ecuación en la segunda:
Para el último paso, aplicamos el Lema de Arden con , y . Tenga en cuenta que los tres idiomas son regulares y , lo que nos permite aplicar el lema. Ahora conectamos este resultado en la primera ecuación:L=Q1 U=ab V=(b∪aa)⋅Q0 ε∉U={ab}
Por lo tanto, hemos encontrado una expresión regular para el lenguaje aceptado por el autómata anterior, a saber
Tenga en cuenta que es bastante sucinto (compárese con el resultado de otros métodos) pero no está determinado de forma exclusiva; resolver el sistema de ecuaciones con una secuencia diferente de manipulaciones conduce a otro - ¡equivalente! - expresiones.
fuente
maybe_union/2
predicado podría usar más trabajo (especialmente wrt eliminando el prefijo común) para hacer expresiones regulares más ordenadas. Otra forma de ver este método es entenderlo como una traducción de la expresión regular a la gramática lineal derecha, donde los lenguajes con unificación similar a Prolog o coincidencia de patrones similares a ML hacen un muy buen transductor, por lo que no es solo un bolígrafo y papel algoritmo :)Método algebraico de Brzozowski
Este es el mismo método que el descrito en la respuesta de Raphael , pero desde el punto de vista de un algoritmo sistemático, y luego, de hecho, el algoritmo. Resulta fácil y natural de implementar una vez que sabes por dónde empezar. También puede ser más fácil a mano si dibujar algún autómata no es práctico por alguna razón.
Al escribir un algoritmo, debe recordar que las ecuaciones deben ser siempre lineales para que tenga una buena representación abstracta de las ecuaciones, algo que puede olvidar cuando resuelve a mano.
La idea del algoritmo.
No describiré cómo funciona, ya que está bien hecho en la respuesta de Raphael, que sugiero leer antes. En cambio, me concentro en el orden en que debe resolver las ecuaciones sin hacer demasiados cálculos adicionales o casos adicionales.
A partir de la ingeniosa solución de la regla de Arden a la ecuación del lenguaje , podemos considerar el autómata como un conjunto de ecuaciones de la forma:X=A∗B X=AX∪B
podemos resolver esto por inducción en actualizando las matrices y consecuencia. En el paso , tenemos:n Ai,j Bi,j n
y la regla de Arden nos da:
y configurando y obtenemos:B′n=A∗n,nBn A′n,i=A∗n,nAn,i
y luego podemos eliminar todas las necesidades de en el sistema configurando, para :Xn i,j<n
Cuando hemos resuelto cuando , obtenemos una ecuación como esta:Xn n=1
sin . Así obtuvimos nuestra expresión regular.A′1,i
El algoritmo
Gracias a esto, podemos construir el algoritmo. Para tener la misma convención que en la inducción anterior, diremos que el estado inicial es y que el número de estado es . Primero, la inicialización para llenar :q1 m B
y :A
y luego la resolución:
la expresión final es entonces:
Implementación
Incluso si parece un sistema de ecuaciones que parece demasiado simbólico para un algoritmo, este es adecuado para una implementación.
Aquí hay una implementación de este algoritmo en Ocaml(enlace roto) . Tenga en cuenta que, aparte de la funciónbrzozowski
, todo es imprimir o usar para el ejemplo de Raphael. Tenga en cuenta que existe una función sorprendentemente eficiente de simplificación de expresiones regularessimple_re
.fuente
Método de cierre transitivo
Este método es fácil de escribir en forma de algoritmo, pero genera expresiones regulares absurdamente grandes y no es práctico si lo hace a mano, principalmente porque esto es demasiado sistemático. Sin embargo, es una solución buena y simple para un algoritmo.
La idea clave
Deje que represente la expresión regular para las cadenas que van de a usando los estados . Sea el número de estados del autómata.Rki,j qi qj {q1,…,qk} n
Suponga que ya conoce la expresión regular de a sin el estado intermedio (excepto las extremidades), para todo . Entonces puede adivinar cómo agregar otro estado afectará la nueva expresión regular : solo cambia si tiene transiciones directas a , y se puede expresar así:Ri,j qi qj qk i,j R′i,j qk
( es y es .)R Rk−1 R′ Rk
Ejemplo
Usaremos el mismo ejemplo que en la respuesta de Raphael . Al principio, solo puedes usar las transiciones directas.
Aquí está el primer paso (tenga en cuenta que un bucle automático con una etiqueta habría transformado el primer en .a ε (ε+a)
En el segundo paso podemos usar (que se renombra en para nosotros, porque ya se usa para el propósito anterior). Veremos cómo funciona .q0 q1 R0 R1
De a : .q2 q2 R12,2=R02,2+R02,1R01,1∗R01,2=ε+bε∗a=ε+ba
¿Porqué es eso? Esto se debe a que pasar de a usando solo como estado intermedio puede realizarse permaneciendo aquí ( ) o yendo a ( ), girando allí ( ) y volviendo ( ).q2 q2 q1 ε q1 a ε∗ b
También puede calcular así y , y le dará la expresión final porque es inicial y final. Tenga en cuenta que aquí se ha hecho mucha simplificación de expresiones. De lo contrario, la primera de sería y la primera de sería .R2 R3 R31,1 1 a R0 (∅+a) a R1 ((∅+a)+ε(ε)∗a)
Algoritmo
Inicializacion:
Clausura transitiva:
Entonces la expresión final es (suponiendo que es el estado inicial):qs
Pero puedes imaginar que genera expresiones regulares feas. De hecho, puede esperar cosas como que representa el mismo lenguaje que . Tenga en cuenta que simplificar una expresión regular es útil en la práctica.a a(∅)∗+(a+(∅)∗)(ε)∗(a+∅) aa
fuente