Fibonacci numbers (Alice ML)
From LiteratePrograms
- Other implementations: ALGOL 68 | Alice ML | bc | C | C Plus Plus templates | dc | E | Eiffel | Erlang | Forth | FORTRAN | Haskell | Hume | Icon | Java | JavaScript | Lisp | Logo | Lua | Mercury | OCaml | occam | Oz | Pascal | PIR | PostScript | Python | Ruby | Scala | Scheme | Sed | sh | sh, iterative | Smalltalk | T-SQL | Visual Basic .NET
The Fibonacci numbers are the integer sequence 0, 1, 1, 2, 3, 5, 8, 13, 21, ..., in which each item is formed by adding the previous two. The sequence can be defined recursively by
- 1 \\ \end{cases} ."/>
Fibonacci number programs that implement this definition directly are often used as introductory examples of recursion. However, many other algorithms for calculating (or making use of) Fibonacci numbers also exist.
Contents |
Import
Alice ML requires any package imports to be located at the top of a script. We will be using the RedBlack dictionary for the memoization.
<<fib.aml>>= import functor MkRedBlackImpMap from "x-alice:/lib/data/MkRedBlackImpMap"
Recursion
A simple recursive solution can be constructed in Alice ML in a way that directly mirrors the mathematical definition of the function.
<<fib.aml>>= fun fib 0 = 0 | fib 1 = 1 | fib n if (n > 1) = fib(n - 1) + fib(n - 2) | fib _ = raise Domain
We use pattern matching on the function's parameter to handle the three cases given in the definition of the Fibonacci numbers. The first two cases we handle are the base cases of the recursion, when n = 0 or n = 1.
fun fib 0 = 0 | fib 1 = 1
In the third case, when n > 1, a pair of recursive calls are made. The first call determines the F(n-1), and the second determines F(n-2), adding the two values to compute the F(n), the nth Fibonacci number.
| fib n if (n > 1) = fib(n - 1) + fib(n - 2)
Finally, we add a final case to our pattern matching to catch all other cases. This is done for two reasons. First, Fibonacci numbers are only defined for non-negative integers. While that may be fine in math, when it comes to programming, one should be aware that invalid inputs may be used. These cases should be caught and handled. Here we raise an exception that can be handled by the caller of ackermann
. Secondly, when using the guards in pattern matching, Alice ML may be unable to determine if all possible cases are covered, and it issues a warning. Using the universal match pattern, _, we can catch any cases not covered by the above patterns (in this case anything involving negative integers).
| fib _ = raise Domain
We can test the fib function in the following manner:
- [fib 0, fib 1, fib 2, fib 3]; val it : int list = [0, 1, 1, 2] - fib 7; val it : int = 13
Memoize
Although the recursive implementation given above is elegant and close to the mathematical definition, it is not very practical. Calculating fib(n) requires calculating two smaller Fibonacci numbers, which in turn require two additional recursive calls each, and so on until all branches reach 1. As a consequence, the time required to calculate fib(n) is exponential in n (it is about Φn, where Φ is the golden ratio). To remedy this, we can employ memoization to cache previous computations.
<<fib.aml>>= local structure Memo = MkRedBlackImpMap Int val table = Memo.map() in fun memo_fib n = let val previously_computed_result = Memo.lookup(table, n) in case previously_computed_result of | SOME item => item | NONE => let fun fib 0 = 0 | fib 1 = 1 | fib n if (n > 1) = memo_fib(n - 1) + memo_fib(n - 2) | fib _ = raise Domain val result = fib n in Memo.insert(table, n, result); result end end end
The memoized fib
function recursively computes and stores the value of fib(n)
if it hasn't been previously stored in the memo
dictionary. Otherwise it simply returns the memoized value of fib(n)
.
With the memoized version, larger Fibonacci numbers can be found in no time:
- memo_fib 44; val it : int = 701408733
Iteration
To calculate the nth Fibonacci number in only n steps, we can also start with 0 and 1 and iteratively add up items n times:
<<fib.aml>>= fun iterative_fib n = let fun fib (a, b, 0) = b | fib (a, b, count) if (n > 0) = fib(a + b, a, count - 1) | fib _ = raise Domain in fib(1, 0, n) end
Streams
One can also use streams in Alice ML to build the Fibonacci numbers in a lazy fashion. The Fibonacci sequence can be defined as a function that takes in the ith and i+1st Fibonacci numbers and returns an infinite stream of Fibonacci numbers starting from i.
<<fib.aml>>= fun lazy fibgen (a, b) = a :: fibgen(b, a + b) val fibs = fibgen(0, 1)
To get the 40th number in the sequence, we can pull it from the list in the following manner:
- List.nth(fibs, 40); val it : int = 102334155
Unlimited Integer Sizes
The standard size of an integer in Alice ML is restricted to 31 bits (Int.minInt = ~1073741824 to Int.maxInt = 1073741823). In order to handle numbers outside of this range, the type for the numbers should be changed to IntInf. Since Alice ML doesn't currently support IntInf constants, we'll declare some values to hold 0 and 1.
<<fib.aml>>= val inf_0 = IntInf.fromInt 0 val inf_1 = IntInf.fromInt 1
Since we are interested in large values, we'll use the iterative version of fib to showcase how to write a fib function using infinite integers:
<<fib.aml>>= fun inf_iterative_fib n = let fun fib (a, b, count) if (count = inf_0) = b : IntInf.t | fib (a, b, count) if (count > inf_0) = fib(a + b, a, count - inf_1) | fib _ = raise Domain in fib(inf_1, inf_0, n) end
The inf_ackermann allows us to compute results with a large number of digits:
- inf_iterative_fib (IntInf.fromInt 100); val it : IntInf.t = 354224848179261915075
Download code |