patternMinor
Haskell Ackermann function
Viewed 0 times
ackermannfunctionhaskell
Problem
I had a challenge a while back from a friend to try and produce the fastest possible program to compute
He wrote it in Java, and it took ~4.4 seconds. The below (very ugly) Haskell program that I wrote took ~1.7 seconds:
This is used on
How can I make this function more memory efficient, and faster for greater Ackermann calls?
Also, the syntax is disgusting. How can I make this more readable (maybe using monads?) without having horrible performance consequences?
A(3,16), where A is the Ackermann function.He wrote it in Java, and it took ~4.4 seconds. The below (very ugly) Haskell program that I wrote took ~1.7 seconds:
{-# LANGUAGE BangPatterns #-}
import Data.Map.Strict (Map)
import qualified Data.Map.Strict as Map
-- Main method:
main :: IO ()
main = print $ fst $ ack (I 3 16) Map.empty
-- Definitions:
data I = I {-# UNPACK #-} !Int {-# UNPACK #-} !Int
deriving (Eq, Ord)
ack :: I -> Map I Int -> (Int, Map I Int)
ack (I 0 n) r = (n + 1, r)
ack i@(I m 0) r1 = maybe bak fun (Map.lookup i r1)
where
(!val, !r2) = ack (I (m-1) 1) r1
bak = (val, Map.insert i val r2)
fun v = (v, r1)
ack i@(I m n) r1 = maybe bak2 fun2 (Map.lookup call2 r2)
where
call1 = (I m (n - 1))
(!val1, !re1) = ack call1 r1
bak1 = (val1, Map.insert call1 val1 re1)
fun1 v = (v, r1)
(!v1, !r2) = maybe bak1 fun1 (Map.lookup call1 r1)
call2 = (I (m - 1) v1)
(!val2, !re2) = ack call2 r2
bak2 = (val2, Map.insert call2 val2 re2)
fun2 v = (v, r2)This is used on
Ints specifically because A(3,16) is 524285, which within Ints range.How can I make this function more memory efficient, and faster for greater Ackermann calls?
Also, the syntax is disgusting. How can I make this more readable (maybe using monads?) without having horrible performance consequences?
Solution
Using Data.Memocombinators
Have a look at Data.Memocombinators module. It offers combinators for memoizing functions which is essentially what you are doing with the
Here is the example from the documentation on how to use it to create a memoizing fibonacci function:
Things to note:
The other thing which will help is to keep in mind that the type
So:
And thus to memoize the Ackerman function:
Again, note how
Using Array memoization
Faster results can be obtained by using arrays to memoize the functions
The only drawback is that explicit bounds have to be determined for each level function. This approach computes
Have a look at Data.Memocombinators module. It offers combinators for memoizing functions which is essentially what you are doing with the
Map.Here is the example from the documentation on how to use it to create a memoizing fibonacci function:
import Data.MemoCombinators
fib = Memo.integral fib'
where
fib' 0 = 0
fib' 1 = 1
fib' x = fib (x-1) + fib (x-2)
^^^ ^^^Things to note:
fibis the memoized version offib'which is just a helper function
fib'callsfibfor any recursive calls
The other thing which will help is to keep in mind that the type
Memo a represents a a combinator which is able to memoize a function whose argument type is a. So:
integralis a combinator which can memoize functions taking anInt
pair integral integralis a combinator which memoize functions taking an(Int,Int)
And thus to memoize the Ackerman function:
ack = (pair integral integral) ack'
where ack' (0,n) = n+1
ack' (m,0) = ack (m-1,1)
ack' (m,n) = ack (m-1, ack (m, n-1))Again, note how
ack is defined as the memoized version of ack' and ack' calls ack for recursive cases.Using Array memoization
Faster results can be obtained by using arrays to memoize the functions
ack(1,.), ack(2,.) and ack(3,.):import Data.Array
import Data.Ix
-- memoize the function f using arrays
arrayMemo bnds f = g
where g i = arr ! i
arr = array bnds [ (i,f arr i) | i <- range bnds ]
a0 n = n+1
a1 = arrayMemo (0,530000) f
where f arr 0 = a0 1
f arr n = a0 (arr ! (n-1))
a2 = arrayMemo (0,270000) f
where f arr 0 = a1 1
f arr n = a1 (arr ! (n-1))
a3 = arrayMemo (0,16) f
where f arr 0 = a2 1
f arr n = a2 (arr ! (n-1))The only drawback is that explicit bounds have to be determined for each level function. This approach computes
a3 16 in about half a second.Code Snippets
import Data.MemoCombinators
fib = Memo.integral fib'
where
fib' 0 = 0
fib' 1 = 1
fib' x = fib (x-1) + fib (x-2)
^^^ ^^^ack = (pair integral integral) ack'
where ack' (0,n) = n+1
ack' (m,0) = ack (m-1,1)
ack' (m,n) = ack (m-1, ack (m, n-1))import Data.Array
import Data.Ix
-- memoize the function f using arrays
arrayMemo bnds f = g
where g i = arr ! i
arr = array bnds [ (i,f arr i) | i <- range bnds ]
a0 n = n+1
a1 = arrayMemo (0,530000) f
where f arr 0 = a0 1
f arr n = a0 (arr ! (n-1))
a2 = arrayMemo (0,270000) f
where f arr 0 = a1 1
f arr n = a1 (arr ! (n-1))
a3 = arrayMemo (0,16) f
where f arr 0 = a2 1
f arr n = a2 (arr ! (n-1))Context
StackExchange Code Review Q#104459, answer score: 5
Revisions (0)
No revisions yet.