Menu Close

Mes: noviembre 2022

Poner en mayúscula la primera letra y las restantes en minúsculas

Definir la función

   mayusculaInicial :: String -> String

tal que mayusculaInicial xs es la palabra xs con la letra inicial en mayúscula y las restantes en minúsculas. Por ejemplo,

   mayusculaInicial "sEviLLa"  ==  "Sevilla"
   mayusculaInicial ""         ==  ""

Soluciones

A continuación se muestran las soluciones en Haskell y las soluciones en Python.


Soluciones en Haskell

import Data.Char (toUpper, toLower)
import Test.QuickCheck
 
-- 1ª solución
-- ===========
 
mayusculaInicial1 :: String -> String
mayusculaInicial1 []     = []
mayusculaInicial1 (x:xs) = toUpper x : [toLower y | y <- xs]
 
-- 2ª solución
-- ===========
 
mayusculaInicial2 :: String -> String
mayusculaInicial2 [] = []
mayusculaInicial2 (x:xs) = toUpper x : aux xs
  where aux (y:ys) = toLower y : aux ys
        aux []     = []
 
-- 3ª solución
-- ===========
 
mayusculaInicial3 :: String -> String
mayusculaInicial3 [] = []
mayusculaInicial3 (x:xs) = toUpper x : map toLower xs
 
-- Comprobación de equivalencia
-- ============================
 
-- La propiedad es
prop_mayusculaInicial :: String -> Bool
prop_mayusculaInicial xs =
  all (== mayusculaInicial1 xs)
      [mayusculaInicial2 xs,
       mayusculaInicial3 xs]
 
-- La comprobación es
--    λ> quickCheck prop_mayusculaInicial
--    +++ OK, passed 100 tests.
 
-- Comparación de eficiencia
-- =========================
 
-- La comparación es
--    λ> length (mayusculaInicial1 (take (10^7) (cycle "aA")))
--    10000000
--    (2.22 secs, 1,680,592,240 bytes)
--    λ> length (mayusculaInicial2 (take (10^7) (cycle "aA")))
--    10000000
--    (2.57 secs, 2,240,592,192 bytes)
--    λ> length (mayusculaInicial3 (take (10^7) (cycle "aA")))
--    10000000
--    (0.16 secs, 1,440,592,192 bytes)


Soluciones en Python

from sys import setrecursionlimit
from timeit import Timer, default_timer
 
from hypothesis import given
from hypothesis import strategies as st
 
setrecursionlimit(10**6)
 
# 1ª solución
# ===========
 
def mayusculaInicial1(xs: str) -> str:
    if xs:
        return "".join([xs[0].upper()] + [y.lower() for y in xs[1:]])
    return ""
 
# 2ª solución
# ===========
 
def mayusculaInicial2(xs: str) -> str:
    def aux(ys: str) -> str:
        if ys:
            return ys[0].lower() + aux(ys[1:])
        return ""
    if xs:
        return "".join(xs[0].upper() + aux(xs[1:]))
    return ""
 
# 3ª solución
# ===========
 
def mayusculaInicial3(xs: str) -> str:
    if xs:
        return "".join([xs[0].upper()] + list(map(str.lower, xs[1:])))
    return ""
 
# 4ª solución
# ===========
 
def mayusculaInicial4(xs: str) -> str:
    return xs.capitalize()
 
# Comprobación de equivalencia
# ============================
 
# La propiedad es
@given(st.text())
def test_mayusculaInicial(xs: str) -> None:
    r = mayusculaInicial1(xs)
    assert mayusculaInicial2(xs) == r
    assert mayusculaInicial3(xs) == r
 
# La comprobación es
#    src> poetry run pytest -q mayuscula_inicial.py
#    1 passed in 0.26s
 
# 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('len(mayusculaInicial1("aB"*(10**7)))')
#    1.92 segundos
#    >>> tiempo('len(mayusculaInicial2("aB"*(10**7)))')
#    Process Python terminado (killed)
#    >>> tiempo('len(mayusculaInicial3("aB"*(10**7)))')
#    1.59 segundos
#    >>> tiempo('len(mayusculaInicial4("aB"*(10**7)))')
#    0.13 segundos

