Ir al contenido principal

Menor número triangular con más de n divisores


La sucesión de los números triangulares se obtiene sumando los números naturales.

x     x      x        x         x
     x x    x x      x x       x x
           x x x    x x x     x x x
                   x x x x   x x x x
                            x x x x x
1     3      6        10        15

Así, el 7º número triangular es

1 + 2 + 3 + 4 + 5 + 6 + 7 = 28.

Los primeros 10 números triangulares son

1, 3, 6, 10, 15, 21, 28, 36, 45, 55, ...

Los divisores de los primeros 7 números triangulares son:

 1: 1
 3: 1,3
 6: 1,2,3,6
10: 1,2,5,10
15: 1,3,5,15
21: 1,3,7,21
28: 1,2,4,7,14,28

Como se puede observar, 28 es el menor número triangular con más de 5 divisores.

Definir la función

menorTriangularConAlMenosNDivisores :: Int -> Integer

tal que (menorTriangularConAlMenosNDivisores n) es el menor número triangular que tiene al menos n divisores. Por ejemplo,

menorTriangularConAlMenosNDivisores 5    == 28
menorTriangularConAlMenosNDivisores 50   == 25200
menorTriangularConAlMenosNDivisores 500  == 76576500
menorTriangularConAlMenosNDivisores 1500 == 7589181600

Soluciones

module Menor_numero_triangular_con_mas_de_n_divisores where

import Data.List (group)
import Data.Numbers.Primes (primes,primeFactors)
import Test.Hspec (Spec, describe, hspec, it, shouldBe)
import Test.QuickCheck

-- 1ª solución: Fuerza bruta (Definición directa)
-- ==============================================

menorTriangularConAlMenosNDivisores1 :: Int -> Integer
menorTriangularConAlMenosNDivisores1 n =
  head [x | x <- triangulares, nDivisores x >= n]

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

-- (nDivisores x) es el número de divisores de x. Por ejemplo,
--    nDivisores 28  ==  6
nDivisores :: Integer -> Int
nDivisores x =
  1 + length [y | y <- [1..x `div` 2], mod x y == 0]

-- 2ª solución: Factorización prima manual
-- =======================================

menorTriangularConAlMenosNDivisores2 :: Int -> Integer
menorTriangularConAlMenosNDivisores2 n =
  head [x | x <- triangulares, nDivisores2 x >= n]

-- (nDivisores2 x) es el número de divisores de x. Por ejemplo,
--    nDivisores2 28  ==  6
nDivisores2 :: Integer -> Int
nDivisores2 1 = 1
nDivisores2 n = product [1 + length xs | xs <- group (factoresPrimos n)]

-- (factoresPrimos n) es la lista de los factores primos de n. Por
-- ejemplo,
--    factoresPrimos 28  ==  [2,2,7]
factoresPrimos :: Integer -> [Integer]
factoresPrimos n = aux n primos
  where aux m (p:ps)
          | p*p > m        = [m]
          | m `mod` p == 0 = p : aux (m `div` p) (p:ps)
          | otherwise      = aux m ps

-- primos es la lista de los números primos. Por ejemplo,
--    take 10 primos  ==  [2,3,5,7,11,13,17,19,23,29]
primos :: [Integer]
primos = 2 : filter ((==1) . length . factoresPrimos) [3,5..]

-- 3ª solución: Factorización usando la lista 'primes' de la librería
-- ==================================================================

menorTriangularConAlMenosNDivisores3 :: Int -> Integer
menorTriangularConAlMenosNDivisores3 n =
  head [x | x <- triangulares, nDivisores3 x >= n]

-- (nDivisores3 x) es el número de divisores de x. Por ejemplo,
--    nDivisores3 28  ==  6
nDivisores3 :: Integer -> Int
nDivisores3 1 = 1
nDivisores3 n = product [1 + length xs | xs <- group (factoresPrimos3 n)]

-- (factoresPrimos3 n) es la lista de los factores primos de n. Por
-- ejemplo,
--    factoresPrimos3 28  ==  [2,2,7]
factoresPrimos3 :: Integer -> [Integer]
factoresPrimos3 n = aux n primes
  where
    aux m (p:ps)
        | p*p > m        = [m]
        | m `mod` p == 0 = p : aux (m `div` p) (p:ps)
        | otherwise      = aux m ps

-- 4ª solución: Usando de 'primeFactors' de la librería
-- ====================================================

menorTriangularConAlMenosNDivisores4 :: Int -> Integer
menorTriangularConAlMenosNDivisores4 n =
  head [x | x <- triangulares, nDivisores4 x >= n]

