Sandro Tosi writes in his blog about the Collatz Conjecture, and gives a nice Python script to compute what is the longest collatz sequence in the [1,1e6] range (Project Euler problem 14).
Intrigued by it, I decided to give it a try myself. My first code was in C, just to give me something to base any future trade-off on.
The code is as follows:
#include <stdio.h> #include <stdlib.h> int main(int argc, char **argv) { long unsigned int n, m, i, max_i=1; int len, max_len=0; if (argc!=2) { printf("Needs a numeric argument\n"); return 1; } n = atol(argv[1]); for (i=2; i<=n; i++) { m = i; len = 0; while (m > 1) { if (m % 2 == 0) m = m / 2; else m = 3 * m + 1; len++; } if (len>max_len) { max_len=len; max_i=i; } } printf("Max sequenze is %d long for %ld\n", max_len, max_i); return 0; }
Nothing fancy here, some brute force and the result is 524 for 837799 (as expected). The time it takes my computer to find the solution is below 1 sec (about 0.7sec).
Second try is in Haskell. A recursive solution is obviously as simple as:
collatz :: Int -> Int collatz n | n == 1 = 0 | even n = 1+collatz(div n 2) | otherwise = 1+collatz(3*n+1)
Even though this doesn’t suffer from stack limitations as its Python sibling, I preferred to use a more straight-forward iterative solution:
collatz :: Int -> Int collatz n | n == 1 = 0 | even n = div n 2 | otherwise = 3*n+1
So now one can use the following to compute a sequence, giving a starting integer:
collatz_list :: Int -> [Int] collatz_list = takeWhile (>0) . iterate collatz
And to find the solution the following code:
collatz_scan :: Int -> Int -> Int collatz_scan a b = foldr1 max $ map (length . collatz_list) [a..b]
Note that I use foldr1 explicitly, and not simply maximum. This is because maximum is implemented with foldl which, as you may know, suffer indeed from stack limitations.
The code is simple and does the job, however it is tremendously slow.
I don’t know exactly what is the culprit here, but I think it is related to lazy evaluation.
This won’t do, I need something to test (and possibly beat) the Python version. I only know two scripting languages well enough, and these are Lua and Perl.
I quite like Lua, it is simple yet powerful, and it is a joy to program with it.
I went on with three versions; the first was again a simple recursive one:
function collatz(n) if (n==1) then return 0 end count = count+1 if ((n % 2)==0) then return collatz(n/2) end return collatz(3*n+1) end function collatz_scan(min,max) max_count = 0 max_n = 0 for n=min,max do count = 0 collatz(n) if (count > max_count) then max_count = count max_n = n end end print(max_count,max_n) end
Nice (at least Lua implements proper tail recursion), but not so fast. The iterative version was better:
function collatz(n) local count = 0 while n > 1 do count = count + 1 if (n % 2) == 0 then n = n/2 else n = 3*n+1 end end return count end function collatz_scan(min,max) max_count = 0 max_n = 0 for n=min,max do count = collatz(n) if (count > max_count) then max_count = count max_n = n end end print(max_count,max_n) end
And obviously the memoized version:
cache = {} function collatz(n) if (cache[n]==nil) then if (n==1) then return 0 end if ((n % 2)==0) then cache[n] = 1+collatz(n/2) else cache[n] = 1+collatz(3*n+1) end end return cache[n] end function collatz_scan(min,max) max_count = 0 max_n = 0 for n=min,max do count = collatz(n) if (count > max_count) then max_count = count max_n = n end end print(max_count,max_n) end
This is pretty fast indeed, about 2.4 sec (which includes launching the “interpreter” and bytecompiling the script). Considering that the Python one needs 5 sec. Lua wins hands down here.