Suma de los dígitos de una cadena

Definir la función

   sumaDigitos :: String -> Int

tal que sumaDigitos xs' es la suma de los dígitos de la cadenaxs`. Por ejemplo,

   sumaDigitos "SE 2431 X"  ==  10

Soluciones

A continuación se muestran las soluciones en Haskell y las soluciones en Python.


Soluciones en Haskell

import Data.Char (digitToInt, isDigit)
import Test.QuickCheck
 
-- 1ª solución
-- ===========
 
sumaDigitos1 :: String -> Int
sumaDigitos1 xs = sum [digitToInt x | x <- xs, isDigit x]
 
-- 2ª solución
-- ===========
 
sumaDigitos2 :: String -> Int
sumaDigitos2 [] = 0
sumaDigitos2 (x:xs)
  | isDigit x  = digitToInt x + sumaDigitos2 xs
  | otherwise  = sumaDigitos2 xs
 
-- 3ª solución
-- ===========
 
sumaDigitos3 :: String -> Int
sumaDigitos3 xs = sum (map digitToInt (filter isDigit xs))
 
-- 4ª solución
-- ===========
 
sumaDigitos4 :: String -> Int
sumaDigitos4 = sum . map digitToInt . filter isDigit
 
-- Comprobación de equivalencia
-- ============================
 
-- La propiedad es
prop_sumaDigitos :: String -> Bool
prop_sumaDigitos xs =
  all (== sumaDigitos1 xs)
      [sumaDigitos2 xs,
       sumaDigitos3 xs,
       sumaDigitos4 xs]
 
-- La comprobación es
--    λ> quickCheck prop_sumaDigitos
--    +++ OK, passed 100 tests.
 
-- Comparación de eficiencia
-- =========================
 
-- La comparación es
--    λ> sumaDigitos1 (take (4*10^6) (cycle "ab12"))
--    3000000
--    (1.92 secs, 819,045,328 bytes)
--    λ> sumaDigitos2 (take (4*10^6) (cycle "ab12"))
--    3000000
--    (1.79 secs, 856,419,112 bytes)
--    λ> sumaDigitos3 (take (4*10^6) (cycle "ab12"))
--    3000000
--    (0.62 secs, 723,045,296 bytes)
--    λ> sumaDigitos4 (take (4*10^6) (cycle "ab12"))
--    3000000
--    (0.63 secs, 723,045,552 bytes)


Soluciones en Python

from sys import setrecursionlimit
from timeit import Timer, default_timer
 
from hypothesis import given
from hypothesis import strategies as st
 
setrecursionlimit(10**6)
 
# 1ª solución
# ===========
 
def sumaDigitos1(xs: str) -> int:
    return sum((int(x) for x in xs if x.isdigit()))
 
# 2ª solución
# ===========
 
def sumaDigitos2(xs: str) -> int:
    if xs:
        if xs[0].isdigit():
            return int(xs[0]) + sumaDigitos2(xs[1:])
        return sumaDigitos2(xs[1:])
    return 0
 
# 3ª solución
# ===========
 
def sumaDigitos3(xs: str) -> int:
    r = 0
    for x in xs:
        if x.isdigit():
            r = r + int(x)
    return r
 
# Comprobación de equivalencia
# ============================
 
# La propiedad es
@given(st.text())
def test_sumaDigitos(xs: str) -> None:
    r = sumaDigitos1(xs)
    assert sumaDigitos2(xs) == r
    assert sumaDigitos3(xs) == r
 
# La comprobación es
#    src> poetry run pytest -q suma_de_digitos_de_cadena.py
#    1 passed in 0.41s
 
