Created
February 22, 2009 16:33
-
-
Save zed/68515 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
"""Solution for project euler problem #12. | |
Based on: | |
http://stackoverflow.com/questions/571488/project-euler-12-in-python/571526#571526 | |
""" | |
def ndiv(n, prime_factors): | |
"""Return number of divisors of `n`. | |
prime_factors: prime factors of `n`. | |
>>> from itertools import starmap | |
>>> list(starmap(ndiv, ((1, []), (12, [2, 3])))) | |
[1, 6] | |
""" | |
assert n > 0 | |
phi = 1 | |
for prime in prime_factors: | |
alpha = 0 # multiplicity of `prime` in `n` | |
q, r = divmod(n, prime) | |
while r == 0: # `prime` is a factor of `n` | |
n = q | |
alpha += 1 | |
q, r = divmod(n, prime) | |
phi *= alpha + 1 # see http://en.wikipedia.org/wiki/Divisor_function | |
return phi | |
def prime_factors_gen(): | |
"""Yield prime factors for each natural number. | |
Based on | |
http://stackoverflow.com/questions/567222/simple-prime-generator-in-python/568618#568618 | |
>>> from itertools import islice | |
>>> list(islice(prime_factors_gen(), 20)) #doctest:+NORMALIZE_WHITESPACE | |
[(1, []), (2, [2]), (3, [3]), (4, [2]), (5, [5]), (6, [3, 2]), | |
(7, [7]), (8, [2]), (9, [3]), (10, [5, 2]), (11, [11]), (12, [3, 2]), | |
(13, [13]), (14, [7, 2]), (15, [5, 3]), (16, [2]), (17, [17]), | |
(18, [3, 2]), (19, [19]), (20, [5, 2])] | |
""" | |
D = {1:[]} # nonprime -> prime factors of `nonprime` | |
#NOTE: dictionary could be replaced by priority queue | |
q = 1 | |
while True: # Sieve of Eratosthenes algorithm | |
if q not in D: # `q` is a prime number | |
D[q + q] = [q] | |
yield q, [q] | |
else: # q is a composite | |
for p in D[q]: # `p` is a factor of `q` | |
# therefore `p` is a factor of `p + q` too | |
D.setdefault(p + q, []).append(p) | |
yield q, D[q] | |
del D[q] | |
q += 1 | |
def highly_composite_triangular(max_ndivisors): | |
""" | |
Return the smallest triangular number that has more than | |
max_ndivisors divisors. | |
>>> # the first 25 highly compisite triangular numbers | |
>>> hcts = (1, 3, 6, 28, 36, 120, 300, 528, 630, 2016, | |
... 3240, 5460, 25200, 73920, 157080, 437580, | |
... 749700, 1385280, 1493856, 2031120, 2162160, | |
... 17907120, 76576500, 103672800, 236215980) | |
... | |
>>> # corresponding hct has >= number of divisors | |
>>> ndivs = (1, 2, 3, 5, 7, 10, 17, 19, 21, 25, 37, 41, | |
... 49, 91, 113, 129, 145, 163, 169, 193, 241, | |
... 321, 481, 577, 649) | |
>>> for maxndiv, t in zip(ndivs, hcts): | |
... h = highly_composite_triangular(maxndiv - 1) | |
... if h != t: | |
... print maxndiv, h, t | |
... break | |
... else: | |
... print "ok" | |
... | |
ok | |
""" | |
ndivs = {0: 0} # n -> number of divisors | |
for n, pfs in prime_factors_gen(): | |
# save number of divisor of `n` | |
ndivs[n] = ndiv(n, pfs) | |
# decompose `(n-1)`th triangular number: `n * (n - 1) // 2` | |
half, odd = (n//2, n-1) if n % 2 == 0 else ((n - 1)//2, n) | |
# n and (n-1) do not have common factors, therefore | |
# ndiv(n * (n - 1)) == ndiv(n) * ndiv(n-1) | |
#NOTE: we already cached ndiv therefore there is no need to | |
# to save ndivs[half], ndivs[odd] for further use | |
if ndivs[half] * ndivs[odd] > max_ndivisors: | |
return half * odd # `(n-1)`th triangular number | |
def main(): | |
import time | |
start = time.clock() | |
h = highly_composite_triangular(500) | |
print "%d (%.2g seconds)" % (h, time.clock() - start) | |
if __name__=="__main__": | |
main() | |
import doctest; doctest.testmod() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment