Ir al contenido principal

Descomposiciones triangulares

Los números triangulares se forman como sigue

   *     *      *
        * *    * *
              * * *
   1     3      6

La sucesión de los números triangulares se obtiene sumando los números naturales. Así, los 5 primeros números triangulares son

    1 = 1
    3 = 1 + 2
    6 = 1 + 2 + 3
   10 = 1 + 2 + 3 + 4
   15 = 1 + 2 + 3 + 4 + 5

Definir la función

   descomposicionesTriangulares :: Int -> [(Int, Int, Int)]

tal que descomposicionesTriangulares n es la lista de las ternas correspondientes a las descomposiciones de n en tres sumandos, como máximo, formados por números triangulares. Por ejemplo,

   λ> descomposicionesTriangulares3 4
   []
   λ> descomposicionesTriangulares3 5
   [(1,1,3)]
   λ> descomposicionesTriangulares3 12
   [(1,1,10),(3,3,6)]
   λ> descomposicionesTriangulares3 30
   [(1,1,28),(3,6,21),(10,10,10)]
   λ> descomposicionesTriangulares3 61
   [(1,15,45),(3,3,55),(6,10,45),(10,15,36)]
   λ> descomposicionesTriangulares3 52
   [(1,6,45),(1,15,36),(3,21,28),(6,10,36),(10,21,21)]
   λ> descomposicionesTriangulares3 82
   [(1,3,78),(1,15,66),(1,36,45),(6,10,66),(6,21,55),(10,36,36)]
   λ> length (descomposicionesTriangulares3 (5*10^5))
   124

1. Soluciones en Haskell

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

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

descomposicionesTriangulares1 :: Int -> [(Int, Int, Int)]
descomposicionesTriangulares1 n =
  [(x,y,z) | x <- xs,
             y <- xs,
             z <- xs,
             x <= y && y <= z,
             x + y + z == n]
  where xs = takeWhile (<=n) triangulares

-- triangulares es la lista de los números triangulares. Por ejemplo,
--    take 9 triangulares  ==  [1,3,6,10,15,21,28,36,45]
triangulares :: [Int]
triangulares = scanl (+) 1 [2..]

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

descomposicionesTriangulares2 :: Int -> [(Int, Int, Int)]
descomposicionesTriangulares2 n =
  [(x,y,z) | x <- xs,
             y <- xs,
             x <= y,
             z <- xs,
             y <= z,
             x + y + z == n]
  where xs = takeWhile (<=n) triangulares

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

descomposicionesTriangulares3 :: Int -> [(Int, Int, Int)]
descomposicionesTriangulares3 n =
  [(x,y,z) | x <- xs,
             y <- xs,
             x <= y,
             let z = n - x - y,
             y <= z,
             z `elem` xs]
  where xs = takeWhile (<=n) triangulares

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

descomposicionesTriangulares4 :: Int -> [(Int, Int, Int)]
descomposicionesTriangulares4 n =
  [(x,y,n-x-y) | x <- xs,
                 y <- dropWhile (<x) xs,
                 let z = n - x - y,
                 y <= z,
                 z `elem` xs]
  where xs = takeWhile (<=n) triangulares

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

verifica :: IO ()
verifica = hspec spec

specG :: (Int -> [(Int, Int, Int)]) -> Spec
specG descomposicionesTriangulares = do
  it "e1" $
    descomposicionesTriangulares  4 `shouldBe`
      []
  it "e2" $
    descomposicionesTriangulares  5 `shouldBe`
      [(1,1,3)]
  it "e3" $
    descomposicionesTriangulares 12 `shouldBe`
      [(1,1,10),(3,3,6)]
  it "e4" $
    descomposicionesTriangulares 30 `shouldBe`
      [(1,1,28),(3,6,21),(10,10,10)]
  it "e5" $
    descomposicionesTriangulares 61 `shouldBe`
      [(1,15,45),(3,3,55),(6,10,45),(10,15,36)]
  it "e6" $
    descomposicionesTriangulares 52 `shouldBe`
      [(1,6,45),(1,15,36),(3,21,28),(6,10,36),(10,21,21)]
  it "e7" $
    descomposicionesTriangulares 82 `shouldBe`
      [(1,3,78),(1,15,66),(1,36,45),(6,10,66),(6,21,55),(10,36,36)]

spec :: Spec
spec = do
  describe "def. 1" $ specG descomposicionesTriangulares1
  describe "def. 2" $ specG descomposicionesTriangulares2
  describe "def. 3" $ specG descomposicionesTriangulares3
  describe "def. 4" $ specG descomposicionesTriangulares4

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

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

-- La propiedad es
prop_descomposicionesTriangulares_equiv ::  Positive Int -> Bool
prop_descomposicionesTriangulares_equiv (Positive n) =
  all (== descomposicionesTriangulares1 n)
      [descomposicionesTriangulares2 n,
       descomposicionesTriangulares3 n,
       descomposicionesTriangulares4 n]

-- La comprobación es
--    λ> quickCheck prop_descomposicionesTriangulares_equiv
--    +++ OK, passed 100 tests.

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

