Ir al contenido principal

Número de divisores

Definir la función

   numeroDivisores :: Integer -> Integer

tal que (numeroDivisores x) es el número de divisores de x. Por ejemplo,

   numeroDivisores 12  ==  6
   numeroDivisores 25  ==  3
   length (show (numeroDivisores (product [1..3*10^4])))  ==  1948

1. Soluciones en Haskell

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

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

numeroDivisores1 :: Integer -> Integer
numeroDivisores1 x =
  genericLength [y | y <- [1..x], x `mod` y == 0]

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

numeroDivisores2 :: Integer -> Integer
numeroDivisores2 1 = 1
numeroDivisores2 x
  | esCuadrado x = 2 * genericLength [y | y <- [1..raizEntera x],
                                          x `mod` y == 0] - 1
  | otherwise    = 2 * genericLength [y | y <- [1..raizEntera x],
                                          x `mod` y == 0]

-- (raizEntera x) es el mayor número entero cuyo cuadrado es menor o
-- igual que x. Por ejemplo,
--    raizEntera 3  ==  1
--    raizEntera 4  ==  2
--    raizEntera 5  ==  2
--    raizEntera 8  ==  2
--    raizEntera 9  ==  3
raizEntera :: Integer -> Integer
raizEntera x = floor (sqrt (fromInteger x))

-- (esCuadrado x) se verifica si x es un cuadrado perfecto. Por ejemplo,
--    esCuadrado 9  ==  True
--    esCuadrado 7  ==  False
esCuadrado :: Integer -> Bool
esCuadrado x =
  x == (raizEntera x)^2

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

numeroDivisores3 :: Integer -> Integer
numeroDivisores3 =
  genericLength . divisores

-- (divisores x) es la lista de los divisores de x. Por ejemplo,
--    divisores 12  ==  [1,3,2,6,4,12]
--    divisores 25  ==  [1,5,25]
divisores :: Integer -> [Integer]
divisores = map (product . concat)
          . productoCartesiano
          . map inits
          . group
          . primeFactors

-- (productoCartesiano xss) es el producto cartesiano de los conjuntos
-- xss. Por ejemplo,
--    λ> productoCartesiano [[1,3],[2,5],[6,4]]
--    [[1,2,6],[1,2,4],[1,5,6],[1,5,4],[3,2,6],[3,2,4],[3,5,6],[3,5,4]]
productoCartesiano :: [[a]] -> [[a]]
productoCartesiano []       = [[]]
productoCartesiano (xs:xss) =
  [x:ys | x <- xs, ys <- productoCartesiano xss]

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

numeroDivisores4 :: Integer -> Integer
numeroDivisores4 = genericLength
                 . map (product . concat)
                 . sequence
                 . map inits
                 . group
                 . primeFactors

-- 5ª solución
-- ===========

numeroDivisores5 :: Integer -> Integer
numeroDivisores5 =
  product . map ((+1) . genericLength) . group . primeFactors

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

verifica :: IO ()
verifica = hspec spec

specG :: (Integer -> Integer) -> Spec
specG numeroDivisores = do
  it "e1" $
    numeroDivisores 12  `shouldBe`  6
  it "e2" $
    numeroDivisores 25  `shouldBe`  3

spec :: Spec
spec = do
  describe "def. 1" $ specG numeroDivisores1
  describe "def. 2" $ specG numeroDivisores2
  describe "def. 3" $ specG numeroDivisores3
  describe "def. 4" $ specG numeroDivisores4
  describe "def. 5" $ specG numeroDivisores5

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

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

-- La propiedad es
prop_numeroDivisores :: Positive Integer -> Bool
prop_numeroDivisores (Positive x) =
  all (== numeroDivisores1 x)
      [ numeroDivisores2 x
      , numeroDivisores3 x
      , numeroDivisores4 x
      , numeroDivisores5 x]

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

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

