PFH: La semana en Exercitium (26 de noviembre de 2022)
Esta semana he publicado en Exercitium las soluciones de los siguientes problemas:
- 1. Aplica según propiedad
- 2. Máximo de una lista
- 3. Movimientos en el plano
- 4. El tipo de figuras geométricas
- 5. El tipo de los números naturales
A continuación se muestran las soluciones.
1. Aplica según propiedad
Definir la función
1 |
filtraAplica :: (a -> b) -> (a -> Bool) -> [a] -> [b] |
tal que filtraAplica f p xs
es la lista obtenida aplicándole a los elementos de xs
que cumplen el predicado p
la función f
. Por ejemplo,
1 |
filtraAplica (4+) (<3) [1..7] == [5,6] |
1.1. Soluciones en Haskell
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 |
import Test.QuickCheck.HigherOrder (quickCheck') -- 1ª solución -- =========== filtraAplica1 :: (a -> b) -> (a -> Bool) -> [a] -> [b] filtraAplica1 f p xs = [f x | x <- xs, p x] -- 2ª solución -- =========== filtraAplica2 :: (a -> b) -> (a -> Bool) -> [a] -> [b] filtraAplica2 f p xs = map f (filter p xs) -- 3ª solución -- =========== filtraAplica3 :: (a -> b) -> (a -> Bool) -> [a] -> [b] filtraAplica3 _ _ [] = [] filtraAplica3 f p (x:xs) | p x = f x : filtraAplica3 f p xs | otherwise = filtraAplica3 f p xs -- 4ª solución -- =========== filtraAplica4 :: (a -> b) -> (a -> Bool) -> [a] -> [b] filtraAplica4 f p = foldr g [] where g x y | p x = f x : y | otherwise = y -- 5ª solución -- =========== filtraAplica5 :: (a -> b) -> (a -> Bool) -> [a] -> [b] filtraAplica5 f p = foldr (\x y -> if p x then f x : y else y) [] -- Comprobación de equivalencia -- ============================ -- La propiedad es prop_filtraAplica :: (Int -> Int) -> (Int -> Bool) -> [Int] -> Bool prop_filtraAplica f p xs = all (== filtraAplica1 f p xs) [filtraAplica2 f p xs, filtraAplica3 f p xs, filtraAplica4 f p xs, filtraAplica5 f p xs] -- La comprobación es -- λ> quickCheck' prop_filtraAplica -- +++ OK, passed 100 tests. -- Comparación de eficiencia -- ========================= -- La comparación es -- λ> sum (filtraAplica1 id even [1..5*10^6]) -- 6250002500000 -- (2.92 secs, 1,644,678,696 bytes) -- λ> sum (filtraAplica2 id even [1..5*10^6]) -- 6250002500000 -- (1.17 secs, 1,463,662,848 bytes) -- λ> sum (filtraAplica3 id even [1..5*10^6]) -- 6250002500000 -- (3.18 secs, 1,964,678,640 bytes) -- λ> sum (filtraAplica4 id even [1..5*10^6]) -- 6250002500000 -- (2.64 secs, 1,924,678,752 bytes) -- λ> sum (filtraAplica5 id even [1..5*10^6]) -- 6250002500000 -- (2.61 secs, 1,824,678,712 bytes) |
1.2. Soluciones en Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 |
from functools import reduce from sys import setrecursionlimit from timeit import Timer, default_timer from typing import Callable, TypeVar from hypothesis import given from hypothesis import strategies as st setrecursionlimit(10**6) A = TypeVar('A') B = TypeVar('B') # 1ª solución # =========== def filtraAplica1(f: Callable[[A], B], p: Callable[[A], bool], xs: list[A]) -> list[B]: return [f(x) for x in xs if p(x)] # 2ª solución # =========== def filtraAplica2(f: Callable[[A], B], p: Callable[[A], bool], xs: list[A]) -> list[B]: return list(map(f, filter(p, xs))) # 3ª solución # =========== def filtraAplica3(f: Callable[[A], B], p: Callable[[A], bool], xs: list[A]) -> list[B]: if not xs: return [] if p(xs[0]): return [f(xs[0])] + filtraAplica3(f, p, xs[1:]) return filtraAplica3(f, p, xs[1:]) # 4ª solución # =========== def filtraAplica4(f: Callable[[A], B], p: Callable[[A], bool], xs: list[A]) -> list[B]: def g(ys: list[B], x: A) -> list[B]: if p(x): return ys + [f(x)] return ys return reduce(g, xs, []) # 5ª solución # =========== def filtraAplica5(f: Callable[[A], B], p: Callable[[A], bool], xs: list[A]) -> list[B]: r = [] for x in xs: if p(x): r.append(f(x)) return r # Comprobación de equivalencia # ============================ # La propiedad es @given(st.lists(st.integers())) def test_filtraAplica(xs: list[int]) -> None: f = lambda x: x + 4 p = lambda x: x < 3 r = filtraAplica1(f, p, xs) assert filtraAplica2(f, p, xs) == r assert filtraAplica3(f, p, xs) == r assert filtraAplica4(f, p, xs) == r assert filtraAplica5(f, p, xs) == r # La comprobación es # src> poetry run pytest -q aplica_segun_propiedad.py # 1 passed in 0.25s # Comparación de eficiencia # ========================= def tiempo(e: str) -> None: """Tiempo (en segundos) de evaluar la expresión e.""" t = Timer(e, "", default_timer, globals()).timeit(1) print(f"{t:0.2f} segundos") # La comparación es # >>> tiempo('filtraAplica1(lambda x: x, lambda x: x % 2 == 0, range(10**5))') # 0.02 segundos # >>> tiempo('filtraAplica2(lambda x: x, lambda x: x % 2 == 0, range(10**5))') # 0.01 segundos # >>> tiempo('filtraAplica3(lambda x: x, lambda x: x % 2 == 0, range(10**5))') # Process Python violación de segmento (core dumped) # >>> tiempo('filtraAplica4(lambda x: x, lambda x: x % 2 == 0, range(10**5))') # 4.07 segundos # >>> tiempo('filtraAplica5(lambda x: x, lambda x: x % 2 == 0, range(10**5))') # 0.01 segundos # # >>> tiempo('filtraAplica1(lambda x: x, lambda x: x % 2 == 0, range(10**7))') # 1.66 segundos # >>> tiempo('filtraAplica2(lambda x: x, lambda x: x % 2 == 0, range(10**7))') # 1.00 segundos # >>> tiempo('filtraAplica5(lambda x: x, lambda x: x % 2 == 0, range(10**7))') # 1.21 segundos |
2. Máximo de una lista
Definir la función
1 |
maximo :: Ord a => [a] -> a |
tal que maximo xs
es el máximo de la lista xs
. Por ejemplo,
1 2 3 |
maximo [3,7,2,5] == 7 maximo ["todo","es","falso</b> == "todo" maximo ["menos","alguna","cosa</b> == "menos" |
2.1. Soluciones en Haskell
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
import Data.List (foldl1') import Test.QuickCheck -- 1ª solución -- =========== maximo1 :: Ord a => [a] -> a maximo1 [x] = x maximo1 (x:y:ys) = max x (maximo1 (y:ys)) -- 2ª solución -- =========== maximo2 :: Ord a => [a] -> a maximo2 = foldr1 max -- 3ª solución -- =========== maximo3 :: Ord a => [a] -> a maximo3 = foldl1' max -- 4ª solución -- =========== maximo4 :: Ord a => [a] -> a maximo4 = maximum -- Comprobación de equivalencia -- ============================ -- La propiedad es prop_maximo :: NonEmptyList Int -> Bool prop_maximo (NonEmpty xs) = all (== maximo1 xs) [maximo2 xs, maximo3 xs] -- La comprobación es -- λ> quickCheck prop_maximo -- +++ OK, passed 100 tests. -- Comparación de eficiencia -- ========================= -- La comparación es -- λ> maximo1 [0..5*10^6] -- 5000000 -- (3.42 secs, 1,783,406,728 bytes) -- λ> maximo2 [0..5*10^6] -- 5000000 -- (0.80 secs, 934,638,080 bytes) -- λ> maximo3 [0..5*10^6] -- 5000000 -- (0.12 secs, 360,591,360 bytes) -- λ> maximo4 [0..5*10^6] -- 5000000 -- (1.40 secs, 892,891,608 bytes) |
2.2. Soluciones en Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
from abc import abstractmethod from functools import reduce from sys import setrecursionlimit from timeit import Timer, default_timer from typing import Any, TypeVar, Protocol from hypothesis import given from hypothesis import strategies as st setrecursionlimit(10**6) A = TypeVar('A', bound="Comparable") class Comparable(Protocol): """Para comparar""" @abstractmethod def __eq__(self, other: Any) -> bool: pass @abstractmethod def __lt__(self: A, other: A) -> bool: pass def __gt__(self: A, other: A) -> bool: return (not self < other) and self != other def __le__(self: A, other: A) -> bool: return self < other or self == other def __ge__(self: A, other: A) -> bool: return not self < other # 1ª solución # =========== def maximo1(xs: list[A]) -> A: if len(xs) == 1: return xs[0] return max(xs[0], maximo1(xs[1:])) # 2ª solución # =========== def maximo2(xs: list[A]) -> A: return reduce(max, xs) # 3ª solución # =========== def maximo3(xs: list[A]) -> A: return max(xs) # ============================ # La propiedad es @given(st.lists(st.integers(), min_size=2)) def test_maximo(xs: list[int]) -> None: r = maximo1(xs) assert maximo2(xs) == r assert maximo3(xs) == r # La comprobación es # src> poetry run pytest -q maximo_de_una_lista.py # 1 passed in 0.33s # Comparación de eficiencia # ========================= def tiempo(e: str) -> None: """Tiempo (en segundos) de evaluar la expresión e.""" t = Timer(e, "", default_timer, globals()).timeit(1) print(f"{t:0.2f} segundos") # La comparación es # >>> tiempo('maximo1(range(2*10**4))') # 0.03 segundos # >>> tiempo('maximo2(range(2*10**4))') # 0.00 segundos # >>> tiempo('maximo3(range(2*10**4))') # 0.00 segundos # # >>> tiempo('maximo2(range(5*10**6))') # 0.38 segundos # >>> tiempo('maximo3(range(5*10**6))') # 0.21 segundos |
3. Movimientos en el plano
Se consideran el tipo de las posiciones del plano definido por
1 2 3 4 5 6 |
type Posicion = (Int,Int) <7p y el tipo de las direcciones definido por <pre lang="text"> data Direccion = Izquierda | Derecha | Arriba | Abajo deriving Show |
Definir las siguientes funciones
1 2 3 |
opuesta :: Direccion -> Direccion movimiento :: Posicion -> Direccion -> Posicion movimientos :: Posicion -> [Direccion] -> Posicion |
tales que
opuesta d
es la dirección opuesta ded
. Por ejemplo,
1 |
opuesta Izquierda == Derecha |
movimiento p d
es la posición reultante de moverse, desde la posiciónp
, un paso en la direcciónd
. Por ejemplo,
1 2 |
movimiento (2,5) Arriba == (2,6) movimiento (2,5) (opuesta Abajo) == (2,6) |
movimientos p ds
es la posición obtenida aplicando la lista de movimientos según las direcciones deds
a la posiciónp
. Por ejemplo,
1 |
movimientos (2,5) [Arriba, Izquierda] == (1,6) |
3.1. Soluciones en Haskell
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
type Posicion = (Int,Int) data Direccion = Izquierda | Derecha | Arriba | Abajo deriving Show -- Definición de opuesta -- ===================== opuesta :: Direccion -> Direccion opuesta Izquierda = Derecha opuesta Derecha = Izquierda opuesta Arriba = Abajo opuesta Abajo = Arriba -- 1ª definición de movimiento -- =========================== movimiento1 :: Posicion -> Direccion -> Posicion movimiento1 (x,y) Izquierda = (x-1,y) movimiento1 (x,y) Derecha = (x+1,y) movimiento1 (x,y) Arriba = (x,y+1) movimiento1 (x,y) Abajo = (x,y-1) -- 2ª definición de movimiento -- =========================== movimiento2 :: Posicion -> Direccion -> Posicion movimiento2 (x,y) d = case d of Izquierda -> (x-1,y) Derecha -> (x+1,y) Arriba -> (x,y+1) Abajo -> (x,y-1) -- 1ª definición de movimientos -- ============================ movimientos1 :: Posicion -> [Direccion] -> Posicion movimientos1 p [] = p movimientos1 p (d:ds) = movimientos1 (movimiento1 p d) ds -- 2ª definición de movimientos -- ============================ movimientos2 :: Posicion -> [Direccion] -> Posicion movimientos2 = foldl movimiento1 |
3.2. Soluciones en Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
from enum import Enum from functools import reduce Posicion = tuple[int, int] Direccion = Enum('Direccion', ['Izquierda', 'Derecha', 'Arriba', 'Abajo']) # 1ª definición de opuesta # ======================== def opuesta1(d: Direccion) -> Direccion: if d == Direccion.Izquierda: return Direccion.Derecha if d == Direccion.Derecha: return Direccion.Izquierda if d == Direccion.Arriba: return Direccion.Abajo if d == Direccion.Abajo: return Direccion.Arriba assert False # 2ª definición de opuesta # ======================== def opuesta2(d: Direccion) -> Direccion: match d: case Direccion.Izquierda: return Direccion.Derecha case Direccion.Derecha: return Direccion.Izquierda case Direccion.Arriba: return Direccion.Abajo case Direccion.Abajo: return Direccion.Arriba assert False # 1ª definición de movimiento # =========================== def movimiento1(p: Posicion, d: Direccion) -> Posicion: (x, y) = p if d == Direccion.Izquierda: return (x - 1, y) if d == Direccion.Derecha: return (x + 1, y) if d == Direccion.Arriba: return (x, y + 1) if d == Direccion.Abajo: return (x, y - 1) assert False # 2ª definición de movimiento # =========================== def movimiento2(p: Posicion, d: Direccion) -> Posicion: (x, y) = p match d: case Direccion.Izquierda: return (x - 1, y) case Direccion.Derecha: return (x + 1, y) case Direccion.Arriba: return (x, y + 1) case Direccion.Abajo: return (x, y - 1) assert False # 1ª definición de movimientos # ============================ def movimientos1(p: Posicion, ds: list[Direccion]) -> Posicion: if not ds: return p return movimientos1(movimiento1(p, ds[0]), ds[1:]) # 2ª definición de movimientos # ============================ def movimientos2(p: Posicion, ds: list[Direccion]) -> Posicion: return reduce(movimiento1, ds, p) |
4. El tipo de figuras geométricas
Se consideran las figuras geométricas formadas por circulos (definidos por su radio) y rectángulos (definidos por su base y su altura). El tipo de las figura geométricas se define por
1 |
data Figura = Circulo Float | Rect Float Float |
Definir las funciones
1 2 |
area :: Figura -> Float cuadrado :: Float -> Figura |
tales que
area f
es el área de la figuraf
. Por ejemplo,
1 2 3 |
area (Circulo 1) == 3.1415927 area (Circulo 2) == 12.566371 area (Rect 2 5) == 10.0 |
cuadrado n
es el cuadrado de ladon
. Por ejemplo,
1 |
area (cuadrado 3) == 9.0 |
4.1. Soluciones en Haskell
1 2 3 4 5 6 7 8 |
data Figura = Circulo Float | Rect Float Float area :: Figura -> Float area (Circulo r) = pi*r^2 area (Rect x y) = x*y cuadrado :: Float -> Figura cuadrado n = Rect n n |
4.2. Soluciones en Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
from dataclasses import dataclass from math import pi @dataclass class Figura: """Figuras geométricas""" @dataclass class Circulo(Figura): r: float @dataclass class Rect(Figura): x: float y: float def area(f: Figura) -> float: match f: case Circulo(r): return pi * r**2 case Rect(x, y): return x * y assert False def cuadrado(n: float) -> Figura: return Rect(n, n) |
5. El tipo de los números naturales
El tipo de los números raturales se puede definir por
1 2 |
data Nat = Cero | Suc Nat deriving (Show, Eq) |
de forma que Suc (Suc (Suc Cero))
representa el número 3.
Definir las siguientes funciones
1 2 3 |
nat2int :: Nat -> Int int2nat :: Int -> Nat suma :: Nat -> Nat -> Nat |
tales que
nat2int n
es el número entero correspondiente al número naturaln
. Por ejemplo,
1 |
nat2int (Suc (Suc (Suc Cero))) == 3 |
int2nat n
es el número natural correspondiente al número enteron
. Por ejemplo,
1 |
int2nat 3 == Suc (Suc (Suc Cero)) |
suma m n
es la suma de los número naturalesm
yn
. Por ejemplo,
1 2 3 4 5 6 |
λ> suma (Suc (Suc Cero)) (Suc Cero) Suc (Suc (Suc Cero)) λ> nat2int (suma (Suc (Suc Cero)) (Suc Cero)) 3 λ> nat2int (suma (int2nat 2) (int2nat 1)) 3 |
5.1. Soluciones en Haskell
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
data Nat = Cero | Suc Nat deriving (Show, Eq) nat2int :: Nat -> Int nat2int Cero = 0 nat2int (Suc n) = 1 + nat2int n int2nat :: Int -> Nat int2nat 0 = Cero int2nat n = Suc (int2nat (n-1)) suma :: Nat -> Nat -> Nat suma Cero n = n suma (Suc m) n = Suc (suma m n) |
5.2. Soluciones en Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
from dataclasses import dataclass @dataclass class Nat: pass @dataclass class Cero(Nat): pass @dataclass class Suc(Nat): n: Nat def nat2int(n: Nat) -> int: match n: case Cero(): return 0 case Suc(n): return 1 + nat2int(n) assert False def int2nat(n: int) -> Nat: if n == 0: return Cero() return Suc(int2nat(n - 1)) def suma(m: Nat, n: Nat) -> Nat: match m: case Cero(): return n case Suc(m): return Suc(suma(m, n)) assert False |