# 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('mayorExponente1(2, 2**(2*10**4))')
 
 
# Comparación de eficiencia
# =========================
#
# La comparación es
#    >>> tiempo('sumaDigitos1("ab12"*5000)')
#    0.00 segundos
#    >>> tiempo('sumaDigitos2("ab12"*5000)')
#    0.02 segundos
#    >>> tiempo('sumaDigitos3("ab12"*5000)')
#    0.00 segundos
#
#    >>> tiempo('sumaDigitos1("ab12"*(5*10**6))')
#    1.60 segundos
#    >>> tiempo('sumaDigitos3("ab12"*(5*10**6))')
#    1.83 segundos

Números de Lychrel

Un número de Lychrel es un número para el que nunca se obtiene un capicúa mediante el proceso de invertir las cifras y sumar los dos números. Por ejemplo, los siguientes números no son números de Lychrel:
+ 56, ya que en un paso se obtiene un capicúa: 56+65=121.
+ 57, ya que en dos pasos se obtiene un capicúa: 57+75=132, 132+231=363
+ 59, ya que en dos pasos se obtiene un capicúa: 59+95=154, 154+451=605, 605+506=1111
+ 89, ya que en 24 pasos se obtiene un capicúa.
En esta serie de ejercicios vamos a buscar el primer número de Lychrel.

Ejercicio 1. Definir la función

   esCapicua :: Integer -> Bool

tal que esCapicua x se verifica si x es capicúa. Por ejemplo,

   esCapicua 252  ==  True
   esCapicua 253  ==  False

Ejercicio 2. Definir la función

   inverso :: Integer -> Integer

tal que inverso x es el número obtenido escribiendo las cifras de x en orden inverso. Por ejemplo,

   inverso 253  ==  352

Ejercicio 3. Definir la función

   siguiente :: Integer -> Integer

tal que siguiente x es el número obtenido sumándole a x su inverso. Por ejemplo,

   siguiente 253  ==  605

Ejercicio 4. Definir la función

   busquedaDeCapicua :: Integer -> [Integer]

tal que busquedaDeCapicua x es la lista de los números tal que el primero es x, el segundo es el siguiente de x y así sucesivamente hasta que se alcanza un capicúa. Por ejemplo,

   busquedaDeCapicua 253  ==  [253,605,1111]

Ejercicio 5. Definir la función

   capicuaFinal :: Integer -> Integer

tal que capicuaFinal x es la capicúa con la que termina la búsqueda de capicúa a partir de x. Por ejemplo,

   capicuaFinal 253  ==  1111

Ejercicio 6. Definir la función

   orden :: Integer -> Integer

tal que orden x es el número de veces que se repite el proceso de calcular el inverso a partir de x hasta alcanzar un número capicúa. Por ejemplo,

   orden 253  ==  2

Ejercicio 7. Definir la función

   ordenMayor :: Integer -> Integer -> Bool

tal que ordenMayor x n se verifica si el orden de x es mayor o igual que n. Dar la definición sin necesidad de evaluar el orden de x. Por ejemplo,

   λ> ordenMayor 1186060307891929990 2
   True
   λ> orden 1186060307891929990
   261

Ejercicio 8. Definir la función

   ordenEntre :: Integer -> Integer -> [Integer]

tal que ordenEntre m n es la lista de los elementos cuyo orden esmayor o igual que m y menor que n. Por ejemplo,

   take 5 (ordenEntre 10 11)  ==  [829,928,9059,9149,9239]

Ejercicio 9. Definir la función

   menorDeOrdenMayor :: Integer -> Integer

tal que menorDeOrdenMayor n es el menor elemento cuyo orden es mayor que n. Por ejemplo,

   menorDeOrdenMayor 2   ==  19
   menorDeOrdenMayor 20  ==  89

Ejercicio 10. Definir la función

   menoresdDeOrdenMayor :: Integer -> [(Integer,Integer)]

tal que menoresdDeOrdenMayor m es la lista de los pares (n,x) tales que n es un número entre 1 y m y x es el menor elemento de orden mayor que n. Por ejemplo,

   menoresdDeOrdenMayor 5  ==  [(1,10),(2,19),(3,59),(4,69),(5,79)]

Ejercicio 11. A la vista de los resultados de menoresdDeOrdenMayor 5 conjeturar sobre la última cifra de menorDeOrdenMayor.

