Práctico Haskell 1: GHCI, Recursión y Tipos de Datos

Instrucciones generales

Hacé cada ejercicio (tarjetas de crédito, tipos y hanoi) en un módulo Haskell distinto.

Validar tarjetas de crédito

¿Alguna vez te preguntaste como hacían los sitios web para validar tu tarjeta de crédito cuando comprás en línea? No buscan en una base de datos gigantesca de números, tampoco usan magia. En realidad, la mayoría de los proveedores de tarjetas de crédito usan una fórmula de suma de control para distinguir números válidos de números aleatorios (o de errores de tipeo).

En esta sección, vas a implementar el algoritmo de validación para tarjetas de crédito. El algoritmo sigue los pasos siguientes:

Parte 1

Primero necesitamos descomponer un número en su último dígito y el resto del número. Escribí las funciones siguientes:

lastDigit :: Integer -> Integer
dropLastDigit :: Integer -> Integer

Si estás perdido, buscá los operadores aritméticos que vimos más temprano en esta materia:

Ejemplos:

lastDigit 123 == 3
lastDigit 0 == 0
dropLastDigit 123 == 12
dropLastDigit 5 == 0

Parte 2

Ahora podemos descomponer un número en sus dígitos. Definí la función

toDigits :: Integer -> [Integer]

toDigits debe convertir enteros positivos en una lista de dígitos. Para 0 o valores negativos, debe devolver la lista vacía.

Ejemplos:

toDigits 1234 == [1,2,3,4]
toDigits 0 == []
toDigits (-17) == []

Parte 3

Una vez que tenemos los dígitos en el orden correcto, necesitamos duplicar uno de cada dos. Definí la función:

doubleEveryOther :: [Integer] -> [Integer]

Recordá que doubleEveryOther debería duplicar cada otro número empezando desde la derecha.

Es mucho más fácil llevar a cabo esta operación sobre una lista de dígitos que está en orden inverso. Probablemente vas a necesitar definir unas funciones auxiliares para que eso ande.

Ejemplos:

doubleEveryOther [8,7,6,5] == [16,7,12,5]
doubleEveryOther [1,2,3] == [1,4,3]

Parte 4

La salida de doubleEveryOther es una mezcla de números de uno y dos dígitos. Definí la función

sumDigits :: [Integer] -> Integer

para calcular la suma de todos los dígitos.

Ejemplo:

sumDigits [16,7,12,5] = 1 + 6 + 7 + 1 + 2 + 5 = 22

Parte 5

Definí la función

validate :: Integer -> Bool

que indica si un entero es un número válido de tarjeta de crédito.

Usará todas las funciones definidas en las partes anteriores:

Ejemplos:

validate 4012888888881881 == True
validate 4012888888881882 == False

Tipos Algebraicos de Datos

Definir los tipos y funciones siguientes en un archivo tipos.hs.

1. Tipos enumerados

Cuando los distintos valores que debemos distinguir en un tipo son finitos, podemos enumerar todos los valores distintos para el tipo. Por ejemplo, podrı́amos representar los tı́tulos nobiliarios de algún paı́s (retrógrado) con el siguiente tipo:

data Titulo = Ducado | Marquesado | Condado | Vizcondado | Baronia
  1. Definí el tipo Titulo como descrito arriba. No tiene que pertenecer a la clase Eq.
  2. Definí hombre :: Titulo -> String que devuelve la forma masculina del tı́tulo.
  3. Definí la función dama que devuelve la inflexión femenina.

2. Constructores con parámetros

En este ejercicio, introducimos dos conceptos: los sinónimos de tipos y tipos algebraicos cuyos constructores llevan parámetros. Los sinónimos de tipos nos permiten definir nombres de tipos nuevo a partir de tipos ya existentes:

-- Territorio y Nombre son sinonimos de tipo.
type Territorio = String
type Nombre = String

cba , bsas :: Territorio
cba = "Cordoba"
bsas = "Buenos Aires"

alicia , bob :: Nombre
alicia = "Alicia"
bob = "Bob"

Los tipos algebraicos tienen constructores que llevan parámetros. Esos parámetros nos permiten agregar información; por ejemplo, en este ejercicio además de distinguir el rango de cada persona, representamos datos pertinentes a cada tipo de persona:

