# Increasing speed of Project Euler #14 - Longest Collatz Sequence

I'm solving Project Euler problems and uploading my solutions to GitHub. Some of my solutions are just based on math and are thanks to that very fast, but #14 is way too slow, and I have no idea how to optimize it.

"""
Longest Collatz sequence
------------------------

The following iterative sequence is defined for the set of positive integers:
n  n/2 (n is even)
n  3n + 1 (n is odd)
Using the rule above and starting with 13, we generate the following sequence:
13  40  20  10  5  16  8  4  2  1
It can be seen that this sequence (starting at 13 and finishing at 1) contains 10 terms. Although it has not been proved yet (Collatz Problem), it is thought that all starting numbers finish at 1.
Which starting number, under one million, produces the longest chain?
NOTE: Once the chain starts the terms are allowed to go above one million.

Approach:

A function collatz which returns the number of terms in the chain, given it's starting position.
It goes trough the sequence, but It also remembers previous ones using a global dictionary.
Get the maximum one.
"""

collatz_counts = {}

def collatz(n):
count = 1
start = n
while n!=1:
if n in collatz_counts:
count += collatz_counts[n]
break
if n%2==0: n/=2
else: n = 3*n+1
count += 1

collatz_counts[start] = count
return count

answer = max(((collatz(i),i) for i in range(1,1000000)))
print("The starting position generating the longest sequence is {0}".format(answer[1]))

• Is it that slow? It takes 3.6s on my computer. One optimization is to use a list for indices less than eg. tn million, and use a dictionary otherwise. Initialize that list before using it. It shave 0.4 seconds (3.7->3.3). Try implementing this in C++ to see how much comes from Python. – Quentin Pradet May 26 '13 at 14:53
• It's a little bit longer on my computer, but I'm trying to optimize as much as possible. – Nico Ekkart May 26 '13 at 15:05
• Note: Project Euler is about algorithms, not micro-optimizations. – Quentin Pradet May 26 '13 at 15:40
• I know. I have the answer, but this is just for myself, to learn more about python. Is there anything that I can do better? – Nico Ekkart May 26 '13 at 15:44

You have a bug. Try calling collatz(2) a few times and you'll see the result increment by one each time.

The source of trouble is initializing count to 1. It means you always add at least 1 to the memoized value. Note however that initializing count to 0 instead would give incorrect results when the loop terminates by the n != 1 condition.

You can avoid this bug and simplify your code at the same time: if you initialize collatz_counts to {1: 1} you can loop while n not in collatz_counts: and eliminate the if ... break.

Note also that an odd number is always followed by an even number in the sequence, so you can take two steps with n = (n * 3 + 1) // 2

I suppose you are on Python 3 because you are using print(). Note that n/=2 is float division. n becomes a float which slows down subsequent calculations. Use n //= 2 instead.

I can not say something about optimizing the python code. But wouldn't it be much faster if you cache partial chains of numbers? From my understanding of the problem it should not be impossible that sometimes collatz series overlap, so that you know that from element X in the series, the number of steps down to 1 are already precalculated. I have something like a dictionary in mind elementx->length of chain down to 1. So that, after you calculated an initial value, you make a lookup for each step, if there is a length already known; and if not cache the result at the end of the calculation.

Perhaps this is some kind of speedup to the algorithm.

• I am doing that, using the collatz_counts dictionary. – Nico Ekkart May 26 '13 at 17:11
• Oh sorry. I overlooked that on the first read ;) – Thomas Junk May 26 '13 at 18:26