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 70)  ==  4087968

Soluciones

import Data.Array ((!), listArray)
import Math.Combinat.Partitions (partitions, fromPartition)
import Test.Hspec (Spec, describe, hspec, it, shouldBe)
import Test.QuickCheck

-- 1ª solución: Recursiva básica  (generar y filtrar)
-- ==================================================

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: Recursiva con poda
-- ===============================

particiones2 :: Int -> [[Int]]
particiones2 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'

-- 3ª solución: Notación monádica
-- ==============================

particiones3 :: Int -> [[Int]]
particiones3 n = aux n n
  where
    aux 0 _ = [[]]
    aux _ 0 = []
    aux k m = do
      x <- [min k m, min k m - 1 .. 1]
      rest <- aux (k - x) x
      return (x:rest)

-- 4ª solución: Memoización con listas infinitas
-- =============================================

particiones4 :: Int -> [[Int]]
particiones4 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]

-- 5ª solución: Programación dinámica con listas
-- =============================================

particiones5 :: Int -> [[Int]]
particiones5 n = tabla !! n !! n
  where
    -- tabla !! m !! k es la lista de particiones de 'k'
    -- con sumandos no mayores a 'm'
    tabla = [[aux m k | k <- [0..n]] | m <- [0..n]]
    aux _ 0 = [[]]
    aux 0 _ = []
    aux m k
      | m > k     = tabla !! k !! k
      | otherwise = [m:p | p <- tabla !! m !! (k-m)] ++ tabla !! (m-1) !! k

-- 6ª solución: Programación dinámica con arrays
-- =============================================

particiones6 :: Int -> [[Int]]
particiones6 n = tabla ! (n, n)
  where
    tabla = listArray ((0,0), (n,n)) [aux m k | m <- [0..n], k <- [0..n]]
    aux _ 0 = [[]]
    aux 0 _ = []
    aux m k
      | m > k     = tabla ! (k, k)
      | otherwise = [m:p | p <- tabla ! (m, k-m)] ++ tabla ! (m-1, k)

-- 7ª solución: Uso de librería especializada (combinat)
-- =====================================================

particiones7 :: Int -> [[Int]]
particiones7 n = reverse (map fromPartition (partitions 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
  describe "def. 5" $ specG particiones5
  describe "def. 6" $ specG particiones6
  describe "def. 7" $ specG particiones7

-- La verificación es
--    λ> verifica
--    14 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
      , particiones5 n
      , particiones6 n
      , particiones7 n
      ]

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

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

-- La comparación es
--    λ> :set +s
--    λ> length (particiones1 23)
--    1255
--    (11.38 secs, 7,316,678,920 bytes)
--    λ> length (particiones2 23)
--    1255
--    (0.04 secs, 7,380,536 bytes)
--    λ> length (particiones3 23)
--    1255
--    (0.04 secs, 7,502,064 bytes)
--    λ> length (particiones4 23)
--    1255
--    (0.01 secs, 3,453,280 bytes)
--    λ> length (particiones5 23)
--    1255
--    (0.01 secs, 1,345,992 bytes)
--    λ> length (particiones6 23)
--    1255
--    (0.01 secs, 1,350,008 bytes)
--    λ> length (particiones7 23)
--    1255
--    (0.01 secs, 2,124,360 bytes)
--
--    λ> length (particiones2 50)
--    204226
--    (2.54 secs, 1,633,031,680 bytes)
--    λ> length (particiones3 50)
--    204226
--    (2.56 secs, 1,700,791,432 bytes)
--    λ> length (particiones4 50)
--    204226
--    (2.43 secs, 863,579,104 bytes)
--    λ> length (particiones5 50)
--    204226
--    (0.16 secs, 76,997,688 bytes)
--    λ> length (particiones6 50)
--    204226
--    (0.19 secs, 77,020,880 bytes)
--    λ> length (particiones7 50)
--    204226
--    (0.36 secs, 377,769,904 bytes)
--
--    λ> length (particiones5 60)
--    966467
--    (0.84 secs, 358,027,072 bytes)
--    λ> length (particiones6 60)
--    966467
--    (0.87 secs, 358,061,664 bytes)
--    λ> length (particiones7 60)
--    966467
--    (2.00 secs, 1,975,593,104 bytes)
--
--    λ> length (particiones5 70)
--    4087968
--    (3.84 secs, 1,507,357,120 bytes)
--    λ> length (particiones6 70)
--    4087968
--    (3.64 secs, 1,507,405,400 bytes)