-- La comparación es
--   λ> last (descomposicionesTriangulares1 (2*10^4))
--   (5671,6328,8001)
--   (3.34 secs, 1,469,517,168 bytes)
--   λ> last (descomposicionesTriangulares2 (2*10^4))
--   (5671,6328,8001)
--   (1.29 secs, 461,433,928 bytes)
--   λ> last (descomposicionesTriangulares3 (2*10^4))
--   (5671,6328,8001)
--   (0.08 secs, 6,574,056 bytes)
--
--   λ> last (descomposicionesTriangulares3 (5*10^5))
--   (140185,148240,211575)
--   (2.12 secs, 151,137,280 bytes)
--   λ> last (descomposicionesTriangulares4 (5*10^5))
--   (140185,148240,211575)
--   (2.30 secs, 103,280,216 bytes)

2. Soluciones en Python

from itertools import count, dropwhile, takewhile
from sys import setrecursionlimit
from timeit import Timer, default_timer
from typing import Iterator

from hypothesis import given
from hypothesis import strategies as st

setrecursionlimit(10**6)

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

# triangular(n) es el n-ésimo número triangular. Por ejemplo,
#    triangular(9) == 45
def triangular(n: int) -> int:
    if n == 1:
        return 1
    return triangular(n-1) + n

# triangulares1() es la lista de los números triangulares. Por ejemplo,
#    >>> from itertools import islice
#    >>> list(islice(triangulares1(), 10))
#    [1, 3, 6, 10, 15, 21, 28, 36, 45, 55]
def triangulares1() -> Iterator[int]:
    return (triangular(n) for n in count(1))

def descomposicionesTriangulares1(n: int) -> list[tuple[int, int, int]]:
    xs = list(takewhile(lambda x : x <= n, triangulares1()))
    return [(x,y,z) for x in xs for y in xs for z in xs if
            x <= y <= z and x + y + z == n]

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

def triangulares2() -> Iterator[int]:
    return ((n*(n+1)) // 2 for n in count(1))

def descomposicionesTriangulares2(n: int) -> list[tuple[int, int, int]]:
    xs = list(takewhile(lambda x : x <= n, triangulares2()))
    return [(x,y,z) for x in xs for y in xs for z in xs if
            x <= y <= z and x + y + z == n]

# 3ª solución
# ===========

def descomposicionesTriangulares3(n: int) -> list[tuple[int, int, int]]:
    xs = list(takewhile(lambda x : x <= n, triangulares2()))
    return [(x,y,z)
            for x in xs
            for y in xs
            if x <= y
            for z in xs
            if y <= z
            if x + y + z == n]

# 4ª solución
# ===========

def descomposicionesTriangulares4(n: int) -> list[tuple[int, int, int]]:
    xs = list(takewhile(lambda x : x <= n, triangulares2()))
    ts = []
    for x in xs:
        for y in xs:
            if x <= y:
                z = n - x - y
                if y <= z and z in xs:
                    ts.append((x, y, z))
    return ts

# 5ª solución
# ===========

def descomposicionesTriangulares5(n: int) -> list[tuple[int, int, int]]:
    xs = list(takewhile(lambda a : a <= n, triangulares2()))
    ts = []
    for x in xs:
        ys = list(dropwhile(lambda y: y < x, xs))
        for y in ys:
            z = n - x - y
            if y <= z and z in xs:
                ts.append((x, y, z))
    return ts

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

def test_descomposicionesTriangulares() -> None:
    for descomposicionesTriangulares in [descomposicionesTriangulares1,
                                         descomposicionesTriangulares2,
                                         descomposicionesTriangulares3,
                                         descomposicionesTriangulares4,
                                         descomposicionesTriangulares5]:
        assert descomposicionesTriangulares(4) ==\
            []
        assert descomposicionesTriangulares(5) ==\
            [(1,1,3)]
        assert descomposicionesTriangulares(12) ==\
            [(1,1,10),(3,3,6)]
        assert descomposicionesTriangulares(30) ==\
            [(1,1,28),(3,6,21),(10,10,10)]
        assert descomposicionesTriangulares(61) ==\
            [(1,15,45),(3,3,55),(6,10,45),(10,15,36)]
        assert descomposicionesTriangulares(52) ==\
            [(1,6,45),(1,15,36),(3,21,28),(6,10,36),(10,21,21)]
        assert descomposicionesTriangulares(82) ==\
            [(1,3,78),(1,15,66),(1,36,45),(6,10,66),(6,21,55),(10,36,36)]
    print("Verificado")

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

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

# La propiedad es
@given(st.integers(min_value=1, max_value=1000))
def test_descomposicionesTriangulares_equiv(n: int) -> None:
    r = descomposicionesTriangulares1(n)
    assert descomposicionesTriangulares2(n) == r
    assert descomposicionesTriangulares3(n) == r
    assert descomposicionesTriangulares4(n) == r

# La comprobación es
#    >>> test_descomposicionesTriangulares_equiv()
#    >>>

# 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('descomposicionesTriangulares1(6*10**4)[-1]')
#    2.16 segundos
#    >>> tiempo('descomposicionesTriangulares2(6*10**4)[-1]')
#    2.05 segundos
#    >>> tiempo('descomposicionesTriangulares3(6*10**4)[-1]')
#    1.04 segundos
#    >>> tiempo('descomposicionesTriangulares4(6*10**4)[-1]')
#    0.10 segundos