Ir al contenido principal

Juego de bloques con letras


Para el juego de los bloques se dispone de un conjunto de bloques con una letra en cada una de sus dos caras. El objetivo del juego consiste en formar palabras sin que se pueda usar un bloque más de una vez y sin diferenciar mayúsculas de minúsculas. Por ejemplo, si se tiene tres bloques de forma que el 1º tiene las letras A y B, el 2ª la N y la O y el 3º la O y la A entonces se puede obtener la palabra ANA de dos formas: una con los bloques 1, 2 y 3 y otra con los 3, 2 y 1.

Definir la función

soluciones :: [String] -> String -> [[String]]

tal que (soluciones bs cs) es la lista de las soluciones del juego de los bloque usando los bloques bs (cada bloque es una cadena de dos letras mayúsculas) para formar la palabra cs. Por ejemplo,

λ> soluciones ["AB","NO","OA"] "ANA"
[["AB","NO","OA"],["OA","NO","AB"]]
λ> soluciones ["AB","NE","OA"] "Bea"
[["AB","NE","OA"]]
λ> soluciones ["AB","NE","OA"] "EvA"
[]
λ> soluciones ["AB","NO","OA","NC"] "ANA"
[["AB","NO","OA"],["AB","NC","OA"],["OA","NO","AB"],["OA","NC","AB"]]
λ> soluciones ["AB","NO","OA","NC"] "Anca"
[["AB","NO","NC","OA"],["OA","NO","NC","AB"]]

Soluciones

import Data.List (delete, nubBy, sort)
import Data.Char (toUpper)
import Test.Hspec (Spec, describe, hspec, it, shouldBe)
import Test.QuickCheck

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

soluciones1 :: [String] -> String -> [[String]]
soluciones1 bs cs = aux bs (map toUpper cs) []
  where
    aux :: [String] -> String -> [String] -> [[String]]
    aux _ [] _ = [[]]
    aux [] _ _ = []
    aux as (d:ds) usados =
      [a : rs | a <- as,
                a `notElem` usados,
                d `elem` a,
                rs <- aux as ds (a : usados)]

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

soluciones2 :: [String] -> String -> [[String]]
soluciones2 _ [] = [[]]
soluciones2 [] _ = []
soluciones2 bs (c:cs) =
  [b:rs | b <- bs,
          toUpper c `elem` b,
          rs <- soluciones2 (delete b bs) cs]

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

soluciones3 :: [String] -> String -> [[String]]
soluciones3 bs cs = aux bs' (map toUpper cs)
  where
    cs' = map toUpper cs
    bs' = filter (\b -> any (`elem` b) cs') bs
    aux _ [] = [[]]
    aux [] _ = []
    aux as (d:ds) =
      [a : rs | a <- as,
                d `elem` a,
                rs <- aux (delete a as) ds]

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

verifica :: IO ()
verifica = hspec spec

specG :: ([String] -> String -> [[String]]) -> Spec
specG soluciones = do
  it "e1" $
    soluciones ["AB","NO","OA"] "ANA"
    `shouldBe` [["AB","NO","OA"],["OA","NO","AB"]]
  it "e2" $
    soluciones ["AB","NE","OA"] "Bea"
    `shouldBe` [["AB","NE","OA"]]
  it "e3" $
    soluciones ["AB","NE","OA"] "EvA"
    `shouldBe` []
  it "e4" $
    soluciones ["AB","NO","OA","NC"] "ANA"
    `shouldBe` [["AB","NO","OA"],["AB","NC","OA"],["OA","NO","AB"],["OA","NC","AB"]]
  it "e5" $
    soluciones ["AB","NO","OA","NC"] "Anca"
    `shouldBe` [["AB","NO","NC","OA"],["OA","NO","NC","AB"]]

spec :: Spec
spec = do
  describe "def. 1" $ specG soluciones1
  describe "def. 2" $ specG soluciones2
  describe "def. 3" $ specG soluciones3

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

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

-- Generador de una letra mayúscula. Por ejemplo,
--    λ> generate genLetra
--    'O'
--    λ> generate genLetra
--    'H'
genLetra :: Gen Char
genLetra = elements ['A'..'Z']

-- Generador de un bloque de dos letras mayúsculas. Por ejemplo,
--    λ> generate genBloque
--    "ZK"
--    λ> generate genBloque
--    "JW"
genBloque :: Gen String
genBloque = do
  c1 <- genLetra
  c2 <- genLetra
  return [c1, c2]

-- (distintos bs) es la lista obtenida eliminando en bs los bloques
-- iguales. Por ejemplo,
--    λ> distintos ["AB", "BC", "BA"]
--    ["AB","BC"]
distintos :: [String] -> [String]
distintos = nubBy (\b1 b2 -> sort b1 == sort b2)

-- Generador de lista de bloques distintos y una palabra objetivo. Por
-- ejemplo,
--    λ> generate genEntrada
--    (["UQ","MB","TP","YV","GC","SY","AD","VN","TU"],"")
--    λ> generate genEntrada
--    (["ZX","OP","XX","SN","MD","HO","KR","NV"],"MTQ")
genEntrada :: Gen ([String], String)
genEntrada = do
  n <- choose (0, 10)
  bs <- vectorOf n genBloque
  m <- choose (0, 5)
  cs <- vectorOf m genLetra
  return (distintos bs, cs)

-- La propiedad es
prop_equivalencia :: Property
prop_equivalencia = forAll genEntrada $ \(bs, cs) ->
  all (== sort (soluciones1 bs cs))
      [sort (soluciones2 bs cs),
       sort (soluciones3 bs cs)]

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

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

bloques :: [String]
bloques = [[x,y] | x <- ['A'..'Z'], y <- [succ x..'Z']]

-- La comparación es
--    λ> length (soluciones1 bloques "ABCD")
--    386878
--    (2.50 secs, 1,307,186,032 bytes)
--    λ> length (soluciones2 bloques "ABCD")
--    386878
--    (1.96 secs, 892,974,224 bytes)
--    λ> length (soluciones3 bloques "ABCD")
--    386878
--    (0.67 secs, 455,688,320 bytes)