Ejercicio 12. Decidir con QuickCheck la conjetura.

Ejercicio 13. Calcular menoresdDeOrdenMayor 50

Ejercicio 14. A la vista de menoresdDeOrdenMayor 50, conjeturar el orden de 196.

Ejercicio 15. Comprobar con QuickCheck la conjetura sobre el orden de 196.

Soluciones

A continuación se muestran las soluciones en Haskell y las soluciones en Python.


Soluciones en Haskell

import Test.QuickCheck
 
-- Solución de ejercicio 1
esCapicua :: Integer -> Bool
esCapicua x = x' == reverse x'
  where x' = show x
 
-- Solución de ejercicio 2
inverso :: Integer -> Integer
inverso = read . reverse . show
 
-- Solución de ejercicio 3
siguiente :: Integer -> Integer
siguiente x = x + inverso x
 
-- Solución de ejercicio 4
busquedaDeCapicua :: Integer -> [Integer]
busquedaDeCapicua x | esCapicua x = [x]
                    | otherwise   = x : busquedaDeCapicua (siguiente x)
 
-- Solución de ejercicio 5
capicuaFinal :: Integer -> Integer
capicuaFinal x = last (busquedaDeCapicua x)
 
-- Solución de ejercicio 6
orden :: Integer -> Integer
orden x | esCapicua x = 0
        | otherwise   = 1 + orden (siguiente x)
 
-- Solución de ejercicio 7
ordenMayor :: Integer -> Integer -> Bool
ordenMayor x n | esCapicua x = n == 0
               | n <= 0      = True
               | otherwise   = ordenMayor (siguiente x) (n-1)
 
-- Solución de ejercicio 8
ordenEntre :: Integer -> Integer -> [Integer]
ordenEntre m n = [x | x <- [1..], ordenMayor x m, not (ordenMayor x n)]
 
-- Solución de ejercicio 9
menorDeOrdenMayor :: Integer -> Integer
menorDeOrdenMayor n = head [x | x <- [1..], ordenMayor x n]
 
-- Solución de ejercicio 10
menoresdDeOrdenMayor :: Integer -> [(Integer,Integer)]
menoresdDeOrdenMayor m = [(n,menorDeOrdenMayor n) | n <- [1..m]]
 
-- Solución de ejercicio 11
-- La conjetura es que para n mayor que 1, la última cifra de
-- (menorDeOrdenMayor n) es 9.
 
-- Solución de ejercicio 12
-- ========================
 
-- La conjetura es
prop_menorDeOrdenMayor :: Integer -> Property
prop_menorDeOrdenMayor n =
  n > 1 ==> menorDeOrdenMayor n `mod` 10 == 9
 
-- La comprobación es
--    λ> quickCheck prop_menorDeOrdenMayor
--    *** Failed! Falsifiable (after 22 tests and 2 shrinks):
--    25
 
-- Se puede comprobar que 25 es un contraejemplo,
--    λ> menorDeOrdenMayor 25
--    196
 
-- Solución de ejercicio 13
-- El cálculo es
--    λ> menoresdDeOrdenMayor 50
--    [(1,10),(2,19),(3,59),(4,69),(5,79),(6,79),(7,89),(8,89),(9,89),
--     (10,89),(11,89),(12,89),(13,89),(14,89),(15,89),(16,89),(17,89),
--     (18,89),(19,89),(20,89),(21,89),(22,89),(23,89),(24,89),(25,196),
--     (26,196),(27,196),(28,196),(29,196),(30,196),(31,196),(32,196),
--     (33,196),(34,196),(35,196),(36,196),(37,196),(38,196),(39,196),
--     (40,196),(41,196),(42,196),(43,196),(44,196),(45,196),(46,196),
--     (47,196),(48,196),(49,196),(50,196)]
 
-- Solución de ejercicio 14
-- La conjetura es que el orden de 196 es infinito y, por tanto,
-- 196 es un número del Lychrel.
 
-- Solución de ejercicio 15
-- ========================
 
