Given that you do y=factorial(n)
inside your decomp(n)
function, I'm gonna assume that this is really about factoring factorials. And my review is gonna be:
- Don't compute the factorial at all.
- Use a better function name.
- Document what the function does.
- Separate the factorization from the presentation. Return something more useful than a string, like pairs of primes and their exponents. That can then be pretty-formatted if desired, or used for computations.
- Test your code, because for example for
decomp(10)
returns '2 * 3 * 5 * 7'
, missing the exponents.
Instead of computing the factorial and then factoring that, factor all the numbers from 1 to n and combine their prime factorizations. Or even better... compute, for every prime from 1 to n, its exponent in n!. Let's say we want to find the exponent for 3 in 100!. Every third number from 1 to 100 (i.e., 3, 6, 9, 12, etc) contributes a factor 3, so there are \$\lfloor\frac{100}{3}\rfloor=33\$ numbers that contribute factor 3. Of those, every third number (i.e., 9, 18, 27, etc) contributes another factor 3. Of those, every third number (i.e., 27, 54 and 81) contributes yet another. And 81 even contributes a fourth. So the exponent for 3 in 100! is 33+11+3+1=48.
Here's an O(n) solution doing that. Takes my laptop about 0.33 seconds to factor 1,000,000!. Compare that to just computing math.factorial(10**6)
, which already takes about 10 seconds.
def factorial_factorization(n):
"""Return the prime factorization of n! as list of (prime, exponent) pairs."""
# Compute primes from 2 to n, see https://cp-algorithms.com/algebra/prime-sieve-linear.html
lp = [None] * (n+1)
primes = []
for i in range(2, n+1):
if not lp[i]:
primes.append(i)
lp[i] = i
for p in primes:
if p > lp[i] or i * p > n:
break
lp[i * p] = p
# Find prime exponents for n!
result = []
for p in primes:
e = 0
m = n // p
while m:
e += m
m //= p
result.append((p, e))
return result
Demo:
>>> factorial_factorization(3)
[(2, 1), (3, 1)]
>>> factorial_factorization(4)
[(2, 3), (3, 1)]
>>> factorial_factorization(100)
[(2, 97), (3, 48), (5, 24), (7, 16), (11, 9), (13, 7), (17, 5), (19, 5), (23, 4), (29, 3), (31, 3), (37, 2), (41, 2), (43, 2), (47, 2), (53, 1), (59, 1), (61, 1), (67, 1), (71, 1), (73, 1), (79, 1), (83, 1), (89, 1), (97, 1)]
>>> factorial_factorization(10**6)[:10]
[(2, 999993), (3, 499993), (5, 249998), (7, 166664), (11, 99998), (13, 83332), (17, 62497), (19, 55553), (23, 45453), (29, 35713)]
If you want a less useful but prettier presentation, that's easy to do then:
>>> n = 42
>>> print(f'{n}! =', ' * '.join(f'{p}^{e}' if e > 1 else f'{p}'
for p, e in factorial_factorization(n)))
42! = 2^39 * 3^19 * 5^9 * 7^6 * 11^3 * 13^3 * 17^2 * 19^2 * 23 * 29 * 31 * 37 * 41
Checking that:
>>> s = '2^39 * 3^19 * 5^9 * 7^6 * 11^3 * 13^3 * 17^2 * 19^2 * 23 * 29 * 31 * 37 * 41'
>>> (result := eval(s.replace('^', '**')))
1405006117752879898543142606244511569936384000000000
>>> (expect := math.factorial(42))
1405006117752879898543142606244511569936384000000000
>>> result == expect
True