-- Persona es un tipo algebraico
data Persona = Rey                  -- constructor sin parametro
          | Noble Titulo Territorio -- constructor con dos parametros
          | Caballero Nombre        -- constructor con un parametro
          | Aldeano Nombre          -- constructor con un parametro
  1. Definí los tipos Territorio, Nombre y Persona como descrito arriba. Este último tipo no debe pertenecer a la clase Eq.
  2. Definí la función tratamiento :: Persona -> String que dada una persona devuelve la forma en que se lo menciona en la corte. Esto es, al rey se lo nombra “Su majestad el rey”, a un noble se lo nombra “El de ”, a un caballero “Sir ” y a un aldeano simplemente con su nombre.
  3. Agreguemos el tipo data Genero = Hombre | Mujer a nuestro módulo. ¿Cómo modificar el tipo Persona para poder representar personas de distintos géneros sin agregarle más constructores? Realizá esta modificación y vuelva a programar la función tratamiento de forma tal de respetar el género de la persona al nombrarla.
  4. Definí la función sirs :: [Persona] -> [String] que dada un lista de personas devuelve solo los nombres de los hombres. Definila con dos casos: el caso base y el inductivo.
  5. Definí una versión alternativa de esta última función, sirs', con un solo caso, usando la función filter.

3. El tipo polimórfico Maybe

Maybe es un tipo predefinido en Haskell cuya definición es la siguiente:

data Maybe a = Nothing | Just a

El tipo Maybe sirve para “envolver” un tipo existente (el tipo a) y darle un valor extra (Nothing) que representa la ausencia de valor, o alguna falla.

El tipo Maybe es polimórfico porque en situaciones concretas, puede tomar como parámetro otro tipo: Maybe Int, Maybe String, etc.

Definí las funciones siguientes:

  1. division_segura :: Int -> Int -> Maybe Int, que toma dos enteros y devuelve Nothing si el segundo es cero, o Just c donde c es el cociente del primero por el segundo.
  2. cabeza :: [a] -> Maybe a que toma una lista y devuelve el primer elemento (dentro de Just) solamente si la lista no es vacía, y Nothing si es vacía.
  3. suma_maybe :: Maybe Int -> Maybe Int -> Maybe Int que toma dos “enteros Maybe”, y solo devuelve un Just de la suma si los dos son Just. En los otros casos, devuelve Nothing.
  4. suma_maybe_num :: Num a => Maybe a -> Maybe a -> Maybe a, una versión más general de suma_maybe.

4. Tipos recursivos y polimórficos

Consideremos la siguiente definición de tipo de datos:

data ListAssoc a b = Empty
                   | Node a b (ListAssoc a b)

Este tipo es recursivo porque él mismo (ListAssoc a b) aparece como un argumento de uno de sus constructores (Node). Los parámetross del constructor de tipo (ListAssoc) indican que es un tipo polimórfico, donde las variables a y b se pueden instanciar con distintos tipos; por ejemplo:

type Diccionario = ListAssoc String String
type DNI = Int
type Padron      = ListAssoc DNI    String

Definí las siguientes funciones:

  1. la_comodin :: Int -> ListAssoc Int Int que, dada un entero positivo n, construye un lista asociativa de n entradas desde n hasta 1, cada entrada teniendo asociado el valor 0. Por ejemplo:

    la_comodin 1 == Node 1 0 Empty
    la_comodin 3 == Node 3 0 (Node 2 0 (Node 1 0 Empty))
    la_comodin 0 == Empty

    Como ListAssoc no pertenece a la clase Show, no vas a poder comprobar esta función sola en GHCi, recién en conjunto con las funciones siguientes vas a poder ver si anda bien.

  2. la_long :: ListAssoc a b -> Int que devuelve la cantidad de datos en una lista.

  3. la_pares :: ListAssoc a b -> [(a,b)] que devuelve la lista de pares contenida en la lista de asociaciones.

  4. la_existe :: Eq a => ListAssoc a b -> a -> Bool que dada una lista y una clave devuelve True si la clave está y False en caso contrario.

  5. la_buscar :: Eq a => ListAssoc a b -> a -> Maybe b que dada una lista y una clave devuelve el dato asociado si es que existe. Podés consultar la definición del tipo Maybe en Hoogle.

  6. la_agregar :: Eq a => a -> b -> ListAssoc a b -> ListAssoc a b que dada una clave a, un dato b lo agrega en la lista si la clave a NO existe. En caso de que la clave a exista, se aplasta el dato anterior.

  7. la_borrar :: Eq a => a ListAssoc a b -> ListAssoc a b que dada una clave a elimina la entrada en la lista.