You only really need to cache the odd numbers. Consider in your program what happens when you start working on a number.
If you take your beginning number, X, and do a mod 4
, you end up with one of four cases:
- 0 or 2: Repeatedly dividing by 2 will eventually result in an odd number that's less than X. You have that value cached. So you can just count the number of divides by 2, add that to the cached value, and you have the sequence length.
- 1: (3x+1)/2 will result in an even number, and dividing that by 2 again will result in a number that's less than X. If the result is odd, then you already have the cached value, so you can just add 3 to it. If the result is even, repeatedly divide by 2, until you get to an odd number (which you already have cached), add 3 and the number of divisions by 2 to the cached value, and you have the sequence length.
- 3: Do the standard Collatz sequence calculation until you get to a number that's less than the starting number. Then you either have the value cached or the number is even and you repeatedly divide by 2 until you get to an odd number.
This might slow your program a little bit because you have a few more divides by 2, but it doubles your cache capacity.
You can double your cache capacity again by only saving sequence lengths for numbers where x mod 4 == 3
, but at the cost of even more processing time.
Those only give you linear increases in cache space. What you really need is a way to groom your cache so that you're not having to save so many results. At the cost of some processing time, you only need to cache the numbers that produce the longest sequences found so far.
Consider that when you compute that 27 has 111 steps, you have saved:
starting value, steps
1, 0
2, 1
3, 7
6, 8
7, 16
9, 19
18, 20
25, 23
27, 111
So when you see 28, you divide by 2 and get 14. Searching your cache, you see that the number of steps for 14 to go to 1 can't be more than 19 (because no number less than 18 takes more than 19 steps). So the maximum possible sequence length is 20. But you already have a maximum of 111. So you can stop.
This can cost you a little more processing time, but it greatly extends your cache. You'd only have 44 entries all the way up to 837799. See https://oeis.org/A006877.
It's interesting to note that if you do a logarithmic scatter plot of those numbers, you get a very close approximation of a straight line. See https://oeis.org/A006877/graph.
You could combine approaches by keeping a second cache that tells you, for numbers that are greater than the number with the current maximum, how many steps it took to get that number down below the current maximum. So in the above case, where 27 has the current maximum, you'd store 26 for the number 35, because it takes six operations (106, 53, 160, 80, 40, 20) to get 35 to 20. The table tells you that it can't take more than 20 more steps to get to 1, giving you a maximum possible of 26 steps. So if any other value reduces to 35, you add the current number of steps to 26, and if the number is less than 111, then you know you can't possibly have a new maximum with this number. If the number is greater than 111, then you have to continue to compute the entire sequence.
Whenever you find a new maximum, you add the number that generated it to your first cache, and clear the second cache.
This will be slower (my gut feel is that in the worst case it might double your processing time) than caching the results for every value, but it'll greatly extend your range.
The key here is that extending your range is going to come at the cost of some speed. That's a common tradeoff. As I pointed out above, you can do a number of things to save every nth item, which will give you an essentially larger cache. So if you save every 4th value, your cache is essentially 4 times as large as if you saved every value. But you reach the point of diminishing returns very quickly. That is, a cache that's 10 times larger than the original isn't a whole lot larger than the 9x cache.
My suggestion gives you essentially an exponential increase in cache space, at the cost of some processing time. But it shouldn't be a huge increase in processing time because in the worst case the number with the next maximum will be double the previous maximum. (Think of 27, with 111 steps, and 54, with 112 steps.) It takes a bit more code to maintain, but it should extend your range, which is currently only 30 bits, to well over 40 bits.