Ir al contenido principal

Distancia de Hamming


La distancia de Hamming entre dos listas es el número de posiciones en que los correspondientes elementos son distintos. Por ejemplo, la distancia de Hamming entre "roma" y "loba" es 2 (porque hay 2 posiciones en las que los elementos correspondientes son distintos: la 1ª y la 3ª).

Definir la función

distancia :: Eq a => [a] -> [a] -> Int

tal que (distancia xs ys) es la distancia de Hamming entre xs e ys. Por ejemplo,

distancia "romano" "comino"  ==  2
distancia "romano" "camino"  ==  3
distancia "roma"   "comino"  ==  2
distancia "roma"   "camino"  ==  3
distancia "romano" "ron"     ==  1
distancia "romano" "cama"    ==  2
distancia "romano" "rama"    ==  1

Comprobar con QuickCheck si la distancia de Hamming tiene la siguiente propiedad

distancia(xs,ys) = 0 si, y sólo si, xs = ys

y, en el caso de que no se verifique, modificar ligeramente propiedad para obtener una condición necesaria y suficiente de distancia(xs,ys) = 0.


Soluciones

import Data.List (foldl')
import Test.Hspec (Spec, describe, hspec, it, shouldBe)
import Test.QuickCheck

-- 1ª solución
-- ===========

distancia1 :: Eq a => [a] -> [a] -> Int
distancia1 xs ys =
  sum [1 | (x,y) <- zip xs ys, x /= y]

-- 2ª solución
-- ===========

distancia2 :: Eq a => [a] -> [a] -> Int
distancia2 [] _  = 0
distancia2 _  [] = 0
distancia2 (x:xs) (y:ys)
  | x /= y    = 1 + distancia2 xs ys
  | otherwise = distancia2 xs ys

-- 3ª solución
-- ===========

distancia3 :: Eq a => [a] -> [a] -> Int
distancia3 xs ys =
  foldl' (\n (x, y) -> if x /= y then n + 1 else n) 0 (zip xs ys)

-- 4ª solución
-- ===========

distancia4 :: Eq a => [a] -> [a] -> Int
distancia4 xs ys =
  length (filter (uncurry (/=)) (zip xs ys))

-- Verificación
-- ============

verifica :: IO ()
verifica = hspec spec

specG :: (String -> String -> Int) -> Spec
specG distancia = do
  it "e1" $
    distancia "romano" "comino"  `shouldBe`  2
  it "e2" $
    distancia "romano" "camino"  `shouldBe`  3
  it "e3" $
    distancia "roma"   "comino"  `shouldBe`  2
  it "e4" $
    distancia "roma"   "camino"  `shouldBe`  3
  it "e5" $
    distancia "romano" "ron"     `shouldBe`  1
  it "e6" $
    distancia "romano" "cama"    `shouldBe`  2
  it "e7" $
    distancia "romano" "rama"    `shouldBe`  1

spec :: Spec
spec = do
  describe "def. 1" $ specG distancia1
  describe "def. 2" $ specG distancia2
  describe "def. 3" $ specG distancia3
  describe "def. 4" $ specG distancia4

-- La verificación es
--    λ> verifica
--    35 examples, 0 failures

-- Comprobación de equivalencia
-- ============================

-- La propiedad es
prop_distancia :: String -> String -> Bool
prop_distancia xs ys =
  all (== distancia1 xs ys)
      [distancia2 xs ys,
       distancia3 xs ys,
       distancia4 xs ys]

-- La comprobación es
--    λ> quickCheck prop_distancia
--    +++ OK, passed 100 tests.

-- Comparación de eficiencia
-- =========================

-- La comparación es
--    λ> distancia1 [1..10^7] [1,3..10^7]
--    4999999
--    (1.92 secs, 1,960,602,616 bytes)
--    λ> distancia2 [1..10^7] [1,3..10^7]
--    4999999
--    (3.87 secs, 2,012,653,888 bytes)
--    λ> distancia3 [1..10^7] [1,3..10^7]
--    4999999
--    (1.81 secs, 2,120,602,496 bytes)
--    λ> distancia4 [1..10^7] [1,3..10^7]
--    4999999
--    (0.42 secs, 1,680,602,600 bytes)

-- Comprobación de la propiedad
-- ============================

-- La propiedad es
prop_distancia1 :: [Int] -> [Int] -> Bool
prop_distancia1 xs ys =
  (distancia1 xs ys == 0) == (xs == ys)

-- La comprobación es
--    λ> quickCheck prop_distancia1
--    *** Failed! Falsifiable (after 2 tests and 1 shrink):
--    []
--    [1]
-- En efecto,
--    λ> distancia [] [1] == 0
--    True
--    λ> [] == [1]
--    False

-- La primera modificación es restringir la propiedad a lista de igual
-- longitud:
prop_distancia2 :: [Int] -> [Int] -> Property
prop_distancia2 xs ys =
  length xs == length ys ==>
  (distancia1 xs ys == 0) == (xs == ys)

-- La comprobación es
--    λ> quickCheck prop_distancia2
--    *** Gave up! Passed only 33 tests.

-- Nota. La propiedad se verifica, pero al ser la condición demasiado
-- restringida sólo 33 de los casos la cumple.

-- La segunda restricción es limitar las listas a la longitud de la más
-- corta:
prop_distancia3 :: [Int] -> [Int] -> Bool
prop_distancia3 xs ys =
  (distancia1 xs ys == 0) == (take n xs == take n ys)
  where n = min (length xs) (length ys)

-- La comprobación es
--    λ> quickCheck prop_distancia3
--    +++ OK, passed 100 tests.