I had a challenge a while back from a friend to try and produce the fastest possible program to compute 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 Int
s specifically because A(3,16)
is 524285, which within Int
s 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?