The simplest optimization (as you defined) is memoization. You yourself tried to create a memoization system, but you ran into problems with saving saved values. There are solutions for this using a convenient method, for example, using the state monad or STArray . However, there is a much simpler solution to your problem - use haskell existing memoization. Haskell remembers constant values ββby default, so if you create a value that stores collatz values, it will be automatically saved in memory!
A simple example of this is the following fibonacci definition:
fib :: Int -> Integer fib n = fibValues !! n where fibValues = 1 : 1 : zipWith (+) fibValues (tail fibValues)
fibValues is [Integer] , and since it is only a constant value, it is remembered. However, this does not mean that all this is instantly remembered, since this is a list of infinte, it will never end. Instead, values ββare only calculated if necessary, since haskell is lazy.
So, if you do something similar to your problem, you will get a memorandum without much work. However, using such a list as indicated above will not work in your solution. This is because the collatz algorithm uses many different values ββto get the result for a given number, so the container used requires random access to be effective. The obvious choice is an array.
collatzMemoized :: Array Integer Int
Then we need to fill the array with the correct values. I will write this function by pretending that there is a collatz function that computes the value of collatz for any n. Also note that arrays are a fixed size, so you must use a value to determine the maximum number for memoize. I will use a million, but any value can be used (this is memory / speed compilation).
collatzMemoized = listArray (1, maxNumberToMemoize) $ map collatz [1..maxNumberToMemoize] where maxNumberToMemroize = 1000000
It's pretty simple, a listArray set to a border, and it is assigned a list of all collatz values ββin this range. Remember that this will not immediately calculate all collatz values, since the values ββare lazy.
Now the collatz function can be written. The most important part is to check only the collatzMemoized array if the number being checked is within its borders:
collatz :: Integer -> Int collatz 1 = 1 collatz n | inRange (bounds collatzMemoized) nextValue = 1 + collatzMemoized ! nextValue | otherwise = 1 + collatz nextValue where nextValue = case n of 1 -> 1 n | even n -> n `div` 2 | otherwise -> 3 * n + 1
In ghci you can now see the effectiveness of memoization. Try collatz 200000 . It takes about 2 seconds. However, if you run it again, it will be completed instantly.
Finally, you can find a solution:
maxCollatzUpTo :: Integer -> (Integer, Int) maxCollatzUpTo n = maximumBy (compare `on` snd) $ zip [1..n] (map collatz [1..n]) where
and then printed:
main = print $ maxCollatzUpTo 1000000
If you run main, the result will print in about 10 seconds.
Now, a small problem with this approach is that it uses a lot of stack space. It works fine in ghci (which is likely to be more flexible regarding stack space). However, if you compile it and try to run the executable file, it will work (with a stack overflow). Therefore, to run the program, you need to specify more when you compile it. This can be done by adding -with-rtsopts='K64m' to the compilation options. This increases the stack to 64 MB.
Now the program can be compiled and launched:
> ghc -O3 --make -with-rtsopts='-K6m' problem.hs
Running ./problem will produce a result in less than a second.