Funciones y estructuras de datos
Una función es una regla que asigna a cada elemento de un primer conjunto un único elemento de un segundo conjunto.
Diremos que una función le “asigna” a los elementos de un conjunto, llamado dominio, elementos de otro conjunto, llamado codominio. La notación:
\[f : A → B \]Indica que \(f\) es una función con dominio \(A\) y codominio \(B\). La definición de una función tiene la forma:
\(f.x\) = ⟨expresión que depende de x⟩ donde \(f\) es el nombre de la función y \(x\) es/son la/s variable/s independiente/s.
En las siguientes definiciones identificaremos las variables, las constantes y el nombre de la función.
\[f.x = 5 ∗ x \]• Variable: \(x\)
• Constante: \(5\)
• Nombre de la función: \(f\)
\[multiplicar.zz.tt = zz ∗ tt \]
• Variables: \(zz, tt\)
• Constantes: Ninguna
• Nombre de la función: \(multiplicar\)
\[duplica.a = a + a \]
• Variable: \(a\)
• Constante: No hay constantes explícitas en esta expresión
• Nombre de la función: \(duplica\)
Tomando las definiciones del punto anterior evaluemos las siguientes expresiones. Justificaremos cada paso utilizando la notación aprendida.
\[(multiplicar.(f.5).2) + 1 \]≡ {
Definición de f }
≡ {
Definición de multiplicar }
≡ {
Aritmética }
≡ {
Aritmética }
≡ {
Aritmética }
Tipado de funciones
El tipado de funciones es una forma de especificar el tipo de datos que una función acepta como entrada y el tipo de datos que devuelve como salida.
Tomemos la función \(g\)
\[g.x.y ≐ 3*x − x * y > 0 \]Esta función toma dos argumentos de tipo \(Num\) y devuelve un valor de tipo \(Bool\). Así, el tipo de \(g\) se declara de la siguiente forma:
\[g : Num → Num → Bool \]Es decir, los tipos de los argumentos se listan primero, siguiendo el orden en el que serán llamados por la función, y en último lugar se coloca el tipo del resultado de evaluar la función.
Ejemplos:
1.
\( g.y ≐ 8 * y\)
\(g :: Num a ⇒ a \)
• Toma un argumento de tipo \(Num\) y devuelve un valor de tipo \(Num\).
2.
\(h.z.w ≐ z + w\)
\(h :: Num a ⇒ a → a → a\)
• Toma dos argumentos de tipo \(Num\) y devuelve un valor de tipo \(Num\).
3.
\(j.x ≐ x ≤ 0\)
\(j :: Num a ⇒ a → Bool\)
• Toma un argumento de tipo \(Num\) y devuelve un valor de tipo \(Bool\).
Definiciones de Funciones por casos
Las definiciones de funciones por casos son una forma de definir funciones que toman diferentes valores dependiendo de los valores de sus argumentos.
Hay ocasiones en las que una sola fórmula no alcanza para definir una función. Así, existen funciones que para una parte del dominio requieren una definición y para otra parte del dominio necesitan de otra definición diferente. Es el caso, por ejemplo, de la función valor absoluto que dado un número devuelve su valor absoluto.
Una definición por casos de una función tendrá la siguiente forma general:
\[\begin{aligned} &f.x ≐ ( B_0 → f_0 \\ &□ \; B_1 → f_1 \\ &... \\ &□ \; B_n → f_n ) & \end{aligned} \]Donde las \(B_i\) son expresiones de tipo booleano, llamadas guardas, y las \(f_i\) son expresiones del mismo tipo que el resultado de \(f\) que nos describe algebraicamente la relación para cada caso.
Para un argumento dado el valor de la función se corresponde con la expresión cuya guarda es verdadera para ese argumento.
Al trabajar con expresiones booleanas se hace necesario incorporar a nuestro formalismo los operadores
\(∧\), \(∨\), \(¬\) que pueden ser utilizados para combinar dos fórmulas para obtener una nueva fórmula. En haskell estos
operadores se escriben &&
, ||
y not
, respectivamente.
Podemos definir distintas funciones para luego, poder implementarlas en Haskell. Por ejemplo:
\(signo : Int → Int\), que dado un entero retorna su signo, de la siguiente forma: retorna \(1\) si
\(x\) es positivo, \(-1\) si es negativo y \(0\) en cualquier otro caso.
Podemos definirla como:
\[\begin{aligned} &signo.x ≐ ( x > 0 → 1 \\ &□ \; x < 0 → -1 \\ &□ \; x = 0 → 0 \; ) \end{aligned} \]Esta definición, en haskell se anota de la siguiente manera
signo :: Int -> Int
signo x | x > 0 = 1
| x < 0 = -1
| x == 0 = 0
Ejercicios resueltos - Definiciones de funciones simples y por casos
1.
\(entre0y9 : Int → Bool\), que dado un entero devuelve \(True\) si el entero se encuentra entre \(0\) y \(9\).
entre0y9 :: Int -> Bool
entre0y9 x = x >= 0 && x <= 9
-- entre0y9 5
-- True
No hace falta aclarar que si el número está dentro del conjunto es \(True\) y si no \(False\) ya que Haskell lo entiende por defecto.
2.
\(rangoPrecio : Int → String\), que dado un número que representa el precio de una computadora, retorne “muy barato” si el precio es menor a \(2000\), “demasiado caro” si el precio es mayor que \(5000\), “hay que verlo bien” si el precio está entre \(2000\) y \(5000\), y “esto no puede ser!” si el precio es negativo.
rangoPrecio :: Int -> String
rangoPrecio x | x < 2000 && x > 0 = "muy barato"
| x > 5000 = "demasiado caro"
| 2000 <= x && x <= 5000 = "hay que verlo bien"
| x < 0 = "esto no puede ser!"
-- rangoPrecio 3000
-- "hay que verlo bien"
Muy importante aclarar que el precio no puede ser negativo, por lo que se pone la condición x < 0
.
3.
\(absoluto : Int → Int\), que dado un entero retorne su valor absoluto.
absoluto :: Int -> Int
absoluto x | x >= 0 = x
| x < 0 = -x
-- absoluto (-5)
-- 5
En este caso, si el número es positivo, se devuelve el mismo número, y si es negativo, se devuelve el número multiplicado por \(-1\).
4.
\(esMultiplo2 : Int → Bool\), que dado un entero \(n\) devuelve \(True\) si \(n\) es múltiplo de \(2\).
esMultiplo2 :: Int -> Bool
esMultiplo2 x = mod x 2 == 0
-- esMultiplo2 4
-- True
En este caso, si el número es divisible por \(2\), se devuelve \(True\), y si no, \(False\).
Combinado de Funciones
El Combinado de Funciones es una operación que toma dos funciones y las combina para formar una nueva función.
En algunos ejercicios que siguen se van a utilizar algunas de las funciones que están en el Prelude (librería básica de Haskell). Por ejemplo:
\(mod\) \(20\) \(3\) = \(2\) - el resto de la división entre \(20\) y \(3\) es \(2\).
\(div\) \(14\) \(3\) = \(4\) - parte entera de la división entre \(14\) y \(3\) es \(4\).
\(max\) \(8\) \(10\) = \(10\) - devuelve el max entre \(2\) números.
\(min\) \(9\) \(15\) = \(9\) - devuelve el min entre \(2\) números.
Por ejemplo, si queremos definir la función \(esMultiploDe : Num→ Num→ Bool\) , que devuelve \(True\) si el segundo es múltiplo del primero. Ejemplo: \(esMultiploDe\) \(3\) \(1\)2 = \(True\).
esMultiploDe :: Int -> Int -> Bool
esMultiploDe x y = (mod y x) == 0
-- esMultiploDe 3 12
-- True
En este caso, si el segundo número es divisible por el primero, se devuelve \(True\), y si no, \(False\).
Ejercicios resueltos - Combinado de Funciones
1.
Definir la función \(esBisiesto: Num→ Bool\), que indica si un año es bisiesto. Un año es bisiesto si es divisible por \(400\) o es divisible por 4 pero no es divisible por \(100\).
esBisiesto :: Int -> Bool
esBisiesto x = (mod x 400 == 0) || (mod x 4 == 0 && mod x 100 /= 0)
-- esBisiesto 2020
-- True
En este caso, si el año es divisible por \(400\) o por \(4\) pero no por \(100\), se devuelve \(True\), y si no, \(False\).
2.
Definir la función \(dispersion : Num→ Num→ Num→ Num\), que toma los tres valores y devuelve la diferencia entre el más alto y el más bajo. Para este caso, se puede extender max
y min
a tres argumentos, usando las versiones de dos elementos. De esa forma se puede definir dispersión sin escribir ninguna guarda (las guardas están en max
y min
, que estamos usando).
dispersion :: Int -> Int -> Int -> Int
dispersion x y z = max x (max y z) - min x (min y z)
-- dispersion 3 5 1
-- 4
En este caso, se devuelve la diferencia entre el número más alto y el más bajo.
3.
Definir la función \(celsiusToFahr : Num→ Num\), pasa una temperatura en grados Celsius a grados Fahrenheit. Para realizar la conversión podemos multiplicar por \(1.8\) y sumar \(32\).
celsiusToFahr :: Float -> Float
celsiusToFahr x = x * 1.8 + 32
-- celsiusToFahr 0
-- 32.0
4.
Definir la función \(fahrToCelsius : Num→ Num\), la inversa de la anterior. Para realizar la conversión hay que primero restar \(32\) y después dividir por \(1.8\).
fahrToCelsius :: Float -> Float
fahrToCelsius x = (x - 32) / 1.8
-- fahrToCelsius 32
-- 0.0
5.
Definir la función \(haceFrioF : Num → Bool\), indica si una temperatura expresada en grados Fahrenheit es fría. Decimos que hace frío si la temperatura es menor a \(8\) grados Celsius.
haceFrioF :: Float -> Bool
haceFrioF x = fahrToCelsius x < 8
-- haceFrioF 32
-- False
En este caso, si la temperatura en grados Fahrenheit es menor a \(8\) grados Celsius, se devuelve \(True\), y si no, \(False\). Reusamos la función fahrToCelsius
que definimos anteriormente.
Tuplas
Una Tupla es una secuencia de elementos de longitud fija. Cada elemento de una tupla puede ser de un tipo diferente.
Una manera de formar un nuevo tipo es combinando los otros ya existentes en tuplas (i.e., haciendo su producto cartesiano). El ejemplo más conocido es, quizás, el de un par de números como \((3,2)\). \((3,2)\) es un elemento de tipo \((Num, Num)\).
Por ejemplo, podemos definir una función que toma dos pares y los suma coordenada a coordenada de la siguiente forma:
\[\begin{aligned} &\text{sumaPares} : (\text{Num}, \text{Num}) \rightarrow (\text{Num}, \text{Num}) \rightarrow (\text{Num}, \text{Num}) \\ &\text{sumaPares}.(a, b).(c, d) \; \dot{=} \; (a+c,\; b+d) \end{aligned} \]Ejercicios resueltos - Tuplas
1.
\(segundo3 : (Num, Num, Num) → Num\), que dada una terna de enteros devuelve su segundo elemento.
segundo3 :: (Int, Int, Int) -> Int
segundo3 (x, y, z) = y
-- segundo3 (1, 2, 3)
-- 2
2.
\(ordena : (Num, Num) → (Num, Num)\), que dados dos enteros los ordena de menor a mayor.
ordena :: (Int, Int) -> (Int, Int)
ordena (x, y) = (min x y, max x y)
-- ordena (3, 1)
-- (1, 3)
En este caso, se devuelve una tupla con el menor número en la primera posición y el mayor en la segunda.
3.
\(rangoPrecioParametrizado : Num → (Num, Num) → String\) que dado un número \(x\), que representa el precio de un producto, y un par (menor, mayor) que represente el rango de precios que uno espera encontrar, retorne “muy barato” si \(x\) está por debajo del rango, “demasiado caro” si está por arriba del rango, “hay que verlo bien” si el precio está en el rango, y “esto no puede ser!” si x es negativo.
rangoPrecioParametrizado :: Int -> (Int, Int) -> String
rangoPrecioParametrizado x (y, z) | x < y && x > 0 = "muy barato"
| x > z = "demasiado caro"
| y <= x && x <= z = "hay que verlo bien"
| x < 0 = "esto no puede ser!"
-- rangoPrecioParametrizado 3000 (2000, 5000)
-- "hay que verlo bien"
4.
\(mayor3 : (Num, Num, Num) → (Bool , Bool , Bool )\), que dada una una terna de enteros devuelve una terna de valores booleanos que indica si cada uno de los enteros es mayor que \(3\).
mayor3 :: (Int, Int, Int) -> (Bool, Bool, Bool)
mayor3 (x, y, z) = (x > 3, y > 3, z > 3)
-- mayor3 (4, 2, 5)
-- (True, False, True)
5.
\(todosIguales : (Num, Num, Num) → Bool\) que dada una terna de enteros devuelva \(True\) si todos sus elementos son iguales y \(False\) en caso contrario.
todosIguales :: (Int, Int, Int) -> Bool
todosIguales (x, y, z) = x == y && y == z
-- todosIguales (3, 3, 3)
-- True
6.
\(notaPromedio : (String, Num, Num, Num) → Num\) que dada una tupla que representa el nombre de un/a estudiante y las notas que sacó en cada parcial, calcule la nota promedio.
notaPromedio :: (String, Int, Int, Int) -> Int
notaPromedio (nombre, x, y, z) = div (x + y + z) 3
-- notaPromedio ("Juan", 7, 8, 9)
-- 8
7.
\(condicionFinal : (String, Num, Num, Num) → String\) que dada una tupla que representa el nombre de un/a estudiante y las notas que sacó en cada parcial, devuelva “promoción” si las 3 notas son mayores a 7, “regular” si no promociona pero las 3 notas son mayores a 4, y “libre” en caso contrario.
condicionFinal :: (String, Int, Int, Int) -> String
condicionFinal (nombre, x, y, z) | x >= 7 && y >= 7 && z >= 7 = "promoción"
| x >= 4 && y >= 4 && z >= 4 = "regular"
| x <= 3 || y <= 3 || z <= 3 = "libre"
-- condicionFinal ("Juan", 7, 8, 9)
-- "promoción"
Listas y sus Operadores
Una Lista es una secuencia de elementos de longitud variable. Cada elemento de una lista puede ser de un tipo diferente.
Ahora, se complejiza el lenguaje de nuestras expresiones agregando listas. Una lista (o secuencia) es una colección ordenada de valores, que deben ser todos del mismo tipo; por ejemplo, \([1, 2, 5]\). Denotamos a la lista vacía con \([ ]\).
Contamos con distintos operadores para trabajar con listas, con su notación formal y su equivalente en Haskell.
● \(▹\), llamado pegar o constructor, toma un elemento x (a izquierda) y una lista xs, y devuelve una lista con x como primer elemento y los elementos de xs a continuación. Ej:
El operador \(▹\) es fundamental (se lo denomina constructor), ya que permite construir listas arbitrarias a partir de la lista vacía. \(▹\) toma un elemento \(x\) (a izquierda) y una lista \(xs\) y devuelve una lista con primer elemento \(x\) seguido de los elementos de \(xs\). Por ejemplo:
\[\begin{aligned} &3 \; ▹ \; [ ] = [3] \\ &1 \; ▹ \; [2, 3] = [1, 2, 3] \end{aligned} \]Para denotar listas no vacías utilizamos expresiones de la forma \([x, y, . . . , z]\), que son abreviaciones de \(x \; ▹ \; (y \; ▹ \; . . . (z \; ▹ \; [ ])\).
El operador \(▹\) es asociativo a derecha, por lo tanto, es lo mismo escribir \(x \; ▹ \; (y \; ▹ \; . . . (z \; ▹ \; [ ])\) que \(x \; ▹ \; y \; ▹ \; . . . \; z \; ▹ \; [ ]\).
En Haskell se escribe:
3 : [ ] = [3]
● \(\#\), llamado cardinal, toma una lista \(x\)s y devuelve su cantidad de elementos. Ej:
\[\#[1, 2, 0, 5] = 4 \]En Haskell se escribe:
length [1, 2, 0, 5] = 4
● \(!\) llamado índice, toma una lista \(xs\) (a izquierda) y un natural \(n\) que indica una posición, y devuelve el elemento de la lista que se encuentra en la posición \(n\) (contando a partir de la posición 0). Ej:
\[[1, 3, 3, 6, 2, 3, 4, 5] \; ! \; 4 = 2. \]Este operador, asocia a izquierda, por lo tanto \(xs \; ! \; n \; ! \; m\) se interpreta como \((xs \; ! \; n) \; ! \; m\).
En Haskell \(xs \; ! \; n\) se escribe xs !! n
:
[1, 3, 3, 6, 2, 3, 4, 5] !! 4 = 2
● \(↑\) llamado tomar, toma una lista \(xs\) (a izquierda) y un natural \(n\) que indica una cantidad, y devuelve la sublista con los primeros \(n\) elementos de \(xs\). Ej:
\[[1, 2, 3, 4, 5, 6] ↑ 2 = [1, 2] \]Este operador asocia a izquierda, por lo tanto \(xs ↑ n ↑ m\) se interpreta como \((xs ↑ n) ↑ m\). En Haskell \(xs ↑ n\) se escribe: take n xs
:
take 2 [1, 2, 3, 4, 5, 6] = [1, 2]
● \(↓\) llamado tirar toma una lista \(xs\) (a izquierda) y un natural n que indica una cantidad, y devuelve la sublista sin los primeros \(n\) elementos de \(xs\). Ej:
\[[1, 2, 3, 4, 5, 6] ↓ 2 = [3, 4, 5, 6] \]Este operador se comporta igual al anterior, interpretando \(xs ↓ n ↓ m\) como \((xs ↓ n) ↓ m\). En Haskell \(xs ↓ n\) se escribe: drop n xs
:
drop 2 [1, 2, 3, 4, 5, 6] = [3, 4, 5, 6]
● \(⧺\) llamado concatenar toma una lista \(xs\) (a izquierda) y otra \(ys\), y devuelve la lista con todos los elementos de \(xs\) seguidos de los elementos de \(ys\). Ej:
\[[1, 2, 4] \; ⧺ \; [1, 0, 7] = [1, 2, 4, 1, 0, 7] \]Este operador es asociativo por lo que podemos escribir sin ambigüedad expresiones sin paréntesis, como \(xs \; ⧺ \; ys ⧺ \; zs\). En Haskell \(xs \; ⧺ \; ys\) se escribe xs ++ ys
.
[1, 2, 4] ++ [1, 0, 7] = [1, 2, 4, 1, 0, 7]
● \(◃\), llamado pegar a izquierda toma una lista \(xs\) (a izquierda) y un elemento y devuelve una lista con todos los elementos de \(xs\) seguidos por \(x\) como último elemento. Ej:
\[[1, 2] \; ◃ \; 3 = [1, 2, 3] \]Este operador es asociativo a izquierda, luego es lo mismo \(([ ] \; ◃ \; z) \; . . . \; ◃ \; y) \; ◃ \; x\) que \([ ] \; ◃ \; z \; . . . \; ◃ \; y \; ◃ \; x\).
En Haskell \(xs \; ◃ \; x\) no está definido, pero podríamos utilizar el operador de concatenar para implementarla de la siguiente manera: xs ++ [x]
[1, 2] ++ [3] = [1, 2, 3]
Existen además dos funciones fundamentales sobre listas en Haskell que listamos a continuación.
● head
, llamada cabeza o head en inglés, toma una lista xs
y devuelve su primer
elemento. Ej:
head.[1,2,3] = 1.
● tail
, llamada cola o tail en inglés, toma una lista xs
y devuelve la sublista que
resulta de eliminar el primer elemento. Ej:
tail.[1,2,3] = [2,3]
Ejercicios resueltos - Listas y sus Operadores
Determinar los resultados en base a su operador:
1.
length [5,6,7]
= {
def. de length }
3
2.
[5, 3, 57] !! 1
= {
def. de !! }
3
3.
[0, 11, 2, 5] : [ ]
= {
def. de : }
[0, 11, 2, 5]
4.
take 2 [5, 6, 7]
= {
def. de take }
[5, 6]
5.
drop 2 [5, 6, 7]
= {
def. de drop }
[7]
6.
head (0 : [1, 2, 3])
= {
def. de : }
head [0, 1, 2, 3]
= {
def. de head }
0
7.
([1, 2] ++ [3, 4]) ++ [2 + 3]
= {
def. de ++ }
[1, 2, 3, 4] ++ [2 + 3]
= {
def. de ++ }
[1, 2, 3, 4, 5]
Funciones recursivas
Una función recursiva es una función que se llama a sí misma dentro de su definición.
Una función recursiva es una función tal que en su definición puede aparecer su propio nombre. Sin embargo, ¿Cómo lograr que no sea una definición circular? La clave está en el principio de inducción: en primer lugar hay que definir la función para el (los) caso(s) más “pequeño(s)”, que llamaremos caso base y luego definir el caso general en términos de algo más “chico”, que llamaremos caso inductivo.
El caso base no debe aparecer el nombre de la función que se está definiendo. El caso inductivo es donde aparece el nombre de la función que se está definiendo, y debe garantizarse que el (los) argumento(s) al cual se aplica en la definición es más “chico” (para alguna definición de más chico) que el valor para la que se está definiendo.
Para dar un ejemplo de función recursiva, supongamos que tenemos una lista de números y queremos sumarlos. Podemos definir la función \(sumatoria\) de la siguiente manera:
\[\begin{equation*} \begin{aligned} &sumatoria : [Int] \rightarrow Int \\ &sumatoria.[ ] \; \dot{=} \; 0 \\ &sumatoria.(x \;▹ \;xs) \; \dot{=} \; x + sumatoria.xs \end{aligned} \end{equation*} \]En Haskell, la función \(sumatoria\) se escribe de la siguiente manera:
sumatoria :: [Int] -> Int
sumatoria [ ] = 0
sumatoria (x:xs) = x + sumatoria xs
-- Si corremos la función sumatoria [1, 2, 3] obtendremos 6:
-- sumatoria [1, 2, 3] = sumatoria (1 : 2 : 3 : [ ])
-- = 1 + sumatoria (2 : 3 : [ ])
-- = 1 + 2 + sumatoria (3 : [ ])
-- = 1 + 2 + 3 + sumatoria [ ]
-- = 1 + 2 + 3 + 0 = 6
Existen algunos tipos de funciones recursivas como:
-
Filter: toma una lista y devuelve una lista con los elementos que cumplen una condición.
-
Map: toma una lista y devuelve una lista con los elementos transformados por una función.
-
Fold: toma una lista y devuelve un valor resultante de combinar los elementos de la lista.
-
Zip: aquella que dadas dos listas devuelve una lista de pares donde el primer elemento de cada par se corresponde con la primera lista, y el segundo elemento de cada par se corresponde con la segunda lista.
-
Unzip: aquella que dada una lista de tuplas devuelve una lista de alguna de las proyecciones de la tupla.
Ejercicios resueltos - Funciones recursivas
Definir recursivamente las siguientes funciones de tipo filter.
1.
\(soloPares : [Int] → [Int]\), que dada una lista de enteros \(xs\) devuelve una lista sólo con los números pares contenidos en \(xs\), en el mismo orden y con las mismas repeticiones (si las hubiera).
Por ejemplo: \(soloPares.[3,0,-2,12] = [0,-2, 12]\)
soloPares :: [Int] -> [Int]
soloPares [] = []
soloPares (x:xs) | mod x 2 == 0 = x : soloPares xs
| mod x 2 /= 0 = soloPares xs
-- soloPares [3, 0, -2, 12]
-- [0, -2, 12]
2.
\(mayoresQue10 : [Int] → [Int]\), que dada una lista de enteros \(xs\) devuelve una lista sólo con los números mayores que \(10\) contenidos en \(xs\).
Por ejemplo: \(mayoresQue10.[3,0,-2, 12] = [12]\)
mayoresQue10 :: [Int] -> [Int]
mayoresQue10 [] = []
mayoresQue10 (x:xs) | x > 10 = x : mayoresQue10 xs
| x <= 10 = mayoresQue10 xs
-- mayoresQue10 [3, 0, -2, 12]
-- [12]
3.
\(mayoresQue : Int → [Int] → [Int]\), que dado un entero \(n\) y una lista de enteros \(xs\) devuelve una lista sólo con los números mayores que \(n\) contenidos en \(xs\).
Por ejemplo: \(mayoresQue2.[3,0,-2, 12] = [3,12]\)
mayoresQue :: Int -> [Int] -> [Int]
mayoresQue n [] = []
mayoresQue n (x:xs) | x > n = x : mayoresQue n xs
| x <= n = mayoresQue n xs
-- mayoresQue 2 [3, 0, -2, 12]
-- [3, 12]
Definir recursivamente las siguientes funciones de tipo map.
4.
\(sumar1: [Int] → [Int]\), que dada una lista de enteros le suma \(1\) a cada uno de sus elementos.
Por ejemplo: \(sumar1.[3, 0, -2] = [4, 1, -1]\)
sumar1 :: [Int] -> [Int]
sumar1 [] = []
sumar1 (x:xs) = (x + 1) : sumar1 xs
-- sumar1 [3, 0, -2]
-- [4, 1, -1]
5.
\(duplica: [Int] → [Int]\), que dada una lista de enteros duplica cada uno de sus elementos.
Por ejemplo: \(duplica.[3, 0, -2] = [6, 0, -4]\)
duplica :: [Int] -> [Int]
duplica [] = []
duplica (x:xs) = (x * 2) : duplica xs
-- duplica [3, 0, -2]
-- [6, 0, -4]
En este caso, se duplica cada número de la lista.
6.
\(multiplica: Int → [Int] → [Int]\), que dado un número \(n\) y una lista, multiplica cada uno de los elementos por \(n\).
Por ejemplo: \(multiplica.3.[3, 0, −2] = [9, 0, −6]\)
multiplica :: Int -> [Int] -> [Int]
multiplica n [] = []
multiplica n (x:xs) = (x * n) : multiplica n xs
-- multiplica 3 [3, 0, -2]
-- [9, 0, -6]
Definir recursivamente las siguientes funciones de tipo fold.
7.
\(todosMenores10 : [Int] → Bool\), que dada una lista devuelve \(True\) si ésta consiste sólo de números menores que \(10\).
Por ejemplo: \(todosMenores10.[1,3,9] = True\)
todosMenores10 :: [Int] -> Bool
todosMenores10 [] = True
todosMenores10 (x:xs) | x < 10 = todosMenores10 xs
| x >= 10 = False
-- todosMenores10 [1, 3, 9]
-- True
8.
\(hay0 : [Int] → Bool\), que dada una lista decide si existe algún \(0\) en ella.
Por ejemplo: \(hay0.[1,0,3] = True\)
hay0 :: [Int] -> Bool
hay0 [] = False
hay0 (x:xs) | x == 0 = True
| x /= 0 = hay0 xs
-- hay0 [1, 0, 3]
-- True
9.
\(suma : [Int] → Int\), que dada una lista devuelve la suma de todos sus elementos.
Por ejemplo: \(suma.[1,2,3] = 6\)
suma :: [Int] -> Int
suma [] = 0
suma (x:xs) = x + suma xs
-- suma [1, 2, 3]
-- 6
En este caso, se devuelve la suma de todos los números de la lista.
Definir recursivamente la siguiente función de tipo zip.
10. \(repartir : [String] → [String] → [(String,String)]\), que dadas dos listas de strings devuelve una lista de pares donde el primer elemento de cada par se corresponde con la primera lista, y el segundo elemento de cada par se corresponde con la segunda lista.
repartir :: [String] -> [String] -> [(String, String)]
repartir [] [] = []
repartir (x:xs) (y:ys) = (x, y) : repartir xs ys
-- repartir ["Juan", "Maria"] ["Dominguez", "Gutierrez"]
-- [("Juan", "Dominguez"), ("Maria", "Gutierrez")]
Definir recursivamente la siguiente función de tipo unzip.
\(apellidos : [(String, String, Int)] → [String]\).
Ej: \(apellidos.[(\textnormal{"Juan"},\textnormal{"Dominguez"},22), (\textnormal{"Maria"},\textnormal{"Gutierrez"},19)]\)
Devuelve \([\textnormal{"Dominguez"},\textnormal{"Gutierrez"}]\)
apellidos :: [(String, String, Int)] -> [String]
apellidos [] = []
apellidos ((x, y, z):xs) = y : apellidos xs
-- apellidos [("Juan", "Dominguez", 22), ("Maria", "Gutierrez", 19)]
-- ["Dominguez", "Gutierrez"]