-- I1M 2021-22: Rel_2_sol.hs
-- Definiciones con condicionales, guardas o patrones.
-- Departamento de Ciencias de la Computación e I.A.
-- Universidad de Sevilla
-- =====================================================================
-- ---------------------------------------------------------------------
-- Introducción --
-- ---------------------------------------------------------------------
-- En esta relación se presentan ejercicios con definiciones elementales
-- (no recursivas) de funciones que usan condicionales, guardas o
-- patrones.
--
-- Estos ejercicios se corresponden con el tema 4 que se encuentran en
-- http://www.cs.us.es/~jalonso/cursos/i1m-17/temas/tema-4.html
import Test.QuickCheck
-- ---------------------------------------------------------------------
-- Ejercicio 1. Definir la función
-- divisionSegura :: Double -> Double -> Double
-- tal que (divisionSegura x y) es x/y si y no es cero y 9999 en caso
-- contrario. Por ejemplo,
-- divisionSegura 7 2 == 3.5
-- divisionSegura 7 0 == 9999.0
-- ---------------------------------------------------------------------
divisionSegura :: Double -> Double -> Double
divisionSegura _ 0 = 9999
divisionSegura x y = x/y
-- ---------------------------------------------------------------------
-- Ejercicio 2.1. La disyunción excluyente xor de dos fórmulas se
-- verifica si una es verdadera y la otra es falsa. Su tabla de verdad
-- es
-- x | y | xor x y
-- ------+-------+---------
-- True | True | False
-- True | False | True
-- False | True | True
-- False | False | False
--
-- Definir la función
-- xor1 :: Bool -> Bool -> Bool
-- tal que (xor1 x y) es la disyunción excluyente de x e y, calculada a
-- partir de la tabla de verdad. Usar 4 ecuaciones, una por cada línea
-- de la tabla.
-- ---------------------------------------------------------------------
xor1 :: Bool -> Bool -> Bool
xor1 True True = False
xor1 True False = True
xor1 False True = True
xor1 False False = False
-- ---------------------------------------------------------------------
-- Ejercicio 2.2. Definir la función
-- xor2 :: Bool -> Bool -> Bool
-- tal que (xor2 x y) es la disyunción excluyente de x e y, calculada a
-- partir de la tabla de verdad y patrones. Usar 2 ecuaciones, una por
-- cada valor del primer argumento.
-- ---------------------------------------------------------------------
xor2 :: Bool -> Bool -> Bool
xor2 True y = not y
xor2 False y = y
-- ---------------------------------------------------------------------
-- Ejercicio 2.3. Definir la función
-- xor3 :: Bool -> Bool -> Bool
-- tal que (xor3 x y) es la disyunción excluyente de x e y, calculada
-- a partir de la disyunción (||), conjunción (&&) y negación (not).
-- Usar 1 ecuación.
-- ---------------------------------------------------------------------
-- 1ª definición:
xor3 :: Bool -> Bool -> Bool
xor3 x y = (x || y) && not (x && y)
-- 2ª definición:
xor3b :: Bool -> Bool -> Bool
xor3b x y = (x && not y) || (y && not x)
-- ---------------------------------------------------------------------
-- Ejercicio 2.4. Definir la función
-- xor4 :: Bool -> Bool -> Bool
-- tal que (xor4 x y) es la disyunción excluyente de x e y, calculada
-- a partir de desigualdad (/=). Usar 1 ecuación.
-- ---------------------------------------------------------------------
xor4 :: Bool -> Bool -> Bool
xor4 x y = x /= y
-- ---------------------------------------------------------------------
-- Ejercicio 3. Las dimensiones de los rectángulos puede representarse
-- por pares; por ejemplo, (5,3) representa a un rectángulo de base 5 y
-- altura 3.
--
-- Definir la función
-- mayorRectangulo :: (Num a, Ord a) => (a,a) -> (a,a) -> (a,a)
-- tal que (mayorRectangulo r1 r2) es el rectángulo de mayor área entre
-- r1 y r2. Por ejemplo,
-- mayorRectangulo (4,6) (3,7) == (4,6)
-- mayorRectangulo (4,6) (3,8) == (4,6)
-- mayorRectangulo (4,6) (3,9) == (3,9)
-- ---------------------------------------------------------------------
mayorRectangulo :: (Num a, Ord a) => (a,a) -> (a,a) -> (a,a)
mayorRectangulo (a,b) (c,d) | a*b >= c*d = (a,b)
| otherwise = (c,d)
-- ---------------------------------------------------------------------
-- Ejercicio 4. Definir la función
-- intercambia :: (a,b) -> (b,a)
-- tal que (intercambia p) es el punto obtenido intercambiando las
-- coordenadas del punto p. Por ejemplo,
-- intercambia (2,5) == (5,2)
-- intercambia (5,2) == (2,5)
-- ---------------------------------------------------------------------
intercambia :: (a,b) -> (b,a)
intercambia (x,y) = (y,x)
-- ---------------------------------------------------------------------
-- Ejercicio 5. Definir la función
-- distancia :: (Double,Double) -> (Double,Double) -> Double
-- tal que (distancia p1 p2) es la distancia entre los puntos p1 y
-- p2. Por ejemplo,
-- distancia (1,2) (4,6) == 5.0
-- ---------------------------------------------------------------------
distancia :: (Double,Double) -> (Double,Double) -> Double
distancia (x1,y1) (x2,y2) = sqrt((x1-x2)**2+(y1-y2)**2)
-- ---------------------------------------------------------------------
-- Ejercicio 6. Definir una función
-- ciclo :: [a] -> [a]
-- tal que (ciclo xs) es la lista obtenida permutando cíclicamente los
-- elementos de la lista xs, pasando el último elemento al principio de
-- la lista. Por ejemplo,
-- ciclo [2,5,7,9] == [9,2,5,7]
-- ciclo [] == []
-- ciclo [2] == [2]
-- ---------------------------------------------------------------------
ciclo :: [a] -> [a]
ciclo [] = []
ciclo xs = last xs : init xs
-- ---------------------------------------------------------------------
-- Ejercicio 7. Definir la función
-- numeroMayor :: (Num a, Ord a) => a -> a -> a
-- tal que (numeroMayor x y) es el mayor número de dos cifras que puede
-- construirse con los dígitos x e y. Por ejemplo,
-- numeroMayor 2 5 == 52
-- numeroMayor 5 2 == 52
-- ---------------------------------------------------------------------
-- 1ª definición:
numeroMayor :: (Num a, Ord a) => a -> a -> a
numeroMayor x y = 10 * max x y + min x y
-- 2ª definición:
numeroMayor2 :: (Num a, Ord a) => a -> a -> a
numeroMayor2 x y | x > y = 10*x+y
| otherwise = 10*y+x
-- ---------------------------------------------------------------------
-- Ejercicio 8. Definir la función
-- numeroDeRaices :: (Floating t, Ord t) => t -> t -> t -> Int
-- tal que (numeroDeRaices a b c) es el número de raíces reales de la
-- ecuación a*x^2 + b*x + c = 0. Por ejemplo,
-- numeroDeRaices 2 0 3 == 0
-- numeroDeRaices 4 4 1 == 1
-- numeroDeRaices 5 23 12 == 2
-- Nota: Se supone que a es no nulo.
-- ---------------------------------------------------------------------
numeroDeRaices :: Double -> Double -> Double -> Int
numeroDeRaices a b c | d < 0 = 0
| d == 0 = 1
| otherwise = 2
where d = b**2-4*a*c
-- 2ª solución
numeroDeRaices2 :: Double -> Double -> Double -> Int
numeroDeRaices2 a b c = 1 + round (signum (b**2-4*a*c))
-- ---------------------------------------------------------------------
-- Ejercicio 9. Definir la función
-- raices :: Double -> Double -> Double -> [Double]
-- tal que (raices a b c) es la lista de las raíces reales de la
-- ecuación ax^2 + bx + c = 0. Por ejemplo,
-- raices 1 3 2 == [-1.0,-2.0]
-- raices 1 (-2) 1 == [1.0,1.0]
-- raices 1 0 1 == []
-- Nota: Se supone que a es no nulo.
-- ---------------------------------------------------------------------
raices :: Double -> Double -> Double -> [Double]
raices a b c
| d >= 0 = [(-b+e)/t,(-b-e)/t]
| otherwise = []
where d = b**2 - 4*a*c
e = sqrt d
t = 2*a
-- ---------------------------------------------------------------------
-- Ejercicio 10. En geometría, la fórmula de Herón, descubierta por
-- Herón de Alejandría, dice que el área de un triángulo cuyo lados
-- miden a, b y c es la raíz cuadrada de s(s-a)(s-b)(s-c) donde s es el
-- semiperímetro
-- s = (a+b+c)/2
--
-- Definir la función
-- area :: Double -> Double -> Double -> Double
-- tal que (area a b c) es el área del triángulo de lados a, b y c. Por
-- ejemplo,
-- area 3 4 5 == 6.0
-- ---------------------------------------------------------------------
area :: Double -> Double -> Double -> Double
area a b c = sqrt (s*(s-a)*(s-b)*(s-c))
where s = (a+b+c)/2
-- ---------------------------------------------------------------------
-- Ejercicio 11.1. Los intervalos cerrados se pueden representar mediante
-- una lista de dos números (el primero es el extremo inferior del
-- intervalo y el segundo el superior).
--
-- Definir la función
-- interseccion :: Ord a => [a] -> [a] -> [a]
-- tal que (interseccion i1 i2) es la intersección de los intervalos i1 e
-- i2. Por ejemplo,
-- interseccion [] [3,5] == []
-- interseccion [3,5] [] == []
-- interseccion [2,4] [6,9] == []
-- interseccion [2,6] [6,9] == [6,6]
-- interseccion [2,6] [0,9] == [2,6]
-- interseccion [2,6] [0,4] == [2,4]
-- interseccion [4,6] [0,4] == [4,4]
-- interseccion [5,6] [0,4] == []
-- ---------------------------------------------------------------------
interseccion :: Ord a => [a] -> [a] -> [a]
interseccion [] _ = []
interseccion _ [] = []
interseccion [a1,b1] [a2,b2]
| a <= b = [a,b]
| otherwise = []
where a = max a1 a2
b = min b1 b2
interseccion _ _ = error "Imposible"
-- ---------------------------------------------------------------------
-- Ejercicio 12.1. Los números racionales pueden representarse mediante
-- pares de números enteros. Por ejemplo, el número 2/5 puede
-- representarse mediante el par (2,5).
--
-- Definir la función
-- formaReducida :: (Int,Int) -> (Int,Int)
-- tal que (formaReducida x) es la forma reducida del número racional
-- x. Por ejemplo,
-- formaReducida (4,10) == (2,5)
-- formaReducida (0,5) == (0,1)
-- ---------------------------------------------------------------------
formaReducida :: (Int,Int) -> (Int,Int)
formaReducida (0,_) = (0,1)
formaReducida (a,b) = (x * signum (a*b), y)
where c = gcd a b
x = abs (a `div` c)
y = abs (b `div` c)
-- ---------------------------------------------------------------------
-- Ejercicio 12.2. Definir la función
-- sumaRacional :: (Int,Int) -> (Int,Int) -> (Int,Int)
-- tal que (sumaRacional x y) es la suma de los números racionales x e
-- y, expresada en forma reducida. Por ejemplo,
-- sumaRacional (2,3) (5,6) == (3,2)
-- sumaRacional (3,5) (-3,5) == (0,1)
-- ---------------------------------------------------------------------
sumaRacional :: (Int,Int) -> (Int,Int) -> (Int,Int)
sumaRacional (a,b) (c,d) = formaReducida (a*d+b*c, b*d)
-- ---------------------------------------------------------------------
-- Ejercicio 12.3. Definir la función
-- productoRacional :: (Int,Int) -> (Int,Int) -> (Int,Int)
-- tal que (productoRacional x y) es el producto de los números
-- racionales x e y, expresada en forma reducida. Por ejemplo,
-- productoRacional (2,3) (5,6) == (5,9)
-- ---------------------------------------------------------------------
productoRacional :: (Int,Int) -> (Int,Int) -> (Int,Int)
productoRacional (a,b) (c,d) = formaReducida (a*c, b*d)
-- ---------------------------------------------------------------------
-- Ejercicio 12.4. Definir la función
-- igualdadRacional :: (Int,Int) -> (Int,Int) -> Bool
-- tal que (igualdadRacional x y) se verifica si los números racionales
-- x e y son iguales. Por ejemplo,
-- igualdadRacional (6,9) (10,15) == True
-- igualdadRacional (6,9) (11,15) == False
-- igualdadRacional (0,2) (0,-5) == True
-- ---------------------------------------------------------------------
igualdadRacional :: (Int,Int) -> (Int,Int) -> Bool
igualdadRacional (a,b) (c,d) =
a*d == b*c
-- ---------------------------------------------------------------------
-- Repaso de operaciones lógicas --
-- ---------------------------------------------------------------------
-- En esta relación se presentan ejercicios de repaso para trabajar con
-- operaciones lógicas sobre valores de verdad (o booleanos), sobre todo
-- con &&, || y not.
--
-- ---------------------------------------------------------------------
-- Ejercicio 13.1 Definir la función
-- or3 :: Bool -> Bool -> Bool -> Bool
-- tal que (or3 a b c) es la disyunción entre a, b y c; es decir,
-- ocurre a o b o c. Definir la función usando '||'. Por ejemplo,
-- or3 True True False == True
-- or3 False False False == False
-- ---------------------------------------------------------------------
or3 :: Bool -> Bool -> Bool-> Bool
or3 a b c = (a || b) || c
-- ---------------------------------------------------------------------
-- Ejercicio 13.2 Definir la función
-- or3' :: Bool -> Bool -> Bool -> Bool
-- tal que (or3' a b c) es la disyunción entre a, b y c; es decir,
-- ocurre a o b o c. Definir la función usando 'or' para listas.
-- Por ejemplo,
-- or3' True True False == True
-- or3' False False False == False
-- ---------------------------------------------------------------------
or3' :: Bool -> Bool -> Bool -> Bool
or3' a b c = or [a,b,c]
-- ---------------------------------------------------------------------
-- Ejercicio 14.1 Definir la función
-- and3 :: Bool -> Bool -> Bool -> Bool
-- tal que (and3 a b c) es la conjunción entre a, b y c; es decir,
-- ocurre a y b y c. Definir la función usando '&&'. Por ejemplo,
-- and3 True True True == True
-- and3 False True False == False
-- ---------------------------------------------------------------------
and3 :: Bool -> Bool -> Bool -> Bool
and3 a b c = (a && b) && c
-- ---------------------------------------------------------------------
-- Ejercicio 14.2 Definir la función
-- and3' :: Bool -> Bool -> Bool -> Bool
-- tal que (and3' a b c) es la conjunción entre a, b y c; es decir,
-- ocurre a o b o c. Definir la función usando 'and' para listas.
-- Por ejemplo,
-- and3' True True True == True
-- and3' False True False == False
-- ---------------------------------------------------------------------
and3' :: Bool -> Bool -> Bool -> Bool
and3' a b c = and [a,b,c]
-- ---------------------------------------------------------------------
-- Ejercicio 15.1. Definir la función
-- siglo20 :: Int -> Bool
-- tal que (siglo20 x) indica si el año x perteneció al siglo 20; es decir,
-- si está comprendido entre el año 1901 y 2000.
-- Por ejemplo,
-- siglo20 1902 == True
-- siglo20 2001 == False
-- ---------------------------------------------------------------------
siglo20 :: Int -> Bool
siglo20 x = (x>=1901) && (x<=2000)
-- ---------------------------------------------------------------------
-- Ejercicio 15.2. Definir la función
-- noSiglo20 :: Int -> Bool
-- tal que (noSiglo20 x) indica si el año x no perteneció al siglo 20; es decir,
-- si no está comprendido entre el año 1901 y 2000.
-- Definirla de tres formas distintas, una usando la función (siglo20 x), otra
-- usando '&&' y otra usando '||'.
--
-- Por ejemplo,
-- noSiglo20 1902 == False
-- noSiglo20 2001 == True
-- ---------------------------------------------------------------------
noSiglo20 :: Int -> Bool
noSiglo20 x = not( (x>=1901) && (x<=2000) )
noSiglo20' x = (x<1901) || (x>2000)
noSiglo20'' x = not( siglo20 x )
-- ---------------------------------------------------------------------
-- Ejercicio 16. Definir la función
-- xnor :: Bool -> Bool -> Bool
-- tal que (xnor a b) calcula 'a si y solo si b'. Su tabla de verdad
-- es
-- x | y | xnor x y
-- ------+-------+---------
-- False | False | True
-- False | True | False
-- True | False | False
-- True | True | True
--
-- Emplear solo operadores lógicos (&&, ||, not).
--
-- Por ejemplo,
-- xnor True True == True
-- xnor False True == False
-- xnor False False == True
-- ---------------------------------------------------------------------
xnor :: Bool -> Bool -> Bool
xnor a b = (a && b) || ((not a) && (not b))
-- ---------------------------------------------------------------------
-- Ejercicio 17. Definir la función
-- aprueba :: Float -> Float -> Float -> Bool
-- tal que (aprueba n1 n2 n3) indica si con las notas de los
-- exámenes parciales n1, n2 y n3 se consigue aprobar la
-- asignatura. Los criterios para aprobar la asignatura son los siguientes:
-- * La media de las notas debe ser mayor o igual que 5, y
-- * Cada una de las notas de los exámenes parciales son mayor o igual
-- que 4.0,
-- * O en el último exámen se obtuvo un 10 (entonces siempre se aprueba)
-- Por ejemplo,
-- aprueba 5.0 6.0 5.0 == True
-- aprueba 1.5 6.0 8.0 == False
-- aprueba 3.7 1.5 10.0 == True
-- ---------------------------------------------------------------------
aprueba :: Float -> Float -> Float -> Bool
aprueba n1 n2 n3 = (((n1 + n2 + n3)/3 >= 5.0) &&
(n1 >= 4.0) && (n2 >= 4.0) && (n3 >= 4.0)) ||
(n3 == 10.0)
-- ---------------------------------------------------------------------
-- Ejercicio 18. Comprobar las leyes de Morgan con QuickCheck. Las
-- leyes de Morgan se definen como sigue:
-- * ley 1: no (A o B) == (no A) y (no B)
-- * ley 2: no (A y B) == (no A) o (no B)
-- Consejo: definir cada ley como una función por separado para componer
-- la propiedad
-- ---------------------------------------------------------------------
ley1:: Bool -> Bool -> Bool
ley1 a b = (not (a || b)) == ((not a) && (not b))
ley2:: Bool -> Bool -> Bool
ley2 a b = (not (a && b)) == ((not a) || (not b))
-- La propiedad es
prop_leyes_morgan :: Bool -> Bool -> Bool
prop_leyes_morgan a b = (ley1 a b) && (ley2 a b)
-- La comprobación es
-- ghci> quickCheck prop_leyes_morgan
-- +++ OK, passed 100 tests.