-- La comparación es
--    λ> numeroDivisores1 (product [1..10])
--    270
--    (1.67 secs, 726,327,208 bytes)
--    λ> numeroDivisores2 (product [1..10])
--    270
--    (0.01 secs, 929,000 bytes)
--
--    λ> numeroDivisores2 (product [1..16])
--    5376
--    (2.10 secs, 915,864,664 bytes)
--    λ> numeroDivisores3 (product [1..16])
--    5376
--    (0.01 secs, 548,472 bytes)
--
--    λ> numeroDivisores3 (product [1..30])
--    2332800
--    (3.80 secs, 4,149,811,688 bytes)
--    λ> numeroDivisores4 (product [1..30])
--    2332800
--    (0.59 secs, 722,253,848 bytes)
--    λ> numeroDivisores5 (product [1..30])
--    2332800
--    (0.00 secs, 587,856 bytes)

2. Soluciones en Python

from itertools import combinations, groupby
from itertools import product as cartesian_product
from math import prod
from timeit import Timer, default_timer

from hypothesis import given
from hypothesis import strategies as st
from sympy import divisor_count, divisors, factorint

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

def numeroDivisores1(x: int) -> int:
    return len([y for y in range(1, x+1) if x % y == 0])

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

# raizEntera(x) es el mayor número entero cuyo cuadrado es menor o
# igual que x. Por ejemplo,
#    raizEntera(3)  ==  1
#    raizEntera(4)  ==  2
#    raizEntera(5)  ==  2
#    raizEntera(8)  ==  2
#    raizEntera(9)  ==  3
def raizEntera(x: int) -> int:
    def aux(a: int, b: int) -> int:
        c = (a+b) // 2
        d = c**2
        if d == x:
            return c
        if c == a:
            return c
        if x <= d:
            return aux(a,c)
        return aux(c,b)
    return aux(1,x)

# esCuadrado(x) se verifica si x es un cuadrado perfecto. Por ejemplo,
#    esCuadrado(9)  ==  True
#    esCuadrado(7)  ==  False
def esCuadrado (x: int) -> bool:
    return x == raizEntera(x) ** 2

def numeroDivisores2(x: int) -> int:
    if x == 1:
        return 1
    if esCuadrado(x):
        return 2 * len([y for y in range(1, 1+raizEntera(x)) if x % y == 0]) - 1
    return 2 * len([y for y in range(1, 1+raizEntera(x)) if x % y == 0])

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

# primeFactors(n) es la lista de los factores primos de n. Por ejemplo,
#    >>> primeFactors(12)
#    [2, 2, 3]
def primeFactors(n: int) -> list[int]:
    return [a for a, b in factorint(n).items() for _ in range(b)]

# subsequences(xs) es la lista de las subsecuencias de xs; es decir, de
# las listas obtenidas eliminando algunos elementos de xs sin cambiar el
# orden de los elementos restantes. Por ejemplo,
#    >>> subsequences([1,2,3])
#    [[], [1], [2], [3], [1, 2], [1, 3], [2, 3], [1, 2, 3]]
def subsequences(xs: list[int]) -> list[list[int]]:
    return [list(ys)
            for i in range(len(xs) + 1)
            for ys in combinations(xs, i)]

# divisores3(n) es el conjunto de los divisores de n. Por ejemplo,
#    divisores3(30) ==  [1,2,3,5,6,10,15,30]
def divisores3(n: int) -> list[int]:
    return sorted(set(map(prod, subsequences(primeFactors(n)))))

def numeroDivisores3(n: int) -> int:
    return len(divisores3(n))

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

# inits(xs) es la lista de los segmentos iniciales de xs. Por ejemplo,
#    >>> inits([3,2,5,2])
#    [[], [3], [3, 2], [3, 2, 5], [3, 2, 5, 2]]
def inits(xs: list[int]) -> list[list[int]]:
    return [xs[:i] for i in range(len(xs) + 1)]

# productoCartesiano(xss) es el producto cartesiano de los conjuntos
# xss. Por ejemplo,
#    >>> productoCartesiano([[[2, 2]], [[], [3]], [[], [5]]])
#    [[[2,2],[],[]],[[2,2],[],[5]],[[2,2],[3],[]],[[2,2],[3],[5]]]
def productoCartesiano(xss: list[list[list[int]]]) -> list[list[list[int]]]:
    return [list(t) for t in cartesian_product(*xss)]