-- La propiedad es
prop_ordenDe196 :: Integer -> Bool
prop_ordenDe196 n =
  ordenMayor 196 n
 
-- La comprobación es
--    λ> quickCheck prop_ordenDe196
--    +++ OK, passed 100 tests.


Soluciones en Python

from itertools import islice
from sys import setrecursionlimit
from typing import Generator, Iterator
 
from hypothesis import given, settings
from hypothesis import strategies as st
 
setrecursionlimit(10**6)
 
# Solución del ejercicio 1
def esCapicua(x: int) -> bool:
    return x == int(str(x)[::-1])
 
# Solución del ejercicio 2
def inverso(x: int) -> int:
    return int(str(x)[::-1])
 
# Solución del ejercicio 3
def siguiente(x: int) -> int:
    return x + inverso(x)
 
# Solución del ejercicio 4
def busquedaDeCapicua(x: int) -> list[int]:
    if esCapicua(x):
        return [x]
    return [x] + busquedaDeCapicua(siguiente(x))
 
# Solución del ejercicio 5
def capicuaFinal(x: int) -> int:
    return busquedaDeCapicua(x)[-1]
 
# Solución del ejercicio 6
def orden(x: int) -> int:
    if esCapicua(x):
        return 0
    return 1 + orden(siguiente(x))
 
# Solución del ejercicio 7
def ordenMayor(x: int, n: int) -> bool:
    if esCapicua(x):
        return n == 0
    if n <= 0:
        return True
    return ordenMayor(siguiente(x), n - 1)
 
# Solución del ejercicio 8
# ========================
 
# naturales es el generador de los números naturales positivos, Por
# ejemplo,
#    >>> list(islice(naturales(), 5))
#    [1, 2, 3, 4, 5]
def naturales() -> Iterator[int]:
    i = 1
    while True:
        yield i
        i += 1
 
def ordenEntre(m: int, n: int) -> Generator[int, None, None]:
    return (x for x in naturales()
            if ordenMayor(x, m) and not ordenMayor(x, n))
 
# Solución del ejercicio 9
def menorDeOrdenMayor(n: int) -> int:
    return list(islice((x for x in naturales() if ordenMayor(x, n)), 1))[0]
 
# Solución del ejercicio 10
def menoresdDeOrdenMayor(m: int) -> list[tuple[int, int]]:
    return [(n, menorDeOrdenMayor(n)) for n in range(1, m + 1)]
 
# Solución del ejercicio 11
# La conjetura es que para n mayor que 1, la última cifra de
# menorDeOrdenMayor(n) es 9.
 
# Solución del ejercicio 12
# =========================
 
# La conjetura es
# @given(st.integers(min_value=2, max_value=200))
# def test_menorDeOrdenMayor(n: int) -> None:
#     assert menorDeOrdenMayor(n) % 10 == 9
 
# La comprobación es
#    src> poetry run pytest -q numeros_de_Lychrel.py
#    E       assert (196 % 10) == 9
#    E        +  where 196 = menorDeOrdenMayor(25)
#    E       Falsifying example: test_menorDeOrdenMayor(
#    E           n=25,
#    E       )
 
# Se puede comprobar que 25 es un contraejemplo,
#    >>> menorDeOrdenMayor(25)
#    196
 
# Solución del ejercicio 13
# El cálculo es
#    λ> menoresdDeOrdenMayor 50
#    [(1,10),(2,19),(3,59),(4,69),(5,79),(6,79),(7,89),(8,89),(9,89),
#     (10,89),(11,89),(12,89),(13,89),(14,89),(15,89),(16,89),(17,89),
#     (18,89),(19,89),(20,89),(21,89),(22,89),(23,89),(24,89),(25,196),
#     (26,196),(27,196),(28,196),(29,196),(30,196),(31,196),(32,196),
#     (33,196),(34,196),(35,196),(36,196),(37,196),(38,196),(39,196),
#     (40,196),(41,196),(42,196),(43,196),(44,196),(45,196),(46,196),
#     (47,196),(48,196),(49,196),(50,196)]
 
