Programación funcional 2 - Universidad de Buenos Aires

Anuncio
Programación Funcional en Haskell
Paradigmas de Lenguajes de Programación
Facultad de Ciencias Exactas y Naturales
Universidad de Buenos Aires
27 de Agosto de 2013
Esquemas de recursión
Definamos las siguientes funciones
duplicar :: [Int] -> [Int]
duplicar [] = []
duplicar (x:xs) = (2 * x) : duplicar xs
longitudes :: [[a]] -> [Int]
longitudes [] = []
longitudes (xs:xss) = (length xs) : longitudes xss
Abstrayendo
map :: (a -> b) -> [a] -> [b]
map f xs = [f x | x <- xs]
duplicar = map (2*)
longitudes = map length
2 / 24
Esquemas de recursión sobre listas
Definamos las siguientes funciones
pares :: [Int] -> [Int]
pares [] = []
pares (x:xs) = if mod x 2 == 0 then x : pares xs
else pares xs
deLongitudN :: Int -> [[a]] -> [[a]]
deLongitudN n [] = []
deLongitudN n (xs:xss) = if length xs == n
then xs : deLongitudN n xss
else deLongitudN n xss
Abstrayendo
filter :: (a -> Bool) -> [a] -> [a]
filter p xs = [k | k <- xs, p k]
pares = filter ( \ x -> mod x 2 == 0)
deLongitudN n = filter (\ xs -> length xs == n)
3 / 24
Esquemas de recursión sobre listas
Definamos las siguientes funciones
sum :: Num a => [a] -> Int
sum [] = 0
sum (x:xs) = x + sum xs
length :: [a] -> Int
length [] = 0
length (x:xs) = 1 + length xs
concat :: [[a]] -> [a]
concat [] = []
concat (xs:xss) = xs ++ concat xss
FoldR
foldr :: (a -> b -> b) -> b -> [a] -> b
foldr f z [] = z
foldr f z (x : xs) = f x (foldr f z xs)
sum = foldr (+) 0
length = foldr (\ x rec -> 1 + rec) 0
concat = foldr (++) []
4 / 24
Esquemas de recursión sobre listas
FoldL
foldl :: (b -> a -> b) -> b -> [a] -> b
foldl f z [] = z
foldl f z (x : xs) = foldl f (f z x) xs
5 / 24
Folds
Desdoblando funciones a izquierda y a derecha
foldr
foldr
foldr
foldr
recorre funciones de izquierda a derecha
:: (a -> b -> b) -> b -> [a] -> b
f z [] = z
f z (x:xs) = f x (foldr f z xs)
foldr f z (a1 :
(a2 :
f a1 (f a2 (f a3 z))
foldl
foldl
foldl
foldl
(a3 :
[]))) =
lo hace a izquierda
:: (b -> a -> b) -> b -> [a] -> b
f z [] = z
f z (x:xs) = foldl f (f z x) xs
foldl f z (a1 :
(a2 :
f (f (f z a1) a2) a3
(a3 :
[]))) =
Si el valor de f x y puede ser computado usando solo el valor x
entonces foldr no necesita examinar toda la lista.
6 / 24
Ejercicios con foldr y foldl
¿Qué computan estas funciones?
f1 :: [Bool] -> Bool
f1 = foldr (&&) True
f2 :: [a] -> [a]
f2 = foldr (:) []
f3 :: [a] -> [a] -> [a]
f3 xs ys = foldr (:) ys xs
f4 :: [a] -> [a]
f4 = foldl (flip (:)) []
7 / 24
Ejemplo
Definir en Haskell la función zip sin usar recursión explı́cita.
zip
zip
zip
zip
:: [a] -> [b] -> [(a,b)]
[] ys = []
(x:xs) [] = []
(x:xs) (y:ys) = (x,y):(zip xs ys)
8 / 24
Ejemplo
Definir en Haskell la función zip sin usar recursión explı́cita.
zip = foldr
(\x rec -> (\lista -> case lista of
[] -> []
(y:ys) -> (x,y):(rec ys)))
(const [])
Recordemos...
foldr :: (a -> b -> b) -> b -> [a] -> b
foldr f z [] = z
foldr f z (x:xs) = f x (foldr f z xs)
foldr f z (a1 : (a2 : (a3 : []))) = f a1 (f a2 (f a3 z))
9 / 24
Tipos de datos
Tipos de datos
algebraicos.
Determinados por la forma de cada elemento.
abstractos.
Determinados por las operaciones que manipulan sus
elementos.
Tipos algebraicos
se acceden usando pattern matching
están formados por constantes que son constructores,
teniendo o no argumentos.
se definen mediante la cláusula data
10 / 24
Ejercicio
Sea el siguiente tipo:
data AEB a = Hoja a | Bin (AEB a) a (AEB a)
Ejemplo: miÁrbol = Bin (Hoja 3) 5 (Bin (Hoja 7) 8 (Hoja 1))
Definir el esquema de recursión estructural (fold) para árboles
estrictamente binarios, y dar su tipo.
El esquema debe permitir definir las funciones altura, ramas,
#nodos, #hojas, espejo, etc.
11 / 24
¿Cómo hacemos?
Recordemos el tipo de foldr, el esquema de recursión estructural
para listas.
foldr :: (a -> b -> b) -> b -> [a] -> b
¿Por qué tiene ese tipo?
(Pista: pensar en cuáles son los constructores del tipo [a]).
Un esquema de recursión estructural espera recibir un argumento
por cada constructor (para saber qué devolver en cada caso), y
además la estructura que va a recorrer.
El tipo de cada argumento va a depender de lo que reciba el
constructor correspondiente. (Y todos van a devolver lo mismo!)
Si el constructor es recursivo, el argumento correspondiente del
fold va a recibir el resultado de cada llamada recursiva.
12 / 24
¿Cómo hacemos? (Continuación)
Miremos bien la estructura del tipo.
Estamos ante un tipo inductivo con un constructor no recursivo y
un constructor recursivo.
¿Cuál va a ser el tipo de nuestro fold?
¿Y la implementación?
13 / 24
Solución
foldAEB::(a->b)->(b->a->b->b)->AEB a -> b
= fHoja n
foldAEB fHoja (Hoja n)
foldAEB fHoja fBin (Bin t1 n t2)
= fBin (foldAEB fHoja fBin t1)
n (foldAEB fHoja fBin t2)
Ejercicio para ustedes: definir las funciones altura, ramas,
#nodos, #hojas y espejo usando foldAB.
Si quieren podemos hacer alguna en el pizarrón.
14 / 24
Ejercicio
Definir el esquema de recursión estructural para el siguiente tipo:
data Num a=>Polinomio a
=
|
|
|
X
Cte a
Suma (Polinomio a) (Polinomio a)
Prod (Polinomio a) (Polinomio a)
Luego usar el esquema definido para escribir la función:
evaluar::Num a=>a->Polinomio a->a
15 / 24
Solución
foldPoli::Num a=>b->(a->b)->(b->b->b)->(b->b->b)->Polinomio a->b
foldPoli casoX casoCte casoSuma casoProd pol = case pol of
X -> casoX
Cte n -> casoCte n
Suma p q -> casoSuma (rec p) (rec q)
Prod p q -> casoProd (rec p) (rec q)
where rec = foldPoli casoX casoCte casoSuma casoProd
evaluar::Num a=>a->Polinomio a->a
evaluar n = foldPoli n id (+) (*)
16 / 24
Algo un poco más complejo... ¿o no?
1
Definir el tipo de datos RoseTree de árboles no vacı́os, donde
cada nodo tiene una cantidad indeterminada de hijos.
2
Escribir el esquema de recursión estructural para RoseTree.
Es importante escribir primero su tipo.
Usando el esquema definido, escribir las siguientes funciones:
3
hojas, que dado un RoseTree, devuelve una lista con sus
hojas ordenadas de izquierda a derecha, según su aparición en
el RoseTree.
2 distancias, que dado un RoseTree, devuelve las distancias de
su raı́z a cada una de sus hojas.
3 altura, que devuelve la altura de un RoseTree (la cantidad de
nodos de la rama m·s larga). Si el RoseTree es una hoja, se
considera que su altura es 1.
4 cantNodos, que devuelve la cantidad total de nodos del
RoseTree.
1
17 / 24
Tipo RoseTree
data RoseTree a = Rose a [RoseTree a]
¿Cómo hacemos el fold?
¿Hay caso/s base?
¿Cuántos llamados recursivos hay que hacer?
18 / 24
Empecemos por el tipo
Recordar: un fold siempre toma un argumento por cada
constructor del tipo, y además la estructura que va a recorrer.
Los argumentos que corresponden a los constructores devuelven
siempre algo del tipo que queremos devolver, y reciben tantos
argumentos como sus respectivos constructores.
Conclusión: reemplazar cada aparición del tipo de lo que estamos
recorriendo por el tipo que queremos devolver. Cada argumento del
fold va a saber qué hacer con una estructura construida con el
constructor correspondiente.
19 / 24
¿Cómo se aplica esto al RoseTree?
data RoseTree a = Rose a [RoseTree a]
En el caso del RoseTree, eso no cambia. Si nuestro constructor
toma una lista de RoseTrees, tendremos una lista de resultados de
las recursiones.
Entonces el tipo del fold es...
foldRT::(a->[b]->b)->RoseTree a->b
20 / 24
Ahora implementémoslo
foldRT::(a->[b]->b)->RoseTree a->b
foldRT f (Rose x hs) = f x (map (foldRT f) hs)
21 / 24
Y finalmente...
1
Definir el tipo de datos RoseTree de árboles no vacı́os, donde
cada nodo tiene una cantidad indeterminada de hijos. X
2
Escribir el esquema de recursión estructural para RoseTree.
Es importante escribir primero su tipo. X
Usando el esquema definido, escribir las siguientes funciones:
3
1
2
3
4
5
hojas, que dado un RoseTree, devuelve una lista con sus
hojas ordenadas de izquierda a derecha, según su aparición en
el RoseTree.
distancias, que dado un RoseTree, devuelve las distancias de
su raı́z a cada una de sus hojas.
altura, que devuelve la altura de un RoseTree (la cantidad de
nodos de la rama más larga). Si el RoseTree es una hoja, se
considera que su altura es 1.
cantNodos, que devuelve la cantidad total de nodos del
RoseTree.
espejo, análogo al de árbol binario.
22 / 24
Soluciones (prueben resolverlo primero)
cantNodos::RoseTree a->Int
cantNodos = foldRT (\ rs->1+(sum rs))
espejo::RoseTree a->RoseTree a
espejo = foldRT (\x rs-> Rose x (reverse rs))
hojas::RoseTree a->[a]
hojas = foldRT (\x rs->if (null rs) then [x] else concat rs)
distancias :: RoseTree a -> [Int]
distancias = foldRT (\x rs->if (null rs) then [0] else map (+1) (concat rs))
altura::RoseTree a->Int
altura = foldRT (\x rs->if (null rs) then 1 else 1+(maxlista rs))
where maxlista = foldr1 max
O, alternativamente...
altura = (+1).maxlista.distancias where maxlista = foldr1 max
(La lista de distancias nunca es vacı́a, ası́ que podemos usar foldr1.)
23 / 24
¿?
¿? ¿?
¿? ¿?
¿? ¿? ¿? ¿? ¿? ¿? ¿?
¿?
Descargar