# concat(xss) es la lista obtenida concatenando los elementos de
# xss. Por ejemplo,
#    >>> concat([[2], [1,3], [5,2]])
#    [2, 1, 3, 5, 2]
def concat(xss: list[list[int]]) -> list[int]:
    return sum(xss, [])

# divisores4(n) es el conjunto de los divisores de n. Por ejemplo,
#    divisores4(30) ==  [1,2,3,5,6,10,15,30]
def divisores4(n: int) -> list[int]:
    factores = primeFactors(n)
    factoresAgrupados = [list(g) for k, g in groupby(factores)]
    return sorted({prod(concat(xss))
                   for xss in productoCartesiano(list(map(inits,
                                                          factoresAgrupados)))})

def numeroDivisores4(n: int) -> int:
    return len(divisores4(n))

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

def numeroDivisores5(n: int) -> int:
    factores = primeFactors(n)
    factoresAgrupados = [list(g) for k, g in groupby(factores)]
    return prod([1 + len(xs) for xs in factoresAgrupados])

# 6ª solución
# ===========

def numeroDivisores6(n: int) -> int:
    return len(divisors(n))

# 7ª solución
# ===========

def numeroDivisores7(n: int) -> int:
    return divisor_count(n)

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

def test_numeroDivisores() -> None:
    for numeroDivisores in [numeroDivisores1,
                            numeroDivisores2,
                            numeroDivisores3,
                            numeroDivisores4,
                            numeroDivisores5,
                            numeroDivisores6,
                            numeroDivisores7]:
        assert numeroDivisores(12)  ==  6
        assert numeroDivisores(25)  ==  3
    print("Verificado")

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

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

# La propiedad es
@given(st.integers(min_value=1, max_value=1000))
def test_numeroDivisores_equiv(x: int) -> None:
    r = numeroDivisores1(x)
    assert numeroDivisores2(x) == r
    assert numeroDivisores3(x) == r
    assert numeroDivisores4(x) == r
    assert numeroDivisores5(x) == r
    assert numeroDivisores6(x) == r
    assert numeroDivisores7(x) == r

# La comprobación es
#    >>> test_numeroDivisores_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('numeroDivisores1(prod(range(1, 12)))')
#    2.02 segundos
#    >>> tiempo('numeroDivisores2(prod(range(1, 12)))')
#    0.00 segundos
#    >>> tiempo('numeroDivisores3(prod(range(1, 12)))')
#    0.06 segundos
#
#    >>> tiempo('numeroDivisores2(prod(range(1, 15)))')
#    0.06 segundos
#    >>> tiempo('numeroDivisores3(prod(range(1, 15)))')
#    1.52 segundos
#
#    >>> tiempo('numeroDivisores2(prod(range(1, 18)))')
#    1.56 segundos
#    >>> tiempo('numeroDivisores4(prod(range(1, 18)))')
#    0.04 segundos
#
#    >>> tiempo('numeroDivisores4(prod(range(1, 30)))')
#    4.29 segundos
#    >>> tiempo('numeroDivisores5(prod(range(1, 30)))')
#    0.00 segundos
#    >>> tiempo('numeroDivisores6(prod(range(1, 30)))')
#    0.96 segundos
#    >>> tiempo('numeroDivisores7(prod(range(1, 30)))')
#    0.00 segundos
#
#    >>> tiempo('numeroDivisores5(prod(range(1, 32)))')
#    0.00 segundos
#    >>> tiempo('numeroDivisores6(prod(range(1, 32)))')
#    2.79 segundos
#    >>> tiempo('numeroDivisores7(prod(range(1, 32)))')
#    0.00 segundos
#
#    >>> tiempo('numeroDivisores5(prod(range(1, 4*10**4)))')
#    4.91 segundos
#    >>> tiempo('numeroDivisores7(prod(range(1, 4*10**4)))')
#    4.88 segundos