Ir al contenido principal

Particiones de enteros positivos

Una partición de un entero positivo n es una manera de escribir n como una suma de enteros positivos. Dos sumas que sólo difieren en el orden de sus sumandos se consideran la misma partición. Por ejemplo, 4 tiene cinco particiones: 4, 3+1, 2+2, 2+1+1 y 1+1+1+1.

Definir la función

   particiones :: Int -> [[Int]]

tal que particiones n es la lista de las particiones del número n. Por ejemplo,

   particiones 4  ==  [[4],[3,1],[2,2],[2,1,1],[1,1,1,1]]
   particiones 5  ==  [[5],[4,1],[3,2],[3,1,1],[2,2,1],[2,1,1,1],[1,1,1,1,1]]
   length (particiones 50)  ==  204226

1. Soluciones en Haskell

import Test.Hspec (Spec, describe, hspec, it, shouldBe)
import Test.QuickCheck

-- 1ª solución
-- ===========

particiones1 :: Int -> [[Int]]
particiones1 0 = [[]]
particiones1 n = [x:y | x <- [n,n-1..1],
                        y <- particiones1 (n-x),
                        [x] >= take 1 y]

-- 2ª solución
-- ===========

particiones2 :: Int -> [[Int]]
particiones2 n = aux !! n
  where
    aux = [] : map particiones [1..]
    particiones m = [m] : [x:p | x <- [m,m-1..1],
                                 p <- aux !! (m-x),
                                 x >= head p]

-- 3ª solución
-- ===========

particiones3 :: Int -> [[Int]]
particiones3 n = aux n n
  where aux 0 _ = [[]]
        aux n' m = concat [map (i:) (aux (n'-i) i)
                          | i <- [n',n'-1..1], i <= m]

-- 4ª solución
-- ===========

particiones4 :: Int -> [[Int]]
particiones4 n = aux n n
  where aux 0 _ = [[]]
        aux n' m = concat [map (i:) (aux (n'-i) i)
                          | i <- [k,k-1..1]]
          where k = min m n'

-- Verificación
-- ============

verifica :: IO ()
verifica = hspec spec

specG :: (Int -> [[Int]]) -> Spec
specG particiones = do
  it "e1" $
     particiones 4  `shouldBe`
     [[4],[3,1],[2,2],[2,1,1],[1,1,1,1]]
  it "e2" $
     particiones 5  `shouldBe`
     [[5],[4,1],[3,2],[3,1,1],[2,2,1],[2,1,1,1],[1,1,1,1,1]]

spec :: Spec
spec = do
  describe "def. 1" $ specG particiones1
  describe "def. 2" $ specG particiones2
  describe "def. 3" $ specG particiones3
  describe "def. 4" $ specG particiones4

-- La verificación es
--    λ> verifica
--    8 examples, 0 failures

-- Comprobación de equivalencia
-- ============================

-- La propiedad es
prop_particiones :: Positive Int -> Bool
prop_particiones (Positive n) =
  all (== particiones1 n)
      [ particiones2 n
      , particiones3 n
      , particiones4 n
      ]

-- La comprobación es
--    λ> quickCheckWith (stdArgs {maxSize=20}) prop_particiones
--    +++ OK, passed 100 tests.

-- Comparación de eficiencia                                        --
-- =========================

-- La comparación es
--    λ> length (particiones1 23)
--    1255
--    (12.50 secs, 6,614,487,992 bytes)
--    λ> length (particiones2 23)
--    1255
--    (0.04 secs, 3,071,104 bytes)
--    λ> length (particiones3 23)
--    1255
--    (0.02 secs, 9,163,544 bytes)
--    λ> length (particiones4 23)
--    1255
--    (0.01 secs, 7,149,512 bytes)
--
--    λ> length (particiones2 50)
--    204226
--    (2.50 secs, 758,729,104 bytes)
--    λ> length (particiones3 50)
--    204226
--    (4.26 secs, 2,359,121,096 bytes)
--    λ> length (particiones4 50)
--    204226
--    (2.67 secs, 1,598,588,040 bytes)

2. Soluciones en Python

from timeit import Timer, default_timer

# 1ª solución
# ===========

def particiones1(n: int) -> list[list[int]]:
    if n == 0:
        return [[]]
    return [[x] + y
            for x in range(n, 0, -1)
            for y in particiones1(n - x)
            if not y or x >= y[0]]

# 2ª solución
# ===========

def particiones2(n: int) -> list[list[int]]:
    def particiones(m: int, aux: list[list[list[int]]]) -> list[list[int]]:
        return [[m]] + [
            [x] + p
            for x in range(m, 0, -1)
            for p in aux[m - x]
            if p and x >= p[0]
        ]
    aux: list[list[list[int]]] = [[]]
    for i in range(1, n + 1):
        aux.append(particiones(i, aux))
    return aux[n]

# Verificación
# ============

def test_particiones() -> None:
    for particiones in [particiones1,
                        particiones2]:
        assert particiones(4) ==\
            [[4],[3,1],[2,2],[2,1,1],[1,1,1,1]]
        assert particiones(5) ==\
            [[5],[4,1],[3,2],[3,1,1],[2,2,1],[2,1,1,1],[1,1,1,1,1]]
    print("Verificado")

# La verificación es
#    >>> test_particiones()
#    Verificado

# Comprobación de equivalencia
# ============================

# La propiedad es
def test_particiones_equiv(n: int) -> bool:
    return [particiones1(k) for k in range(1, n)]  ==\
           [particiones2(k) for k in range(1, n)]

# La comprobación es
#    >>> test_particiones_equiv(20)
#    True

# 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('particiones1(23)')
#    4.00 segundos
#    >>> tiempo('particiones2(23)')
#    0.01 segundos