FP and C++: A Left Fold Fibonacci Implementation
So today, I was tinkering around with doing functional programming in C++ and thought about implementing one of the traditionally recursively implemented Fibonacci sequence generator. The classic problem is how would you write a function "fib" such that given a parameter "n" it will yield the nth fibonacci number.
The traditional implementation (recursive) would look like this:
unsigned long fib(int n) {
if (n <= 1uL) return 1uL;
return fib(n - 2) + fib(n - 1);
}
So that's not particularly surprising nor efficient (space or runtime-wise). So we then think about how we can optimize it. Some people will think about memoization where we'll remember previously computed fibonacci numbers so the next time we compute it we'll benefit form cached values. That's alright because it amortizes the running time as you call the fib function more and more.
If you don't know it yet, there's actually a way to get fibonacci numbers with an linear (O(n)) running time algorithm. It's no fun implementing a loop ourselves, and to show that it's possible in C++, we use the existing numeric algorithm 'accumulate' which behaves precisely as a left fold.
First, we need to implement the function object that will actually perform the binary operation that will yield the accumulated value. The good thing with the binary function object we're going to write is that the left parameter can have a different type from the right parameter. We can then use this to return a 'state' variable that is preserved as the left parameter of the next invocation of the function object.
Let's see the code as we would write it:
struct fibonacci_folder {
template <class T, class U>
pair<U,U> operator()(pair<U,U> const & state, T) const {
return make_pair(state.second, state.first + state.second);
}
};
So given the above implementation, the first (or left) parameter to the call to the fibonacci_folder function object is a pair which we can call the state object. This state object carries two values which are the 'previous' and 'current' value in the computation being used by the fibonacci fold. The second parameter though is ignored in this computation.
If we can visualize it somehow, we can start with just two numbers (0,1). As we call fibonacci_folder()( (0,1), ignored ), it will return a pair (1, 1). The next call will become then fibonacci_folder()( (1,1), ignored ), and yield (1, 2). You can continue this as long as we're iterating.
Next thing we need to do is setup our boundary iterators. If you notice, accumulate requires that you have forward iterators as the first two arguments giving the bounds upon which the function F(accumulated, *iterator) where F is the function object, accumulated is the state, and iterator is a forward iterator. We can do this simply with a Boost.Iterator called a counting_iterator<T> which simply yields a value of type T. We can then see that we can set up the left fold using accumulate with the fibonacci_folder this way:
unsigned long fib(int n) {
return accumulate(
make_counting_iterator(0),
make_counting_iterator(n),
make_pair(0uL,1uL),
fibonacci_folder()
).second; // we want the "current" value in the state
}
This now allows us to compute the nth fibonacci number in linear time using a left fold implementation.
Do you know any other interesting algorithms that are effectively converted to and are more efficient as folds instead of recursive or simply loop-based implementations? Do you think writing in this style is more elegant than simply using imperative programming principles?
I'd love to know what you think!



7 comments:
An algorithmic note: Computing Fibonacci numbers in linear time is strange, there exists an O(log n) algorithm. Raise the matrix
[ [ 1 1 ]
[ 1 0 ] ]
to the (n-1)th power, and take the TL element.
Computing Fibonacci numbers in exponential time is silly.
"Do you think writing in this style is more elegant than simply using imperative programming principles?"
Not for this kind of problem!! You spent five times the code and used far more complexity than the original code.
Look, if this was as easy to do in C++ as it is in Scheme, there would be some sense to it. But you have to create a dog's breakfast of Boost this and template parameter that just to do something that is extraordinarily simple.
Give it up! Find something useful to do with all the cool tools.
Great stuff! :)
Too bad it took me some time to finally understand it. :)
But like JonM said to bad you don't have all the syntactic sugar of a higher level language. :) But I still like it. :)
Hi JonM!
I wrote the code with C++03 standard. With C++0x, it would simply look like this:
accumulate(
make_counting_iterator(0),
make_counting_iterator(n),
make_pair(0,1),
[](
pair<unsigned long, unsigned long> const & state,
int
) {
return make_pair(
state.second, state.first + state.second
);
}
).second;
This uses C++0x lambda's which last I checked was a functional programming construct. ;-)
I agree that this problem is "too simple" for using FP in C++, but the basic idea was being presented that yes it can be done with existing tools (STL and Boost). I can think of more complex problems to solve, but then that would make the point harder to deliver though, don't you think? :-)
Interesting :-)
well,
regardless this information,
thanx anyway
isn't this algorithm O(n) also
fib(int n){
unsigned int pre_prev=1;
unsigned int prev=1,temp;
for (int i=1;i < n;i++)
{
temp=prev;
prev=pre_prev+prev;
pre_prev=temp;
}
return prev;
}
is your's better?
Hi Jaqoup, no my solution is not better than yours because essentially the left fold implementation reduces technically into the same loop you just implemented. :-) The only difference between your implementation and my implementation is that your implementation is an imperative approach, while my implementation is a functional approach.
Post a Comment