Ir al contenido principal

Código Morse


El código Morse es un sistema de representación de letras y números mediante señales emitidas de forma intermitente.

A los signos (letras mayúsculas o dígitos) se le asigna un código como se muestra a continuación

+---+-------+---+-------+---+-------+---+-------+
| A | .-    | J | .---  | S | ...   | 1 | ..--- |
| B | -...  | K | -.-   | T | -     | 2 | ...-- |
| C | -.-.  | L | .-..  | U | ..-   | 3 | ....- |
| D | -..   | M | --    | V | ...-  | 4 | ..... |
| E | .     | N | -.    | W | .--   | 5 | -.... |
| F | ..-.  | O | ---   | X | -..-  | 6 | --... |
| G | --.   | P | .--.  | Y | -.--  | 7 | ---.. |
| H | ....  | Q | --.-  | Z | --..  | 8 | ----. |
| I | ..    | R | .-.   | 0 | .---- | 9 | ----- |
+---+-------+---+-------+---+-------+---+-------+

El código Morse de las palabras se obtiene a partir del de sus caracteres insertando un espacio entre cada uno. Por ejemplo, el código de "todo" es "- --- -.. ---"

El código Morse de las frase se obtiene a partir del de sus palabras insertando un espacio entre cada uno. Por ejemplo, el código de "todo o nada" es "- --- -.. --- --- -. .- -.. .-"

Definir las funciones

fraseAmorse :: String -> String
morseAfrase :: String -> String

tales que

  • (fraseAmorse cs) es la traducción de la frase cs a Morse. Por ejemplo,
λ> fraseAmorse "En todo la medida"
". -.  - --- -.. ---  .-.. .-  -- . -.. .. -.. .-"
  • (morseAfrase cs) es la frase cuya traducción a Morse es cs. Por ejemplo,
λ> morseAfrase ". -.  - --- -.. ---  .-.. .-  -- . -.. .. -.. .-"
"EN TODO LA MEDIDA"

Nota: La lista de los códigos Morse de A, B, ..., Z, 0, 1, ..., 9 es

[".-","-...","-.-.","-..",".","..-.","--.","....","..",".---",
 "-.-",".-..","--","-.","---",".--.","--.-",".-.","...","-",
 "..-","...-",".--","-..-","-.--","--..",".----","..---","...--",
 "....-",".....","-....","--...","---..","----.","-----"]

Soluciones

import Data.Char (toUpper, isAlphaNum)
import Data.List (intercalate)
import Data.List.Split (splitOn)
import Data.Maybe (fromJust)
import Test.Hspec (Spec, describe, hspec, it, shouldBe)
import Test.QuickCheck

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

-- caracteres es la lista ordenada de las caracteres (letras mayúsculas
-- y dígitos) que se usan en los mensajes Morse.
caracteres :: [Char]
caracteres = ['A'..'Z'] ++ ['0'..'9']

-- morse es la lista de los códigos Morse correspondientes a la lista
-- de caracteres.
morse :: [String]
morse = [".-","-...","-.-.","-..",".","..-.","--.","....","..",".---",
         "-.-",".-..","--","-.","---",".--.","--.-",".-.","...","-",
         "..-","...-",".--","-..-","-.--","--..",".----","..---","...--",
         "....-",".....","-....","--...","---..","----.","-----"]

-- (correspondiente xs ys x) es el elemento de ys en la misma posición
-- que x en xs. Por ejemplo,
--    correspondiente [1..10] [2,4..20] 3  ==  6
correspondiente :: Ord a => [a] -> [b] -> a -> b
correspondiente xs ys x =
  head [y | (z,y) <- zip xs ys, z == x]

-- (caracterAmorse x) es el código Morse correspondiente al carácter
-- x. Por ejemplo,
--    caracterAmorse 'A'  ==  ".-"
--    caracterAmorse 'B'  ==  "-..."
--    caracterAmorse '1'  ==  "..---"
--    caracterAmorse 'a'  ==  ".-"
caracterAmorse :: Char -> String
caracterAmorse =
  correspondiente caracteres morse . toUpper

-- (morseAcaracter x) es el carácter cuyo código Morse es x. Por
-- ejemplo,
--    morseAcaracter ".-"     ==  'A'
--    morseAcaracter "-..."   ==  'B'
--    morseAcaracter "..---"  ==  '1'
morseAcaracter :: String -> Char
morseAcaracter =
  correspondiente morse caracteres

-- (palabraAmorse cs) es el código Morse correspondiente a la palabra
-- cs. Por ejemplo,
--    palabraAmorse "En"  ==  ". -."
palabraAmorse :: [Char] -> String
palabraAmorse = unwords . map caracterAmorse

-- (morseApalabra cs) es la palabra cuyo traducción a Morse es cs. Por
-- ejemplo,
--    morseApalabra ". -."  ==  "EN"
morseApalabra :: String -> [Char]
morseApalabra = map morseAcaracter . words