# Solución del ejercicio 14
# El orden de 196 es infinito y, por tanto, 196 es un número
# de Lychrel.
 
 
# Solución del ejercicio 15
# =========================
 
# La propiedad es
@settings(deadline=None)
@given(st.integers(min_value=2, max_value=5000))
def test_ordenDe196(n: int) -> None:
    assert ordenMayor(196, n)
 
# La comprobación es
#    src> poetry run pytest -q numeros_de_Lychrel.py
#    1 passed in 7.74s

El algoritmo de Luhn

El objetivo de este ejercicio es estudiar un algoritmo para validar algunos identificadores numéricos como los números de algunas tarjetas de crédito; por ejemplo, las de tipo Visa o Master Card.

El algoritmo que vamos a estudiar es el algoritmo de Luhn consistente en aplicar los siguientes pasos a los dígitos del número de la tarjeta.

  1. Se invierten los dígitos del número; por ejemplo, [9,4,5,5] se transforma en [5,5,4,9].
  2. Se duplican los dígitos que se encuentra en posiciones impares (empezando a contar en 0); por ejemplo, [5,5,4,9] se transforma en [5,10,4,18].
  3. Se suman los dígitos de cada número; por ejemplo, [5,10,4,18] se transforma en 5 + (1 + 0) + 4 + (1 + 8) = 19.
  4. Si el último dígito de la suma es 0, el número es válido; y no lo es, en caso contrario.

A los números válidos, se les llama números de Luhn.

Definir las siguientes funciones:

   digitosInv    :: Integer -> [Integer]
   doblePosImpar :: [Integer] -> [Integer]
   sumaDigitos   :: [Integer] -> Integer
   ultimoDigito  :: Integer -> Integer
   luhn          :: Integer -> Bool

