Acciones

Relación 22 Sol

De Informática de 1º de Matemáticas [Curso 2021-22, Grupo 3]

-- I1M 2021-22: Relación 22
-- Cálculo de la distancia de Levenshtein
-- Departamento de Ciencias de la Computación e Inteligencia Artificial
-- Universidad de Sevilla
-- ============================================================================

-- ============================================================================
-- Librerías auxiliares
-- ============================================================================

import qualified Data.Array as A
import qualified Data.Matrix as M

type Matriz a = A.Array (Int,Int) a

-- ============================================================================
--
-- ENUNCIADO:
--
-- Dadas dos cadenas de caracteres X e Y se define la distancia de edición o
-- distancia de Levenshtein como el mínimo número de operaciones básicas de
-- edición para transformar X en Y.
--
-- Las operaciones básicas de edición son:
-- 1. Añadir un caracter a X.
-- 2. Eliminar un caracter a X.
-- 3. Sustituir un caracter de X por otro.
--
-- Por ejemplo, si queremos convertir CENA en CAMA podemos eliminar los tres
-- últimos caracteres de CENA para llegar a C y después añadir los tres
-- caracteres AMA para llegar a CAMA. Eso serían un total de 6 operaciones
-- básicas de edición.
-- Pero es mejor sustituir la E por una A y la N por una M para obtener la
-- cadena destino CAMA con solo dos operaciones básicas de edición.
-- Se pretende calcular este número mínimo de operaciones.
--
-- Para ello se va a hacer uso de la técnica de Programación Dinámica usando
-- como estructura auxiliar una matriz M con índices por filas que van desde 0
-- hasta la longitud de la cadena X (lx), y con índices por columnas que van
-- desde 0 hasta la longitud de la cadena Y (ly). En nuestro ejemplo
-- lx = ly = 4.
--
-- En el ejemplo anterior la matriz quedaría dimensionada así:
--
--          0         1         2         3         4
--     * ------- * ------- * ------- * ------- * ------- *
--     |         |         |         |         |         |
--  0  |         |         |         |         |         |
--     |         |         |         |         |         |
--     * ------- * ------- * ------- * ------- * ------- *
--     |         |         |         |         |         |
--  1  |         |         |         |         |         |
--     |         |         |         |         |         |
--     * ------- * ------- * ------- * ------- * ------- *
--     |         |         |         |         |         |
--  2  |         |         |         |         |         |
--     |         |         |         |         |         |
--     * ------- * ------- * ------- * ------- * ------- *
--     |         |         |         |         |         |
--  3  |         |         |         |         |         |
--     |         |         |         |         |         |
--     * ------- * ------- * ------- * ------- * ------- *
--     |         |         |         |         |         |
--  4  |         |         |         |         |         |
--     |         |         |         |         |         |
--     * ------- * ------- * ------- * ------- * ------- *
--
-- La definición de cada posición de la matriz sería la siguiente:
-- M(i,j) es el mínimo número de operaciones básicas de edición para transformar
-- la subcadena X hasta el caracter i-ésimo, en la subcadena Y hasta el caracter
-- j-ésimo.
--
-- Así:
-- * M(0,0) sería 0 ya que ambas subcadenas están vacías.
-- * M(0,1) sería 1 ya que para transformar la cadena vacía en "C" hay que
--   añadir 1 caracter.
-- * M(2,2) sería 1 ya que para transformar la cadena "CE" en "CA" hace falta
--   sustituir un caracter.
-- * M(lx,ly) tendríamos la solución final al problema.
--
-- El ejemplo anterior terminaría con la siguiente matriz:
--
--                      C         A         M         A
--            0         1         2         3         4
--       * ------- * ------- * ------- * ------- * ------- *
--       |         |         |         |         |         |
--    0  |    0    |    1    |    2    |    3    |    4    |
--       |         |         |         |         |         |
--       * ------- * ------- * ------- * ------- * ------- *
--       |         |         |         |         |         |
--  C 1  |    1    |    0    |    1    |    2    |    3    |
--       |         |         |         |         |         |
--       * ------- * ------- * ------- * ------- * ------- *
--       |         |         |         |         |         |
--  E 2  |    2    |    1    |    1    |    2    |    3    |
--       |         |         |         |         |         |
--       * ------- * ------- * ------- * ------- * ------- *
--       |         |         |         |         |         |
--  N 3  |    3    |    2    |    2    |    2    |    3    |
--       |         |         |         |         |         |
--       * ------- * ------- * ------- * ------- * ------- *
--       |         |         |         |         |         |
--  A 4  |    4    |    3    |    2    |    3    |    2    |
--       |         |         |         |         |         |
--       * ------- * ------- * ------- * ------- * ------- *

-- Pasos para resolver el Problema
-- 1. Plantee una ecuación en recurrencia que permita resolver este problema.
--    1.1. Preste atención a los casos base.
--    1.2. Resuelva el caso recursivo atendiendo a la definición y a los casos
--         particulares que hay en el ejemplo
-- 2. Implemente la función Haskell que construya la matriz anterior y devuelva
--    el elemento en la posición inferior derecha. Para ello use la signatura:
-- distanciaLevenshtein :: String -> String -> Int
--    Como ejemplo de la llamada anterior podríamos hacer:
-- distanciaLevenshtein "CENA" "CAMA" == 2
--
-- ============================================================================

distanciaLevenshtein :: String -> String -> Int
distanciaLevenshtein x y = m A.! (lx,ly)
  where lx = length x
        ly = length y
        m = A.listArray ((0,0),(lx,ly)) [ponElem i j | i <- [0..lx],
                                                       j <- [0..ly]]
        ponElem i j | i == 0 = j
                    | j == 0 = i
                    | x !! (i-1) == y !! (j-1) = m A.! ((i-1),(j-1))
                    | otherwise = 1 + (min izquierda (min arriba diagonal))
          where izquierda = m A.! ((i),(j-1))
                arriba    = m A.! ((i-1),(j))
                diagonal  = m A.! ((i-1),(j-1)) 
        
-- ============================================================================

data Mat = Mat [[String]] Int

instance Show Mat where
  show (Mat x m) = "/ " ++ (showRow (x !! 0) m) ++ "\\\n" ++
                   (showIntermediateRows x 1 m) ++
                   "\\ " ++ (showRow (x !! (length x - 1)) m) ++ "/"

showRow :: [String] -> Int -> String
showRow [] m = []
showRow (x:xs) m = (replicate (m - (length x)) ' ') ++ x ++ " " ++ (showRow xs m)

showIntermediateRows :: [[String]] -> Int -> Int -> String
showIntermediateRows xxs i m | i >= length xxs - 1 = []
                             | otherwise = "| " ++ showRow (xxs !! i) m ++ "|\n" ++
                               showIntermediateRows xxs (i+1) m

printMatrix :: Show a => M.Matrix a -> Mat
printMatrix m = Mat (M.toLists nm) mx
  where nm = (M.mapPos (\ (i,j) e -> show e) m)
        mx = maximum (map (length) (M.toList nm))