-- (fraseAmorse cs) es la traducción de la frase cs a Morse. Por ejemplo,
--    λ> fraseAmorse "En todo la medida"
--    ". -.  - --- -.. ---  .-.. .-  -- . -.. .. -.. .-"
fraseAmorse1 :: String -> String
fraseAmorse1 = intercalate "  " . map palabraAmorse . words

-- Ejemplo de cálculo
--    fraseAmorse "En todo la medida"
--    = (intercalate "  " . map palabraAmorse . words)
--      "En todo la medida"
--    = (intercalate "  " . map palabraAmorse)
--      ["En","todo","la","medida"]
--    = intercalate "  " [". -.","- --- -.. ---",".-.. .-","-- . -.. .. -.. .-"]
--    = ". -.  - --- -.. ---  .-.. .-  -- . -.. .. -.. .-"

-- (morseAfrase cs) es la frase cuya traducción a Morse es cs. Por
-- ejemplo,
--    λ> morseAfrase ". -.  - --- -.. ---  .-.. .-  -- . -.. .. -.. .-"
--    "EN TODO LA MEDIDA"
morseAfrase1 :: String -> String
morseAfrase1 = unwords . map morseApalabra . splitOn "  "

-- Ejemplo de cálculo
--    morseAfrase ". -.  - --- -.. ---  .-.. .-  -- . -.. .. -.. .-"
--    = (unwords . map morseApalabra)
--      ". -.  - --- -.. ---  .-.. .-  -- . -.. .. -.. .-"
--    = (unwords . map morseApalabra)
--      [". -.","- --- -.. ---",".-.. .-","-- . -.. .. -.. .-"]
--    = unwords ["EN","TODO","LA","MEDIDA"]
--    = "EN TODO LA MEDIDA"

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

-- Diccionario de Morse
diccionarioMorse :: [(Char, String)]
diccionarioMorse =
  zip (['A'..'Z'] ++ ['0'..'9'])
      [".-","-...","-.-.","-..",".","..-.","--.","....","..",".---",
       "-.-",".-..","--","-.","---",".--.","--.-",".-.","...","-",
       "..-","...-",".--","-..-","-.--","--..",".----","..---","...--",
       "....-",".....","-....","--...","---..","----.","-----"]

fraseAmorse2 :: String -> String
fraseAmorse2 =
  intercalate "  "
  . map (intercalate " "
  . map caracterAmorse2)
  . words
  . map toUpper

caracterAmorse2 :: Char -> String
caracterAmorse2 c =
  fromJust (lookup c diccionarioMorse)

morseAfrase2 :: String -> String
morseAfrase2 =
  unwords
  . map (map morseAcaracter2 . words)
  . splitOn "  "

morseAcaracter2 :: String -> Char
morseAcaracter2 m =
  fromJust (lookup m (map (\(a,b) -> (b,a)) diccionarioMorse))

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

verifica :: IO ()
verifica = hspec spec

specG1 :: (String -> String) -> Spec
specG1 fraseAmorse = do
  it "e1" $
    fraseAmorse "En todo la medida" `shouldBe`
    ". -.  - --- -.. ---  .-.. .-  -- . -.. .. -.. .-"

specG2 :: (String -> String) -> Spec
specG2 morseAfrase = do
  it "e1" $
    morseAfrase ". -.  - --- -.. ---  .-.. .-  -- . -.. .. -.. .-"
    `shouldBe` "EN TODO LA MEDIDA"

spec :: Spec
spec = do
  describe "def. 1" $ specG1 fraseAmorse1
  describe "def. 2" $ specG1 fraseAmorse2
  describe "def. 1" $ specG2 morseAfrase1
  describe "def. 2" $ specG2 morseAfrase2

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

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

-- La propiedad es
prop_equivalencia :: Property
prop_equivalencia =
  forAll (listOf (elements (['A'..'Z'] ++ ['0'..'9'] ++ " "))) $ \xs ->
  let ys = fraseAmorse1 xs in
  fraseAmorse2 xs == ys && morseAfrase1 ys == morseAfrase2 ys

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

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

-- La comparación es
--    λ> length (fraseAmorse1 (take (3*10^6) (cycle "ABC ")))
--    10499998
--    (2.03 secs, 3,318,602,592 bytes)
--    λ> length (fraseAmorse2 (take (3*10^6) (cycle "ABC ")))
--    10499998
--    (0.93 secs, 2,694,602,584 bytes)
--
--    λ> length (morseAfrase1 ejemplo)
--    2999999
--    (4.04 secs, 6,606,601,512 bytes)
--    λ> length (morseAfrase2 ejemplo)
--    2999999
--    (0.79 secs, 3,288,600,112 bytes)