introalg:taller09_4
Diferencias
Muestra las diferencias entre dos versiones de la página.
Ambos lados, revisión anteriorRevisión previaPróxima revisión | Revisión previa | ||
introalg:taller09_4 [2009/04/19 23:11] – laura | introalg:taller09_4 [2018/08/10 03:03] (actual) – editor externo 127.0.0.1 | ||
---|---|---|---|
Línea 1: | Línea 1: | ||
- | ====== | + | ====== |
- | =====Comparación | + | =====Tipos de datos y comparación |
+ | |||
+ | ====Tipos==== | ||
+ | |||
+ | En la clase anterior vimos que haskell es un lenguaje tipado. La definición de una función suele venir siempre precedida de su //signatura de tipos//, que tiene la siguiente forma: | ||
+ | < | ||
+ | nombreFunción :: TipoArgumento1 -> TipoArgumento2 ... -> TipoArgumentoN -> TipoResultado | ||
+ | </ | ||
+ | Por ejemplo: | ||
+ | < | ||
+ | ordena :: (Int,Int) -> (Int,Int) | ||
+ | ambospositivos :: Int -> Int -> Bool | ||
+ | edad :: (Int, Int, Int) -> (Int, Int, Int) -> Int | ||
+ | cabeza :: [a] -> a | ||
+ | cola :: [a] -> [a] | ||
+ | esVaciaOPrimer0 :: [Int] -> Bool | ||
+ | segundoEsSegundo :: (Int,Int) -> [Int] -> Bool | ||
+ | recortaDia :: [Char] -> [Char] | ||
+ | </ | ||
+ | En estos ejemplos hemos visto los tipos más básicos con los que vamos a trabajar en la clase: enteros '' | ||
+ | |||
+ | ====Patrones==== | ||
+ | |||
+ | Vimos también que en haskell se puede expresar muy naturalmente la definición de una función mediante análisis por casos. Estos casos son lo que en haskell se conoce como // | ||
+ | < | ||
+ | nombreFunción arg1 arg2 arg3 | patron1 = resultado1 | ||
+ | | patron2 = resultado2 | ||
+ | | patron3 = resultado3 | ||
+ | ... | ||
+ | </ | ||
+ | Los patrones son expresiones booleanas, que en general involucran a las variables a las que se asigna nombre al definir la función ('' | ||
+ | < | ||
+ | nombreFunción arg1 arg2 arg3 | arg1 + arg2 == arg3 = arg3 | ||
+ | | arg1 - arg2 == arg3 = (-arg3) | ||
+ | | arg1 + arg2 /= arg3 = error "el tercer argumento no es ni la suma ni la resta de los dos primeros" | ||
+ | </ | ||
+ | |||
+ | Para resolver una expresión, el intérprete de haskell funciona de forma muy parecida al de prolog: dada una expresión, el intérprete busca una definición de función con la que pueda unificar, de la misma forma que el intérprete de prolog buscaba una regla con cuya cabeza pudiera unificar la expresión. Luego, si esta definición tiene patrones, el intérprete se fija si esos patrones evalúan a '' | ||
+ | |||
+ | Veamos algunos ejemplos de definición de funciones mediante análisis por casos en patrones, y luego veamos cómo funcionan los mecanismos de unificación. | ||
+ | |||
+ | La función '' | ||
+ | < | ||
+ | ordena :: (Int,Int) -> (Int,Int) | ||
+ | ordena (x,y) | x < y = (x,y) | ||
+ | | ||
+ | | ||
+ | </ | ||
+ | Veamos que hemos hecho una sola definición para la función, en la que hemos asignado nombres de variables a su argumento, llamando '' | ||
+ | |||
+ | Dada una expresión como '' | ||
+ | |||
+ | Fíjense que habría otras formas de definir la función, más legibles, más cortas y más eficientes, ya que algunos casos se evaluarían a '' | ||
+ | < | ||
+ | ordena' | ||
+ | ordena' | ||
+ | | x > y = (y,x) | ||
+ | </ | ||
+ | Efectivamente, | ||
+ | |||
+ | La función '' | ||
+ | < | ||
+ | ambospositivos :: Int -> Int -> Bool | ||
+ | ambospositivos x y | x >= 0 && y >= 0 = True | ||
+ | | x >= 0 && y < 0 = False | ||
+ | | x < 0 && y >= 0 = False | ||
+ | | x < 0 && y < 0 = False | ||
+ | </ | ||
+ | Si nos fijamos un poco, vemos que los tres últimos patrones se pueden colapsar en uno solo, usando un " | ||
+ | < | ||
+ | ambospositivos' | ||
+ | ambospositivos' | ||
+ | | x >= 0 || y < 0 = False | ||
+ | </ | ||
+ | Pero además, vemos que también podemos expresar lo mismo de otra forma: que nos devuelva '' | ||
+ | < | ||
+ | ambospositivos'' | ||
+ | ambospositivos'' | ||
+ | | otherwise | ||
+ | </ | ||
+ | Pero si reflexionamos bien bien sobre el asunto, nos damos cuenta de que en realidad, si el resultado que buscamos es un booleano, ya lo teníamos, delante de nuestros ojos: el primer patrón es un booleano que evalúa a '' | ||
+ | < | ||
+ | ambospositivos''' | ||
+ | ambospositivos''' | ||
+ | </ | ||
+ | |||
+ | La función '' | ||
+ | < | ||
+ | edad :: (Int, Int, Int) -> (Int, Int, Int) -> Int | ||
+ | edad (diaN, | ||
+ | | otherwise | ||
+ | </ | ||
+ | |||
+ | Las funciones '' | ||
+ | < | ||
+ | cabeza :: [a] -> a | ||
+ | cabeza (x:xs) = x | ||
+ | </ | ||
+ | |||
+ | Pero qué pasa si decimos '' | ||
+ | < | ||
+ | cabeza' | ||
+ | cabeza' | ||
+ | cabeza' | ||
+ | </ | ||
+ | |||
+ | Fíjense que para la definición de '' | ||
+ | < | ||
+ | cabeza'' | ||
+ | cabeza'' | ||
+ | cabeza'' | ||
+ | </ | ||
+ | |||
+ | Pero incluso podemos usar el patrón irrefutable en el segundo caso, ya que éste sólo se aplica si el primero falla, y si el primero falla, claramente se tratará de una lista vacía: | ||
+ | < | ||
+ | cabeza''' | ||
+ | cabeza''' | ||
+ | cabeza''' | ||
+ | </ | ||
+ | Pero esta última solución presenta el inconveniente de que resulta más difícil de leer, así que no deberíamos usarla. | ||
+ | |||
+ | En la definición de '' | ||
+ | < | ||
+ | cola :: [a] -> [a] | ||
+ | cola (_:xs) = xs | ||
+ | cola [] = [] | ||
+ | </ | ||
+ | Como el tipo del resultado es lista, para el caso de la lista vacía podemos devolver como resultado una lista vacía, o bien un error, como se prefiera. | ||
=====Modularización===== | =====Modularización===== | ||
Para hacer un programa que resuelva un problema complejo, lo mejor es analizar bien el problema para dividirlo en partes más chicas. Solucionaremos estas partes más chicas cada una por separado, comprobando que efectivamente cada una hace lo que esperamos que haga. Finalmente, las uniremos para llevar a cabo la tarea compleja que nos planteábamos al principio. A esta estrategia se la conoce como // | Para hacer un programa que resuelva un problema complejo, lo mejor es analizar bien el problema para dividirlo en partes más chicas. Solucionaremos estas partes más chicas cada una por separado, comprobando que efectivamente cada una hace lo que esperamos que haga. Finalmente, las uniremos para llevar a cabo la tarea compleja que nos planteábamos al principio. A esta estrategia se la conoce como // | ||
+ | |||
+ | ==== ej.1: '' | ||
+ | |||
+ | Por ejemplo, para la función de la edad tenemos que suponer que las fechas están siempre bien formadas y que la primera es menor o igual a la segunda. Podríamos incluir esas comprobaciones dentro de la función '' | ||
+ | < | ||
+ | edad' :: (Int, Int, Int) -> (Int, Int, Int) -> Int | ||
+ | edad' (diaN, | ||
+ | | compruebaParametros (diaN, | ||
+ | </ | ||
+ | Podemos usar nombres más cortos para que quede toda la función dentro de una línea, pero entonces nos resultará más difícil recordar qué representa cada variable o función: | ||
+ | < | ||
+ | e :: (Int, Int, Int) -> (Int, Int, Int) -> Int | ||
+ | e (d,m,a) (f,n,b) | c (d,m,a) (f,n,b) && (m < n || (m == n && d < f) = ( b - a ) - 1 | ||
+ | | c (d,m,a) (f, | ||
+ | </ | ||
+ | |||
+ | Veamos ahora cómo podríamos plantear la función '' | ||
+ | < | ||
+ | compruebaParametros :: (Int, Int, Int) -> (Int, Int, Int) -> Bool | ||
+ | compruebaParametros (diaN, | ||
+ | </ | ||
+ | |||
+ | Pero cuidado! esta función permitiría un día 31 de febrero! cómo podemos hacerla más específica? | ||
+ | < | ||
+ | compruebaParametros' | ||
+ | compruebaParametros' | ||
+ | | mesN == 2 = diaN > 0 && diaN <= 28 && diaA > 0 && diaA <= 31 && mesA > 0 && mesA <= 12 && anioN <= anioA = True | ||
+ | | mesA == 2 = diaN > 0 && diaN <= 31 && diaA > 0 && diaA <= 28 && mesN > 0 && mesN <= 12 && anioN <= anioA = True | ||
+ | | otherwise | ||
+ | </ | ||
+ | |||
+ | Y qué pasa con los meses que tienen 30 días y no 31? y con los años bisiestos? la función se complica... si queremos que resulte legible y modificable, | ||
+ | < | ||
+ | compruebaParametros'' | ||
+ | compruebaParametros'' | ||
+ | |||
+ | compruebaMesDia :: (Int, | ||
+ | compruebaMesDia (mes, | ||
+ | | mes == 4 || mes == 6 || mes == 9 || mes == 11 = dia > 0 && dia <= 30 | ||
+ | | otherwise | ||
+ | |||
+ | compruebaAnterior :: (Int, Int, Int) -> (Int, Int, Int) -> Bool | ||
+ | compruebaAnterior (diaN, | ||
+ | | anioA > anioN = True | ||
+ | | otherwise | ||
+ | </ | ||
+ | |||
+ | Como ven, un problema complejo resulta mucho más fácil de abordar si hacemos el esfuerzo inicial de analizarlo con detenimiento, | ||
+ | |||
+ | ==== ej.2: supermercado ==== | ||
+ | |||
+ | Imaginemos un elaborado sistema de descuentos en un supermercado. Por ejemplo, imaginemos que el super hace un descuento del 20% en el monto total si hacemos una compra de 257 pesos, de 350 pesos, de 489 pesos o mayor a 1000. En cambio, si hacemos una compra menor a 100 pesos el descuento es del 5%. Si el monto está entre 200 y 500 pesos, pero no es ninguno de los anteriores, el descuento es del 10%, si es mayor a 500 pero menor a 1000, el descuento es del 15%, excepto si se trata de una compra de 768 pesos, de 999 o de 573, que tienen un descuento del 20 por ciento. Además, los descuentos también dan puntos: los del 20% aportan un número de puntos 10 veces mayor al monto total de la compra, los del 15% aportan un número de puntos 5 veces mayor al monto total de la compra, los del 10% aportan el doble de puntos que el monto de la compra, y si la compra es menor a 200 pesos, el número de puntos es igual al monto de la compra. Por último, si compramos con tickets, en un descuento del 20% nos devuelven el 10% del monto total, en un descuento del 15% nos devuelven el 5% y en un descuento del 10% nos devuelven en 2%. La devolución en tickets se devuelve en tickets, no se aplica un descuento al monto total a pagar. | ||
+ | |||
+ | Cómo podemos hacer un programa que nos calcule el descuento, los puntos y el descuento en tickets para un monto dado? Veamos una función que traduzca literalmente lo que dijimos acá arriba. | ||
+ | |||
+ | < | ||
+ | descuento :: (Float, | ||
+ | -- argumento: un par que representa el monto (Float) y si tenemos tickets (Bool) | ||
+ | -- resultado: una tresupla (Float, | ||
+ | -- restando el descuento correspondiente, | ||
+ | -- y el descuento en tickets, respectivamente. | ||
+ | |||
+ | descuento (x,y) | x == 257 && y = ( x - 0.2*x , 10*x, 0.1*x) | ||
+ | | x == 257 && not y = ( x - 0.2*x , 10*x, 0) | ||
+ | | x == 350 && y = ( x - 0.2*x , 10*x, 0.1*x) | ||
+ | | x == 350 && not y = ( x - 0.2*x , 10*x, 0) | ||
+ | | x == 489 && y = ( x - 0.2*x , 10*x, 0.1*x) | ||
+ | | x == 489 && not y = ( x - 0.2*x , 10*x, 0) | ||
+ | | x > 1000 && y = ( x - 0.2*x , 10*x, 0.1*x) | ||
+ | | x > 1000 && not y = ( x - 0.2*x , 10*x, 0) | ||
+ | | x == 768 && y = ( x - 0.2*x , 10*x, 0.1*x) | ||
+ | | x == 768 && not y = ( x - 0.2*x , 10*x, 0) | ||
+ | | x == 999 && y = ( x - 0.2*x , 10*x, 0.1*x) | ||
+ | | x == 573 && not y = ( x - 0.2*x , 10*x, 0) | ||
+ | | x > 500 && x <= 1000 && y = ( x - 0.15*x , 5*x, 0.05*x) | ||
+ | | x > 500 && x <= 1000 && not y = ( x - 0.15*x , 5*x, 0) | ||
+ | | x > 200 && x <= 500 && y = ( x - 0.1*x , 2*x, 0.02*x) | ||
+ | | x > 200 && x <= 500 && not y = ( x - 0.1*x , 2*x, 0) | ||
+ | | x <= 200 = ( x , x , 0) | ||
+ | </ | ||
+ | |||
+ | Como pueden ver, este código es muy grande, bastante ilegible y difícil de mantener. Qué haríamos, por ejemplo, si ahora el supermercado decidiera reducir el número de puntos que dá a los descuentos del 20%? Aplicando la estrategia " | ||
+ | |||
+ | < | ||
+ | descuento' | ||
+ | descuento' | ||
+ | | x > 1000 = descuento20 (x,y) | ||
+ | | x > 500 && x <= 1000 = descuento15 (x,y) | ||
+ | | x > 200 && x <= 500 = descuento10 (x,y) | ||
+ | | x <= 200 = descuento0 (x,y) | ||
+ | |||
+ | descuento20 :: (Float, | ||
+ | descuento20 (x,y) | y == True = ( x - 0.2*x , 10*x, 0.1*x) | ||
+ | | y == False = ( x - 0.2*x , 10*x, 0) | ||
+ | |||
+ | descuento15 :: (Float, | ||
+ | descuento15 (x,y) | y == True = ( x - 0.15*x , 5*x, 0.05*x) | ||
+ | | y == False = ( x - 0.15*x , 5*x, 0) | ||
+ | |||
+ | descuento10 :: (Float, | ||
+ | descuento10 (x,y) | y == True = ( x - 0.1*x , 2*x, 0.02*x) | ||
+ | | y == False = ( x - 0.1*x , 2*x, 0) | ||
+ | |||
+ | descuento0 :: (Float, | ||
+ | descuento0 (x,y) = ( x , x , 0) | ||
+ | </ | ||
+ | |||
+ | ==== ej.3: '' | ||
+ | |||
+ | Tomemos como ejemplo la función '' | ||
+ | | ||
+ | Suponer el siguiente juego: m jugadores en ronda comienzan a decir los números naturales consecutivamente. | ||
+ | Cuando toca un multiplo de 7 o un número con algún dígito igual a 7, el jugador debe decir pip en lugar del número. | ||
+ | |||
+ | Se pide: encontrar un predicado '' | ||
+ | |||
+ | Con un complicado juego de '' | ||
+ | |||
+ | < | ||
+ | pip :: Int -> Bool | ||
+ | pip n = n `mod` 7 == 0 || | ||
+ | n `mod` 10 == 7 || (n `div` 10) `mod` 10 == 7 || | ||
+ | (n `div` 100) `mod` 10 == 7 || (n `div` 1000) `mod` 10 == 7 | ||
+ | |||
+ | </ | ||
+ | |||
+ | Si en cambio dividimos el problema en partes, obtenemos una versión mucho más simple de comprender. | ||
+ | |||
+ | < | ||
+ | pip' :: Int -> Bool | ||
+ | pip' n = n `mod` 7 == 0 || unidad n == 7 || decena n == 7 || centena n == 7 || unidadDeMil n == 7 | ||
+ | |||
+ | unidad, decena, centena, unidadDeMil :: Int -> Int | ||
+ | unidad n = n `mod` 10 | ||
+ | decena n = (n `div` 10) `mod` 10 | ||
+ | centena n = (n `div` 100) `mod` 10 | ||
+ | unidadDeMil n = (n `div` 1000) `mod` 10 | ||
+ | </ | ||
+ | |||
+ | Siempre es conveniente ser desconfiado y probamos que ambas funciones sean iguales en el rango // | ||
+ | |||
+ | Main> filter pip [0..9999] == filter pip' [0..9999] | ||
+ | True | ||
+ | |||
=====Definiciones locales===== | =====Definiciones locales===== | ||
- | A veces queremos modularizar pero queremos que los módulos sólo estén disponibles para el programa que estamos haciendo en este momento, y no para todo el resto. Por ejemplo, si queremos definir la función "'' | + | A veces queremos modularizar pero queremos que los módulos sólo estén disponibles para el programa que estamos haciendo en este momento, y no para todo el resto. Por ejemplo, si queremos definir la función "'' |
Podemos definir módulos (en el caso de haskell, funciones) de forma que sólo las pueda usar un determinado programa, definiéndolas localmente. Vimos un ejemplo de esto en la clase anterior, para definir la función que calcula el área de un prisma: | Podemos definir módulos (en el caso de haskell, funciones) de forma que sólo las pueda usar un determinado programa, definiéndolas localmente. Vimos un ejemplo de esto en la clase anterior, para definir la función que calcula el área de un prisma: | ||
Línea 35: | Línea 306: | ||
ERROR " | ERROR " | ||
</ | </ | ||
- | |||
- | Funciones locales vs. globales | ||
===== Ejercicios ===== | ===== Ejercicios ===== | ||
+ | Si no lo hicieron el día anterior, traten de resolver los siguientes problemas: | ||
+ | |||
+ | - '' | ||
+ | - '' | ||
+ | - '' | ||
+ | - Definan, según su propio criterio para otorgar becas, la función '' | ||
+ | |||
+ | Nuevos problemas: | ||
+ | |||
+ | - Definir una función '' | ||
+ | - Definir la función '' | ||
+ | |||
+ | Viejos problemas, nuevas soluciones: re-piensen los problemas difíciles que planteamos en la segunda clase de prolog usando modularización: | ||
+ | |||
+ | - ¿Cómo sería un programa para recomendar amigos en una red social tipo // | ||
+ | - Tenemos una lista de las conexiones por tren entre pares de ciudades, por ejemplo '' |
introalg/taller09_4.1240182691.txt.gz · Última modificación: 2018/08/10 03:03 (editor externo)