-- (nDivisores4 x) es el número de divisores de x. Por ejemplo,
--    nDivisores4 28  ==  6
nDivisores4 :: Integer -> Int
nDivisores4 n = product [1 + length xs | xs <- group (primeFactors n)]

-- 5ª solución: Estilo functional (point-free)
-- ===========================================

menorTriangularConAlMenosNDivisores5 :: Int -> Integer
menorTriangularConAlMenosNDivisores5 n =
  head [x | x <- triangulares, nDivisores5 x >= n]

-- (nDivisores5 x) es el número de divisores de x. Por ejemplo,
--    nDivisores5 28  ==  6
nDivisores5 :: Integer -> Int
nDivisores5 =
  product . map ((+1) . length) . group . primeFactors

-- 6ª solución: Optimización matemática
-- ====================================

-- El n-ésimo número triangular es T(n) = n(n+1)2.
--
-- Los números n y n+1 son coprimos (no comparten factores primos). Esto
-- implica que:
-- + Si n es par:   d(T(n)) = d(n/2)⋅d(n+1)
-- + Si n es impar: d(T(n)) = d(n)⋅d((n+1)/2)
-- donde d(x) es el número de divisores de x.

menorTriangularConAlMenosNDivisores6 :: Int -> Integer
menorTriangularConAlMenosNDivisores6 n =
  head [ k*(k+1) `div` 2 | k <- [1..], nDivisoresTriangular k >= n]

nDivisoresTriangular :: Integer -> Int
nDivisoresTriangular k = g k * g (k + 1)
  where
    g i | even i    = nDivisores5 (i `div` 2)
        | otherwise = nDivisores5 i

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

verifica :: IO ()
verifica = hspec spec

specG :: (Int -> Integer) -> Spec
specG menorTriangularConAlMenosNDivisores = do
  it "e1" $
    menorTriangularConAlMenosNDivisores 5  `shouldBe` 28
  it "e2" $
    menorTriangularConAlMenosNDivisores 50 `shouldBe` 25200

spec :: Spec
spec = do
  describe "def. 1" $ specG menorTriangularConAlMenosNDivisores1
  describe "def. 2" $ specG menorTriangularConAlMenosNDivisores2
  describe "def. 3" $ specG menorTriangularConAlMenosNDivisores3
  describe "def. 4" $ specG menorTriangularConAlMenosNDivisores4
  describe "def. 5" $ specG menorTriangularConAlMenosNDivisores5
  describe "def. 6" $ specG menorTriangularConAlMenosNDivisores6

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

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

-- La propiedad es
prop_equivalencia :: Positive Int -> Bool
prop_equivalencia (Positive n) =
  all (== menorTriangularConAlMenosNDivisores1 n)
      [ menorTriangularConAlMenosNDivisores2 n
      , menorTriangularConAlMenosNDivisores3 n
      , menorTriangularConAlMenosNDivisores4 n
      , menorTriangularConAlMenosNDivisores5 n
      , menorTriangularConAlMenosNDivisores6 n
      ]

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

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

-- La comparación es
--    λ> :set +s
--    λ> menorTriangularConAlMenosNDivisores1 100
--    73920
--    (1.69 secs, 952,422,656 bytes)
--    λ> menorTriangularConAlMenosNDivisores2 100
--    73920
--    (0.04 secs, 3,739,104 bytes)
--    λ> menorTriangularConAlMenosNDivisores3 100
--    73920
--    (0.04 secs, 8,445,272 bytes)
--    λ> menorTriangularConAlMenosNDivisores4 100
--    73920
--    (0.03 secs, 7,224,216 bytes)
--    λ> menorTriangularConAlMenosNDivisores5 100
--    73920
--    (0.02 secs, 7,078,136 bytes)
--    λ> menorTriangularConAlMenosNDivisores6 100
--    73920
--    (0.03 secs, 6,139,112 bytes)
--
--    λ> menorTriangularConAlMenosNDivisores2 700
--    236215980
--    (1.26 secs, 755,192,600 bytes)
--    λ> menorTriangularConAlMenosNDivisores3 700
--    236215980
--    (2.30 secs, 3,857,324,424 bytes)
--    λ> menorTriangularConAlMenosNDivisores4 700
--    236215980
--    (1.13 secs, 3,479,164,848 bytes)
--    λ> menorTriangularConAlMenosNDivisores5 700
--    236215980
--    (1.28 secs, 3,475,136,128 bytes)
--    λ> menorTriangularConAlMenosNDivisores6 700
--    236215980
--    (0.48 secs, 1,083,563,856 bytes)