introalg:taller09_6
Diferencias
Muestra las diferencias entre dos versiones de la página.
Próxima revisión | Revisión previa | ||
introalg:taller09_6 [2009/05/04 00:50] – creado laura | introalg:taller09_6 [2018/08/10 03:03] (actual) – editor externo 127.0.0.1 | ||
---|---|---|---|
Línea 3: | Línea 3: | ||
En la clase anterior introdujimos el concepto de recursividad o recursión. En esta clase vamos a consolidarlo, | En la clase anterior introdujimos el concepto de recursividad o recursión. En esta clase vamos a consolidarlo, | ||
- | En primer lugar, recordemos que de un programa recursivo | + | En primer lugar, recordemos |
- | | + | |
- | | + | |
Para garantizar que se cumplen estos dos requisitos, los programas o funciones recursivos tienen que tener las siguientes tres propiedades: | Para garantizar que se cumplen estos dos requisitos, los programas o funciones recursivos tienen que tener las siguientes tres propiedades: | ||
Línea 19: | Línea 19: | ||
< | < | ||
repetir :: Int -> a -> [a] | repetir :: Int -> a -> [a] | ||
- | repetir 0 x = [] | + | repetir 0 x = [] -- caso base |
- | repetir n x = x : repetir (n-1) x | + | repetir n x = x : repetir (n-1) x -- caso recursivo |
</ | </ | ||
- | La función '' | + | En esta función, lo que hacemos en cada caso es simplemente concatenar el elemento, que hemos llamado '' |
+ | |||
+ | Veamos otro ejemplo, la función '' | ||
+ | < | ||
+ | potencia :: Int -> Int -> Int | ||
+ | potencia 0 x = 1 | ||
+ | potencia n x = x * potencia (n-1) x | ||
+ | </ | ||
+ | |||
+ | Fíjense que en la función '' | ||
+ | |||
+ | Pero además, no podemos devolver cualquier entero o cualquier lista: tienen que ser elementos que NO modifiquen el resultado final del cálculo. Para saber cuál ese elemento tenemos que fijarnos en la operación que estamos aplicando: multiplicación en un caso y concatenación en el otro. Para una operación cualquiera, existe un elemento que se llama **neutro** que tiene la propiedad de no modificar el resultado de esa operación, de tal modo que para una operación ☆, su neutro N y un elemento arbitrario '' | ||
+ | p ☆ N = p | ||
+ | Ese elemento depende de cada operación, veamos algunos casos particulares: | ||
+ | p + 0 = p | ||
+ | p * 1 = p | ||
+ | p && True = p | ||
+ | p || False = p | ||
+ | |||
+ | Esto será especialmente importante recordarlo para los [[http:// | ||
===== Recursión en listas ===== | ===== Recursión en listas ===== | ||
+ | |||
+ | Las listas son una estructura que por definición contiene un número arbitrario de elementos, por lo tanto son el contexto natural para aplicar funciones recursivas. Hemos distinguido tres grandes tipos de funciones recursivas en listas: | ||
+ | * **aplicaciones**: | ||
+ | |||
+ | aplica :: [a] -> [b] | ||
+ | |||
+ | donde el tipo de la lista inicial no necesariamente tiene que ser el mismo que el de la lista resultante, ya que la función que aplicamos a cada elemento puede cambiar ese tipo (por ejemplo, si cambiamos de booleano a 0 o 1). | ||
+ | |||
+ | * **filtros**: | ||
+ | |||
+ | filtra :: [a] -> [a] | ||
+ | |||
+ | donde el tipo de la lista inicial tiene que ser exactamente el mismo que el de la lista resultante, ya que devolvemos elementos del mismo tipo solamente que podemos devolver menos elementos, descartando los que no cumplen el predicado (por ejemplo, los que no son pares). | ||
+ | |||
+ | * **acumuladores**: | ||
+ | |||
+ | acumula :: [a] -> b | ||
+ | |||
+ | fíjense que el tipo del resultado es distinto que el de la lista inicial, de hecho, ni siquiera es necesario que sea una lista. El tipo del resultado es exactamente **el neutro de la operación** que estemos aplicando para acumular el resultado (por ejemplo, en una sumatoria es el 0, en una productoria el 1, etc.). | ||
+ | |||
+ | Algunas funciones se corresponden exactamente con estos tipos generales. Otras funciones más complejas, en cambio, **combinan** varios de estos tipos de funciones para resolver un problema. Veamos ahora ejemplos de cada uno de estos tres tipos y luego ejemplos de combinaciones. | ||
==== Aplicaciones (map) ==== | ==== Aplicaciones (map) ==== | ||
- | '' | + | Las funciones de tipo aplicación toman una lista y devuelven otra lista que es el resultado de aplicar una función a todos los elementos de la lista original. Veamos algunos ejemplos: |
+ | < | ||
+ | duplicar :: [Int] -> [Int] | ||
+ | duplicar [] = [] | ||
+ | duplicar (x:xs) = 2*x : duplicar xs | ||
+ | </ | ||
+ | |||
+ | < | ||
+ | veintePorCiento :: [Float] -> [Float] | ||
+ | veintePorCiento [] = [] | ||
+ | veintePorCiento (x:xs) = 0.2*x : veintePorCiento xs | ||
+ | </ | ||
+ | |||
+ | < | ||
+ | cuadrados :: [Int] -> [Int] | ||
+ | cuadrados [] = [] | ||
+ | cuadrados (x:xs) = cuadrado x : cuadrados xs | ||
+ | |||
+ | cuadrado :: Int -> Int | ||
+ | cuadrado x = x^2 | ||
+ | </ | ||
+ | |||
+ | Entonces, la estructura general de una función de tipo aplicación es la siguiente: | ||
+ | |||
+ | ___________ :: [a] -> [b] | ||
+ | ___________ [] = [] | ||
+ | ___________ (x:xs) = _________ x : ___________ xs | ||
+ | |||
+ | |||
+ | Ahora que sabemos esto, resolvamos la función | ||
==== Filtros (filter) ==== | ==== Filtros (filter) ==== | ||
- | Definir | + | Una función tipo filtro toma una lista y devuelve los elementos de esa lista que cumplen con un determinado predicado. |
+ | |||
+ | Veamos funciones de tipo filtro que hemos creado: | ||
+ | |||
+ | < | ||
+ | soloTrue :: [Bool] -> [Bool] | ||
+ | soloTrue [] = [] | ||
+ | soloTrue (x:xs) | x == True = x : soloTrue xs | ||
+ | | otherwise = | ||
+ | </ | ||
+ | |||
+ | < | ||
+ | soloPares :: [Int] -> [Int] | ||
+ | soloPares [] = [] | ||
+ | soloPares (x:xs) | esPar x | ||
+ | | otherwise = soloPares xs | ||
+ | |||
+ | esPar :: Int -> Bool | ||
+ | esPar x = mod x 2 == 0 | ||
+ | </ | ||
+ | |||
+ | < | ||
+ | empiezaM :: [String] -> [String] | ||
+ | empiezaM [] = [] | ||
+ | empiezaM (x:xs) | primeraEsM x = x : empiezaM xs | ||
+ | | otherwise | ||
+ | |||
+ | primeraEsM :: [Char] -> Bool | ||
+ | primeraEsM xs = head xs == ' | ||
+ | </ | ||
+ | |||
+ | ¿Cuál es la estructura que tienen en común estas funciones? | ||
+ | |||
+ | __________ :: [a] -> [a] | ||
+ | __________ [] = [] | ||
+ | __________ (x:xs) | ______ x = x : _____________ xs | ||
+ | | otherwise = _____________ xs | ||
+ | |||
+ | Ahora que sabemos esto, resolvamos | ||
==== Acumuladores (foldr) ==== | ==== Acumuladores (foldr) ==== | ||
- | sumatoria | + | Los acumuladores son un tipo de funciones que toman cada uno de los elementos de una lista y hacen alguna operación para acumular ese elemento con el resultado de hacer lo mismo con el resto de la lista. |
- | productoria | + | Más y más funciones fueron acumuladores, |
- | (el neutro) | + | < |
+ | sumatoria :: [Int] -> Int | ||
+ | sumatoria [] = 0 | ||
+ | sumatoria | ||
+ | </ | ||
- | Definir la función '' | + | < |
+ | productoria | ||
+ | productoria | ||
+ | productoria (x: | ||
- | Definir la función '' | + | producto |
+ | producto x y = x*y | ||
+ | </code> | ||
- | * Definir la función '' | + | < |
+ | concatenaInt | ||
+ | concatenaInt [] = [] | ||
+ | concatenaInt (xs:xss) = (++) xs (concatenaInt xss) | ||
+ | </code> | ||
- | * Definir | + | ¿Cuál es la estructura común de todas estas funciones? Para poder verla bien, debemos saber que todo operador que usemos entre sus dos argumentos, como //+//, //:// o //++//, se puede usar también con la misma sintaxis |
+ | sumatoria (x:xs) = (+) x (sumatoria xs) | ||
- | ===== Mezclando diferentes tipos ===== | + | Donde el primer número a sumar es //x// y el segundo es //sumatoria xs//, es decir, el resultado de resolver la función //sumatoria xs//. |
- | * Definir la función '' | + | Entonces, la estructura común |
- | * Definir la función // | + | |
- | * Definir la función '' | + | |
- | * Definir la función '' | + | |
- | ===== Múltiples casos base ===== | + | _________ :: [a] -> b |
+ | _________ [] = _____ | ||
+ | _________ (x: | ||
+ | |||
+ | |||
+ | Como el resultado de la función no es una lista, el resultado del caso base no es una lista vacía. El resultado del caso base debe ser definido para cada función, ya que debe ser el **neutro del operador** que se utilice en la función. | ||
+ | |||
+ | Ahora que sabemos esto, resolvamos la función '' | ||
+ | |||
+ | Otro ejemplo: la función '' | ||
+ | |||
+ | Más ejercicios: la función '' | ||
+ | |||
+ | Y finalmente, la función '' | ||
+ | |||
+ | |||
+ | ===== Combinando diferentes tipos ===== | ||
+ | |||
+ | Muchas funciones necesitan hacer cosas complejas, y para eso combinan dos o más de los tipos de funciones recursivas en listas que hemos visto. Veamos los siguientes ejemplos: | ||
+ | |||
+ | La función '' | ||
+ | < | ||
+ | sumaPares :: [Int] -> Int | ||
+ | sumaPares [] = 0 | ||
+ | sumaPares (x:xs) | mod x 2 == 0 = x + sumaPares xs | ||
+ | | otherwise | ||
+ | </ | ||
+ | Fíjense que el resultado es el resultado propio de un acumulador, es decir, no es una lista, por lo tanto para el caso base tenemos que usar el neutro de la operación, en este caso, como la operación es la suma, el resultado para el caso base es el 0. | ||
+ | |||
+ | También podríamos definir la función de forma que queden más separados el filtro y el acumulador, definiendo las funciones por separado y usándolas luego combinadas, primero aplicando el filtro y luego sumando los elementos que quedaron seleccionados en el filtro. Para indicar que primero se tiene que aplicar una operación y después la otra, usamos paréntesis: | ||
+ | < | ||
+ | sumaPares' | ||
+ | sumaPares' | ||
+ | |||
+ | pares :: [Int] -> [Int] | ||
+ | pares [] = [] | ||
+ | pares (x:xs) | mod x 2 == 0 = x : pares xs | ||
+ | | otherwise | ||
+ | |||
+ | suma :: [Int] -> Int | ||
+ | suma [] = 0 | ||
+ | suma (x:xs) = x + suma xs | ||
+ | </ | ||
+ | Fíjense que ahora no tenemos que definir caso base y caso recursivo para definir '' | ||
+ | |||
+ | Pero no les resultan muy familiares estas funciones '' | ||
+ | < | ||
+ | sumaPares'' | ||
+ | sumaPares'' | ||
+ | </ | ||
+ | Corto, fácil de leer y reusando código! Qué más queremos! ;-) | ||
+ | |||
+ | Veamos otros ejemplos: la función '' | ||
+ | |||
+ | Cómo calculamos la función '' | ||
===== Recursión en dos argumentos ===== | ===== Recursión en dos argumentos ===== | ||
+ | |||
+ | La recursión en dos argumentos aplica los mismos principios de la recursión, pero a dos argumentos a la vez. Es decir, en un mismo paso de recursión, vamos a consumir dos estructuras de datos, por ejemplo, dos listas. En cualquiera de los pasos, puede suceder que sea vacía una de las estructuras, | ||
+ | |||
+ | Veamos como ejemplo la función // | ||
+ | |||
+ | abrochar :: [a] -> [b] -> [(a,b)] | ||
+ | abrochar [] [] = [] | ||
+ | abrochar (x:xs) [] = [] | ||
+ | abrochar [] (y: | ||
+ | abrochar (x:xs) (y:ys) = (x,y) : abrochar xs ys | ||
+ | |||
+ | Como 3 de los 4 casos de // | ||
+ | |||
+ | abrochar' | ||
+ | abrochar' | ||
+ | abrochar' | ||
+ | |||
+ | La función //nesimo//, que devuelve el n-ésimo elemento de una lista (comenzando de 0), también se puede definir como recursiva en dos argumentos, donde uno es una lista y el otro un natural: | ||
+ | |||
+ | nesimo :: Int -> [a] -> a | ||
+ | nesimo _ [] = error "la lista está vacía" | ||
+ | nesimo 0 (x: | ||
+ | nesimo (n+1) (x:xs) = nesimo n xs | ||
+ | |||
+ | Vemos que para esta función sólo hay que definir 3 casos porque el caso //nesimo 0 []// cae dentro del caso //nesimo _ []//. Vemos también que en el caso recursivo no se devuelve ningún resultado, sino que lo que se hace es recorrer la lista según va indicando el número, reduciendo el número en 1 cada vez que se recorre un elemento de la lista, hasta que se llega al punto indicado por el número, que se habrá convertido en 0, y ahí se devuelve el elemento correspondiente, | ||
+ | |||
+ | Veamos ahora una función un poco más compleja: // | ||
+ | |||
+ | encuentraEstafador :: [Int] -> [Int] -> [Int] | ||
+ | encuentraEstafador [] _ = [] | ||
+ | encuentraEstafador (x:xs) ys | existe x ys = x : encuentraEstafador xs ys | ||
+ | | otherwise | ||
+ | |||
+ | Esta versión es poco eficiente porque para cada elemento de la primera lista tenemos que recorrer toda la segunda lista. Sin embargo, si suponemos que las listas están ordenadas, podríamos aprovechar este hecho para recorrerlas a ambas de forma más eficiente. | ||
====== Ejercicios ====== | ====== Ejercicios ====== | ||
+ | ==== Repaso de prolog ==== | ||
+ | |||
+ | * A partir de la siguiente base de conocimiento en prolog, crear las reglas necesarias para que el intérprete nos diga si una persona alérgica a la frutilla puede comer helado de frutilla: | ||
+ | < | ||
+ | esAlérgico(pepe, | ||
+ | esAlérgico(juan, | ||
+ | esAlérgico(maría, | ||
+ | |||
+ | tiene(helado, | ||
+ | tiene(torta, | ||
+ | </ | ||
+ | |||
+ | ==== Repaso de haskell ==== | ||
+ | |||
+ | * Definir la función **estanOrdenados**, | ||
+ | |||
+ | * Definir la función **cuadradoOcubo**, | ||
+ | |||
+ | * Definir la función **dobleONada**, | ||
+ | |||
+ | * Definir la función **multiplo**, | ||
+ | |||
+ | * Definir la función **restaPositiva**, | ||
+ | |||
+ | * Definir la función **esSigno**, | ||
+ | Ejemplo: esSigno ``aries'' | ||
+ | Ejemplo: esSigno ``leo'' | ||
+ | |||
+ | * Definir la función **cuatroCabezas**, | ||
+ | Ayuda: se puede usar la función " | ||
+ | |||
+ | ==== Funciones recursivas ==== | ||
+ | |||
+ | * Definir la función **librosRecomendados**, | ||
+ | Ejemplo: librosRecomendados [(``El nombre de la Rosa'', | ||
+ | |||
+ | * Definir la función **peliculasAccion**, | ||
+ | Ejemplo: peliculasAccion [(" | ||
+ | |||
+ | * Definir la función **cuadrados**, | ||
+ | |||
+ | * Definir la función **soloEntre0y9**, | ||
+ | |||
+ | * Definir la función **sumatoriaNegativa**, | ||
+ | Ejemplo: sumatoriaNegativa [1,2,3] = -6. | ||
+ | |||
+ | * Definir la función **productoTriplas**, | ||
+ | |||
+ | * Definir la función **suma2ysuma**, | ||
+ | Ejemplo: suma2ysuma [1,2,3] = 12. | ||
+ | |||
+ | * Definir la función **sumatoriaEsPar**, | ||
+ | Ejemplo: sumatoriaEsPar [1,2,3,4] = True. | ||
+ | |||
+ | * Definir la función **cuantos0**, | ||
+ | Ayuda: recordar cómo se hizo la función " | ||
+ | |||
+ | * Definir la función **cuantosEntre**, | ||
+ | Ejemplo: cuantosEntre 30 40 [15, | ||
+ | |||
+ | * Definir la función **paresHasta**, | ||
+ | |||
+ | * Definir la función **multiplica**, | ||
+ | |||
+ | * Definir la función // | ||
+ | |||
+ | * Definir la función // | ||
+ | |||
+ | |||
+ | ===Ejercicios de recursión en dos argumentos=== | ||
+ | |||
+ | * Definir //iguales :: Eq a => [a] -> [a] -> Bool// que decide si dos listas son iguales. | ||
+ | |||
+ | * Definir // | ||
+ | |||
+ | * Activar con '': | ||
+ | |||
+ | * Supongamos ahora que el listado de las personas que reciben subsidio por desempleo es una lista de tuplas // | ||
+ | < | ||
+ | listadoSubsidio :: [(Int, | ||
+ | listadoSubsidio = [(23763956," | ||
+ | listadoMuni :: [(String, | ||
+ | listadoMuni = [(" | ||
+ | </ |
introalg/taller09_6.1241398223.txt.gz · Última modificación: 2018/08/10 03:03 (editor externo)