Instead of generating 3 million numbers and store them in memory, you only need to generate 3 of them at a time (one triangular, one pentagonal and one hexagonal) and compare them. The lowest should be advanced to the next of its kind and the processus repeated. That way you only generate the amount of numbers you need to achieve your goal and you don't put that much pressure on your memory.
You can use 3 simples functions to generate each kind of number:
def triangular_numbers():
n = 0
while True:
n += 1
yield (n * (n + 1)) // 2
def pentagonal_numbers():
n = 0
while True:
n += 1
yield (n * (3 * n - 1)) // 2
def hexagonal_numbers():
n = 0
while True:
n += 1
yield (n * (2 * n - 1))
And combine them to generate each number that are the 3 at the same time:
def triangular_pentagonal_and_hexagonal_numbers():
"""yields triangles that are also pentagons and hexagons."""
triangles = triangular_numbers()
pentagons = pentagonal_numbers()
hexagons = hexagonal_numbers()
t = next(triangles)
p = next(pentagons)
h = next(hexagons)
while True:
if t == p == h:
yield t
m = min(t, p, h)
if m == t:
t = next(triangles)
elif m == p:
p = next(pentagons)
else:
h = next(hexagons)
You now only need to ask this function for the number you’re after:
def main():
numbers = triangular_pentagonal_and_hexagonal_numbers()
one = next(numbers)
assert one == 1
given = next(numbers)
assert given == 40755
return next(numbers)
if __name__ == '__main__':
print(main())
Now that it works, you can simplify the first 3 functions using itertools.count
:
def triangular_numbers():
yield from ((n * (n + 1)) // 2 for n in count(start=1))
def pentagonal_numbers():
yield from ((n * (3 * n - 1)) // 2 for n in count(start=1))
def hexagonal_numbers():
yield from (n * (2 * n - 1) for n in count(start=1))
Comparing to your approach:
$ python emadboctor.py
1533776805
Time: 1.0859220027923584 seconds.
Mine is around 20 times faster:
$ python -m timeit 'import tri_pen_hexa; tri_pen_hexa.main()'
5 loops, best of 5: 50.4 msec per loop