tales que

  • digitosInv n es la lista de los dígitos del número n, en orden inverso. Por ejemplo,
     digitosInv 320274  ==  [4,7,2,0,2,3]
  • doblePosImpar ns es la lista obtenida doblando los elementos de ns en las posiciones impares (empezando a contar en cero y dejando igual a los que están en posiciones pares. Por ejemplo,
     doblePosImpar [4,9,5,5]    ==  [4,18,5,10]
     doblePosImpar [4,9,5,5,7]  ==  [4,18,5,10,7]
  • sumaDigitos ns es la suma de los dígitos de ns. Por ejemplo,
     sumaDigitos [10,5,18,4] = 1 + 0 + 5 + 1 + 8 + 4 =
                             = 19
  • ultimoDigito n es el último dígito de n. Por ejemplo,
     ultimoDigito 123 == 3
     ultimoDigito   0 == 0
  • luhn n se verifica si n es un número de Luhn. Por ejemplo,
     luhn 5594589764218858  ==  True
     luhn 1234567898765432  ==  False

Soluciones

A continuación se muestran las soluciones en Haskell y las soluciones en Python.


Soluciones en Haskell

-- Definición de digitosInv
-- ========================
 
digitosInv :: Integer -> [Integer]
digitosInv n = [read [x] | x <- reverse (show n)]
 
-- Nota: En el ejercicio "Dígitos de un número" https://bit.ly/3Tkhc2T
-- se presentan otras definiciones.
 
-- Definiciones de doblePosImpar
-- =============================
 
-- 1ª definición
doblePosImpar :: [Integer] -> [Integer]
doblePosImpar []       = []
doblePosImpar [x]      = [x]
doblePosImpar (x:y:zs) = x : 2*y : doblePosImpar zs
 
-- 2ª definición
doblePosImpar2 :: [Integer] -> [Integer]
doblePosImpar2 (x:y:zs) = x : 2*y : doblePosImpar2 zs
doblePosImpar2 xs       = xs
 
-- 3ª definición
doblePosImpar3 :: [Integer] -> [Integer]
doblePosImpar3 xs = [f n x | (n,x) <- zip [0..] xs]
  where f n x | odd n     = 2*x
              | otherwise = x
 
-- Definiciones de sumaDigitos
-- ===========================
 
sumaDigitos :: [Integer] -> Integer
sumaDigitos ns = sum [sum (digitosInv n) | n <- ns]
 
-- Nota: En el ejercicio "Suma de los dígitos de un número"
-- https://bit.ly/3U4u7WR se presentan otras definiciones.
 
-- Definición de ultimoDigito
-- ==========================
 
ultimoDigito :: Integer -> Integer
ultimoDigito n = n `rem` 10
 
-- Definiciones de luhn
-- ====================
 
-- 1ª definición
luhn1 :: Integer -> Bool
luhn1 n =
  ultimoDigito (sumaDigitos (doblePosImpar (digitosInv n))) == 0
 
-- 2ª definición
luhn2 :: Integer -> Bool
luhn2 =
  (==0) . ultimoDigito . sumaDigitos . doblePosImpar . digitosInv


Soluciones en Python

# Definición de digitosInv
# ========================
 
def digitosInv(n: int) -> list[int]:
    return [int(x) for x in reversed(str(n))]
 
# Nota: En el ejercicio "Dígitos de un número" https://bit.ly/3Tkhc2T
# se presentan otras definiciones.
 
# Definiciones de doblePosImpar
# =============================
 
# 1ª definición
def doblePosImpar(xs: list[int]) -> list[int]:
    if len(xs) <= 1:
        return xs
    return [xs[0]] + [2*xs[1]] + doblePosImpar(xs[2:])
 
# 2ª definición
def doblePosImpar2(xs: list[int]) -> list[int]:
    def f(n: int, x: int) -> int:
        if n % 2 == 1:
            return 2 * x
        return x
    return [f(n, x) for (n, x) in enumerate(xs)]
 
# Definiciones de sumaDigitos
# ===========================
 
def sumaDigitos(ns: list[int]) -> int:
    return sum((sum(digitosInv(n)) for n in ns))
 
# Nota: En el ejercicio "Suma de los dígitos de un número"
# https://bit.ly/3U4u7WR se presentan otras definiciones.
 
# Definición de ultimoDigito
# ==========================
 
def ultimoDigito(n: int) -> int:
    return n % 10
 
# Definiciones de luhn
# ====================
 
def luhn(n: int) -> bool:
    return ultimoDigito(sumaDigitos(doblePosImpar(digitosInv(n)))) == 0

Subconjuntos de un conjunto

Definir la función

   subconjuntos :: [a] -> [[a]]

tal que subconjuntos xs es la lista de las subconjuntos de la lista xs. Por ejemplo,

   λ> subconjuntos [2,3,4]
   [[2,3,4],[2,3],[2,4],[2],[3,4],[3],[4],[]]
   λ> subconjuntos [1,2,3,4]
   [[1,2,3,4],[1,2,3],[1,2,4],[1,2],[1,3,4],[1,3],[1,4],[1],
      [2,3,4],  [2,3],  [2,4],  [2],  [3,4],  [3],  [4], []]

Comprobar con QuickChek que el número de elementos de subconjuntos xs es 2 elevado al número de elementos de xs.

Soluciones

A continuación se muestran las soluciones en Haskell y las soluciones en Python.


Soluciones en Haskell

import Data.List (sort, subsequences)
import Test.QuickCheck
 
-- 1ª solución
-- ===========
 
subconjuntos1 :: [a] -> [[a]]
subconjuntos1 []     = [[]]
subconjuntos1 (x:xs) = [x:ys | ys <- sub] ++ sub
  where sub = subconjuntos1 xs
 
-- 2ª solución
-- ===========
 
subconjuntos2 :: [a] -> [[a]]
subconjuntos2 []     = [[]]
subconjuntos2 (x:xs) = map (x:) sub ++ sub
  where sub = subconjuntos2 xs
 
-- 3ª solución
-- ===========
 
subconjuntos3 :: [a] -> [[a]]
subconjuntos3 = subsequences
 
-- Comprobación de equivalencia
-- ============================
 
-- La propiedad es
prop_subconjuntos :: [Int] -> Bool
prop_subconjuntos xs =
  all (== sort (subconjuntos1 xs))
      [sort (subconjuntos2 xs),
       sort (subconjuntos3 xs)]
 
-- La comprobación es
--    λ> quickCheckWith (stdArgs {maxSize=7}) prop_subconjuntos
--    +++ OK, passed 100 tests.
 
-- Comparación de eficiencia
-- =========================
 
-- La comparación es
--    λ> length (subconjuntos1 [1..23])
--    8388608
--    (2.05 secs, 1,476,991,840 bytes)
--    λ> length (subconjuntos2 [1..23])
--    8388608
--    (0.87 secs, 1,208,555,312 bytes)
--    λ> length (subconjuntos3 [1..23])
--    8388608
--    (0.09 secs, 873,006,608 bytes)
 
-- Comprobación de la propiedad
-- ============================
 
-- La propiedad es
prop_length_subconjuntos :: [Int] -> Bool
prop_length_subconjuntos xs =
  length (subconjuntos1 xs) == 2 ^ length xs
 
-- La comprobación es
--    λ> quickCheckWith (stdArgs {maxSize=7}) prop_length_subconjuntos
--    +++ OK, passed 100 tests.


Soluciones en Python

from itertools import combinations
from sys import setrecursionlimit
from timeit import Timer, default_timer
from typing import TypeVar
 
from hypothesis import given
from hypothesis import strategies as st
from sympy import FiniteSet
 
setrecursionlimit(10**6)
 
A = TypeVar('A')
 
# 1ª solución
# ===========
 
def subconjuntos1(xs: list[A]) -> list[list[A]]:
    if xs:
        sub = subconjuntos1(xs[1:])
        return [[xs[0]] + ys for ys in sub] + sub
    return [[]]
 
# 2ª solución
# ===========
 
def subconjuntos2(xs: list[A]) -> list[list[A]]:
    if xs:
        sub = subconjuntos1(xs[1:])
        return list(map((lambda ys: [xs[0]] + ys), sub)) + sub
    return [[]]
 
# 3ª solución
# ===========
 
def subconjuntos3(xs: list[A]) -> list[list[A]]:
    c = FiniteSet(*xs)
    return list(map(list, c.powerset()))
 
# 4ª solución
# ===========
 
def subconjuntos4(xs: list[A]) -> list[list[A]]:
    return [list(ys)
            for r in range(len(xs)+1)
            for ys in combinations(xs, r)]
 
# Comprobación de equivalencia
# ============================
 
# La propiedad es
@given(st.lists(st.integers(), max_size=5))
def test_subconjuntos(xs: list[int]) -> None:
    ys = list(set(xs))
    r = sorted([sorted(zs) for zs in subconjuntos1(ys)])
    assert sorted([sorted(zs) for zs in subconjuntos2(ys)]) == r
    assert sorted([sorted(zs) for zs in subconjuntos3(ys)]) == r
    assert sorted([sorted(zs) for zs in subconjuntos4(ys)]) == r
 
# La comprobación es
#    src> poetry run pytest -q subconjuntos_de_un_conjunto.py
#    1 passed in 0.89s
 
# 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('subconjuntos1(range(14))')
#    0.00 segundos
#    >>> tiempo('subconjuntos2(range(14))')
#    0.00 segundos
#    >>> tiempo('subconjuntos3(range(14))')
#    6.01 segundos
#    >>> tiempo('subconjuntos4(range(14))')
#    0.00 segundos
#
#    >>> tiempo('subconjuntos1(range(23))')
#    1.95 segundos
#    >>> tiempo('subconjuntos2(range(23))')
#    2.27 segundos
#    >>> tiempo('subconjuntos4(range(23))')
#    1.62 segundos
 
# Comprobación de la propiedad
# ============================
 
# La propiedad es
@given(st.lists(st.integers(), max_size=7))
def test_length_subconjuntos(xs: list[int]) -> None:
    assert len(subconjuntos1(xs)) == 2 ** len(xs)
 
# La comprobación es
#    src> poetry run pytest -q subconjuntos_de_un_conjunto.py
#    2 passed in 0.95s