Unexpected fibonacci Implementation

It's interesting to see a Fibonacci example in the course of BuckleScript's User Manual, which is different from the usual implementation method. I'm afraid it's not convenient to query later. Here's a summary of my understanding.

The example code in BuckleScript is ocaml, but I'll convert it to Js later.

let fib n =
  let rec aux n a b =
    if n = 0 then a
    else
      aux (n - 1) b (a+b)
  in aux n 1 1

Normal recursive version

The normal version is a simple and straightforward logic and one of my favorite versions.

function fibonacci(n){
   if(n==0)return 0
   else if(n==1)return 1
   else return fibonacci(n-1) + fibonacci(n-2)
}

The code is beautiful, logical and clear, but one problem with this version is that there are a lot of repeated calculations that result in a decline in the performance engineering index level. When n is 5, it calculates fibonacci(4) + fibonacci(3). When n is 4, it calculates fibonacci(3) + fibonacci(2) and there are a lot of repetitions.

for Loop Version

Recursion has performance problems, and it's easy to wonder if it can be done with loops.

function fibonacci(n){
  var last = 1
  var last2 = 0
  var current = last2
  for(var i=1;i<=n;i++){
    last2 = last
    last = current
    current = last + last2
  }
  return current
}

It's true that using loops for performance is much better, but it's not the recursion itself, it's the algorithmic problem of the previous version. Loop also has a looping problem, that is, there are many state variables. This implementation uses four state variables (last,last2,current,i) and state variables are the source of bugs. In the process of writing, modifying and deleting, we need to be extra careful. A little neglect will lead to bugs. Logic expression is not so simple and direct as recursion.

Removing Recursive Versions of Repetitive Computing

This is the example at the beginning of the article to convert to the version of js

function fib(n){
   function fib_(n,a,b){
       if(n==0)  return a
       else return fib_(n-1,b,a+b)
   }
   return fib_(n,0,1)
}

Making the first two digits into parameters skillfully avoids repeated calculation and achieves performance improvement.

Using Memory Function to Optimize Normal Recursive Version

The fibonacci we write is a pure function( /pure-function.html ) I can optimize it with memory functions.

function memozi(fn){
  var r = {}
  return function(n){
    if(r[n] == null){
      r[n] = fn(n)
      return r[n]
    }else{
        return r[n]
    }
  }
}

var fibfn = memozi(function(n){
    if(n==0){
        return 0
    }else if(n==1){
        return 1
    }else{
        return fibfn(n-1) + fibfn(n-2)
    }
})

Inert Sequence Version

We have implemented an output for input. I wonder if you have ever thought that fibonacci is an infinite sequence of numbers. Can we express it in some form? The answer must be inert sequence.

Inert Sequence is a concept that should originate from stream concept in SICP. It stores a sequence but only evaluates when it is really used. So we can tell it the evaluation rules and then evaluate when it is used to produce an infinite sequence without causing memory exhaustion.

This is a concise version of the implementation

function cos(a,fn){
    function r(flag){
        if(flag == 'first') {
            return a
        }else{
            return fn()
        }
    }

    return r;
}

var endFlag = {'placeholder@end':'end'}
function end(){
    return endFlag
}

function car(seq){
    return seq('first')
}
function cdr(seq){
    return seq('xxx')
}
function toArray(seq){
    var h = car(seq);
    var t = cdr(seq);
    if(h==endFlag){
        return []
    }else{
        return [h].concat(toArray(t))
    }
}
function map(fn,seq){
    var h = car(seq)
    return cos(fn(h),function(){ return map(fn,cdr(seq))})
}
function zip(fn,seq1,seq2){
    var h1 = car(seq1)
    var h2 = car(seq2)
    return cos(fn(h1,h2),function(){
        return zip(fn,cdr(seq1),cdr(seq2))
    })
}
function take(n,seq){
    if(n == 0){
        return end
    }else if(cdr(seq) == endFlag){
        return seq;
    }else{
        return cos(car(seq),function(){return take(n-1,cdr(seq))})
    }
}
function drop(n,seq){
    var h = car(seq)
    if(n==0){
        return seq
    }else if(h == endFlag){
        return seq
    }else{
        return drop(n-1,cdr(seq))
    }
}

var fibSeq = cos(0,function(){
    return cos(1,function(){
        return zip(function(a,b){return a+b},fibSeq,cdr(fibSeq))
    })
})

You can call toArray (take (10, fibSeq) under console to see the output. Never stuck to Array (fibSeq) because fibSeq is an infinite toArray that always evaluates.

> http://www.diqye.com/fibonacci.html

Posted by Comtemplative on Thu, 27 Jun 2019 15:09:31 -0700