DEV Community

Michée Allidjinou
Michée Allidjinou

Posted on

Fibonacci Sequence Computation in Go Programming Language

In Go, you can define a function that returns a function. Let's call the first function A and the returned one B. The function B is defined anonymously as follow return func() returnType {} and can reference the variables of function A. In case the anonymous function do reference variables from function A, we say that B closes up on all nonlocal variables that it references to form a closure. Let's take the example of a function that generates odd numbers (example taken from the book Introducing Go Build Reliable, Scalable Programs):

func main() {
    nextOdd := MakeOddGenerator()
    fmt.Println(nextOdd()) // Prints 1
    fmt.Println(nextOdd()) // Prints 3
}

MakeOddGenerator() func() uint {
    i := uint(1)
    return func() (res uint) {
        res = i
        i = 2 * i + 1
        return
    }
}
Enter fullscreen mode Exit fullscreen mode

In the above example, the variable i keeps its value between the different calls of the function nextOdd. But how does it help us in computing the elements of the Fibonacci sequence ?

Well, the most used approach to compute the elements of the Fibonacci sequence is to use recursion, as a matter of fact, recursion seems to comes out naturally from the definition of the Fibonacci' sequence. So let's look at what is the Fibonacci' sequence ? It is a sequence (put it simply, a list of elements) of positive integers denoted as F or Fib defined as follow:

  • Fib(0) = 0
  • Fib(1) = 1
  • for n greater than 1, Fib(n) = Fib(n - 1) + Fib(n - 2)

And here are the first 15 elements of the Fibonacci sequence: 0, 1 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610.

The last line Fib(n) = Fib(n - 1) + Fib(n - 2) is what gives us an intuition about using recursion for the computation. So, let's implement a recursive algorithm to compute the nth element of the Fibonacci sequence:

func FibonacciRec(x uint) uint {
    if x == 0 {
        return 0
    } else if x == 1 {
        return 1
    } else {
        return FibonacciRec(x-1) + FibonacciRec(x-2)
    }
}
Enter fullscreen mode Exit fullscreen mode

If you try this function, it may work fine with small values of x, but when I tried it with x = 50, on my machine (A Core i7 11th Gen with 16Gb of RAM running Ubuntu inside WSL2) I got these results for computing Fib(50):

Running time of recursive Fibonacci algorithm

I don't know for you but 1 minute is a long time for 'just' Fib(50). And it makes sense when you think about it. Let's analyse the call stack of Fib(n). If n is different from 0 or 1, you need at least two other function calls to get the answer, and for each one, if it is not 0 or 1, you also need at least two other function calls and so on and so on.

Call stack of recursive Fibonacci sequence
(not really a 'stack' but I thought this representation might be easier to grasp)

Now, from this 'stack', you may also notice that some values are computed twice (or more than that if you draw out the stack at a deeper depth), in order words, with the current algorithm, we cannot reuse already computed values like Fib(n-2) and Fib(n-3) in our case.

Let's try to solve this issue using closures in Go. First we want a function that returns a function:

func FibonacciDyna() func(y int) int {

    return func(y int) int {
    }
}
Enter fullscreen mode Exit fullscreen mode

Next, we need to 'remember' the previously computed elements of the sequence, for that we add an array (or slice in Go terminology) variable to the englobing function:

func FibonacciDyna() func(y int) int {
    fib := []int{0,1}
    return func(y int) int {
    }
}
Enter fullscreen mode Exit fullscreen mode

Notice that we add the first two elements of the Fibonacci sequence. Then, we need to consider multiple cases, but before that, we also have to pay attention to how the nth element of the sequence corresponds to the nth element of our array:

func FibonacciDyna() func(y int) int {
    fib := []int{0,1}
    return func(y int) int {
        // negative numbers are not supported
        if y < 0 {
            panic("negative number not supported")
        }
        // We already computed the value.
        if y < int(len(fib)) {
            return fib[y]
        }
        // We have the two previous value Fib(n-1) and
        // Fib(n-2) needed for computation.
        if y-2 >= 0 && y < len(fib) {
            // Store the newly computed value
            fib = append(fib, fib[y-1]+fib[y-2])
            return fib[y]
        }
        // We don't have one or both of the two previous
        // value needed for computation.
        // We then compute and store all the missing values.
        for i:= len(fib); i<= y; i++ {
            fib = append(fib, fib[y-1]+fib[y-2])
        }
        return fib[y]
    }
}
Enter fullscreen mode Exit fullscreen mode

You can even add some tests:

func TestFibonacciOf0ShouldReturn0(t *testing.T) {
    x := 0
    want := 0

    getFibDyn := FibonacciDynamic()
    have := getFibDyn(x)

    if want != have {
        t.Fatalf(`FibonacciDynamic(%d) = %d, want equal for %d`, x, have, want)
    }
}

func TestFibonacciOf1ShouldReturn1(t *testing.T) {
    x := 1
    want := 1

    getFibDyn := FibonacciDynamic()
    have := getFibDyn(x)

    if want != have {
        t.Fatalf(`FibonacciDynamic(%d) = %d, want equal for %d`, x, have, want)
    }
}

func TestFibonacciOf10ShouldReturn55(t *testing.T) {
    x := 10
    want := 55

    getFibDyn := FibonacciDynamic()
    have := getFibDyn(x)

    if want != have {
        t.Fatalf(`FibonacciDynamic(%d) = %d, want equal for %d`, x, have, want)
    }
}
Enter fullscreen mode Exit fullscreen mode

And now let's run it:

Running time of dynamic Fibonacci algorithm

Much better !

That's it, let me know what you think in the comments below !

You may find the source code here

Top comments (2)

Collapse
 
rbauhn profile image
Robin Bauhn

Nice article!
Instead of keeping a slice of all numbers you can make it even more efficient by just keeping two ints and iterate from 1 to y

Collapse
 
yoshua70 profile image
Michée Allidjinou

Yes, I thought about it, but I really wanted to try the 'caching' of the values. With the slice, I can get values I already computed, without computation, so I found it more interesting, but I will definitively try your approach !