viernes, 5 de octubre de 2012

Distancia Mínima de Edición

A veces es importante identificar cuanto se parece una palabra a otra, es decir: cuan cerca está una palabra de otra .
Supongamos un corrector ortográfico, si el usuario tipea : "aselrgia"
¿Cuál es la palabra más cercana: alergiaalegría, alergias?

Distancia Mínima de Edición (Edit Distance)

La distancia mínima de edición entre dos strings es la menor cantidad de operaciones de edición:
  • Inserción
  • Borrado
  • Sustitución
necesarias para convertir un string en otro.


Ejemplo: "intención" y "ejecución"

I  N  T  E  *  N  C  I  Ó  N
|  |  |  |  |  |  |  |  |  |
*  E  J  E  C  U  C  I  Ó  N
b  s  s     i  s => (b: borrado, s: sustitución e i: inserción)

Distancia: 5 (si cada operación cuesta 1)

¿Por qué aparecen unos * en medio de la palabra?

Para encontrar el menor número de operaciones de edición es importante que los strings estén alineados de la forma más conveniente. Esto se ve muy claro en el siguiente ejemplo: "mente" y "sutilmente"

*  *  *  *  *  M  E  N  T  E
|  |  |  |  |  |  |  |  |  |
S  U  T  I  L  M  E  N  T  E
i  i  i  i  i
Distancia: 5 (si cada operación cuesta 1)

Si alineamos las palabras para que coincidan las partes iguales nos ahorramos 5 operaciones.

Algoritmo de Levenshtein para encontrar la Distancia Mínima de Edición

Este algoritmo considera que las operaciones de borrado e inserción tienen un costo de 1 mientras que la de sustitución tiene un costo de 2 ya que la considera como compuesta por una operación de borrado más una de inserción. (esta diferencia es Levenshtein )
  • Inserción   => 1
  • Borrado    => 1
  • Sustitución => 2
Inicialización
D(i,0) = i
D(0,j) = j

Relación recurrente para el computo de la distancia

para cada i de 1 a M
    para cada j de 1 a N
                                   {  D(i-1, j) +1
                D(i,j) = min {  D(i, j-1) +1
                                   { D(i-1,j-1) +  { 0 si X(i) = Y(j) ó 2 si X(i) != Y(j) }

Finalización
            La distancia es D(N,M)

Explicación
       La distancia la calculamos recursivamente, donde la distancia entre la palabra A y la palabra B es la suma de la operación final (inserción,borrado o sustitución) más la distancia entre la palabra A-1 letra y la palabra B, o bien entre A y B -1 letra o bien entre la palabra A-1 letra y B-1 letra. Y así sucesivamente hasta llegar a una sola letra en donde las distancias son las obvias que seteamos en la inicialización.

Este algoritmo se puede realizar también de forma iterativa, utilizando una tabla y muchas veces es útil hacerlo así porque nos interesa seguir la pista de las operaciones realizadas. 
    
Dejo un pseudo código más legible para la versión iterativa:

Inicialización

matrix = new matrix [word1.length + 1][word2.length + 1] //definimos una matriz de enteros
 for(i=0;  i<=word1.length; i++)     matrix [i][0]=i; //inicializamos las distancias de la fila 0, 
 for(j=0; j<=word2.length; j++ )    matrix [0][j]=j; // inicializamos las distancias de la columna 0

Computo

 for(i=1; i<=word1.length; i++)
           for(j=1;j<=word2.length; j++)
           
            if (word1[i-1] == word2[j-1]) factor = 0; //son iguales no hay costo de operación
else  factor = 2;  //sustitucion

            matrix[i][j] = MIN(matrix[i-1][j]+1, matrix[i][j-1]+1, matrix[i-1][j-1]+factor);

Finalización

            return matrix[word1.length][word2.length];


Ejemplo:
Finalizado quedaría así:


4 comentarios:

  1. Hola Juan, estoy tomando este curso como offline de coursera y voy por este punto. Tengo las siguientes inquietudes que agradezco me ayudes a resolver.

    1) teniendo "word1=intención", cuando "i=5": "word1[i-1]=inte"? o "word1[i-1]=e"? Suponiendo que el índice empieza en 1 y no en 0.
    2) Mi principal "quebradero de cabeza" está cuando "i=4" y "j=1". Según el cálculo para la celda "matriz[i][j]" el mínimo sería: "matrix[3][1]=4" + 1 (es decir 5); "matrix[4][0]=4" + 1 (es decir 5) o (aquí va mi confusión) -> como "word1[3]=int" es diferente que "word2[0]=(nulo)", el costo sería (factor, en tu pseudo código) 2, por lo que "matrix[3][0]=3" + 2 (factor) // entonces, "matrix[4][1] = MIN (5, 5, 5)"; pero tanto en el material de coursera como en esta publicación veo esta celda con valor de "3".

    Principalmente la segunda inquietud es la que me ha "roto" la cabeza.

    Gracias por la publicación y por las respuestas a mis inquietudes.

    Saludos!!!

    ResponderEliminar
    Respuestas
    1. Hola... (no sé cual es tu nombre :-/),
      Intentaré responder a ambas preguntas:
      1) Suponiendo que el primer elemento sea 1, entonces si word1="intención" e i=5, word1[4]="e" . Sin embargo en todos los lenguajes de programación el primer elemento es siempre el 0. Ojo con eso, hay que tenerlo en cuenta. En un código real sería word1[4]="n".
      2) Teniendo en cuenta lo anterior, vas a ver que el "costo" o " factor" es cero. Porque word1[3]="e" y word2[0]="e".
      ¿Ahora, por qué esto tiene sentido? Tratemos de entender que quiere hacer el algoritmo cuando está analizando este posible camino que coincide con i=4 y j=1. Hasta i=4 sería lo mismo que analizar la palabra: "inte" y hasta j=1 sería lo mismo que tener la palabra: "e". Esta distancia se la puede calcular a ojo. Conviene alinear la "e" de "inte" con la otra "e" e insertar las 3 letras que faltan:
      i n t e
      * * * e
      Lo que me da un costo igual a 3, esto es lo que el algoritmo está calculando.

      Espero haber sido claro y ojalá te sirva la explicación. Cualquier inquietud no dudes en comentarla. Gracias por tu interés en este blog.

      Eliminar