<functional> is required for std::function. std::function is a wrapper for 'callable things' (i.e. usual functions, functors (aka function objects (i.e. objects that overload the () operator)), lambda expressions). A lambda expression essentially is an anonymous function object. They are especially convenient when used with higher order functions (functions that take other functions as arguments) that operate on containers (e.g. sort, find_if, ... etc.) Googling for introductory examples on the terms above will yield results much faster than I can type, so, I'll just focus on explaining the fibonacci snippet.
Consider a simple fibonacci function:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
#include <iostream>
typedef unsigned long long ull;
ull fib(int n) {
return (n == 0 || n == 1) ?
1ull :
fib(n - 1) + fib(n - 2);
}
int main()
{
for (int i = 0; i < 90; ++ i)
std::cout << fib(i) << std::endl;
}
|
Executing this will take some time (if it doesn't,
try increasing the upper limit for i I'm in love with your pc).
This is because many of the fibonacci terms are evaluated over and over again. It can be made faster by
simply caching the results of fib calls and using them later, when required:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
|
#include <iostream>
#include <map>
typedef unsigned long long ull;
ull fib(int n) {
static std::map<int, ull> cache;
if (n == 0 || n == 1)
return 1ull;
if (cache.find(n) == cache.end())
cache[n] = fib(n - 1) + fib(n - 2);
return cache[n];
}
int main()
{
for (int i = 0; i < 90; ++ i)
std::cout << fib(i) << std::endl;
}
|
There is a special term for this tranformation and it is 'memoization'. Notice that the
program executes instantly now. There are two problems with this approach though:
(1) The memoized function definition is somewhat ugly.
(2) You have to do this for every function you want to memoize.
Fortunately, both these problems can be solved by encapsulating the memoization transformation
into a separate function. That function will accept any function and return a memoized version of it:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
|
#include <functional>
#include <iostream>
#include <map>
template <class R, class V>
std::function<R (V)> memo(std::function<R (V)> fn) {
std::map<V, R> cache;
return [=](V arg) mutable {
if (cache.find(arg) == cache.end()) {
cache[arg] = fn(arg);
}
return cache[arg];
};
}
typedef unsigned long long ull;
ull fib(int n) {
return (n == 0 || n == 1) ?
1ull :
fib(n - 1) + fib(n - 2);
}
int main()
{
auto fib_m = memo(std::function<ull(int)>(fib));
for (int i = 0; i < 90; ++ i)
std::cout << fib_m(i) << std::endl;
}
|
Hmm.... There's something wrong here. This executes at the speed of the original (unmemoized) version. The problem is that only the
first call to fib is memoized. The recursive calls fib(n - 1) and fib(n - 2) use the original, unmemoized function. The next and final step is this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
|
#include <functional>
#include <iostream>
#include <map>
template <class R, class V>
std::function<R (V)> memo(std::function<R (V)> fn) {
std::map<V, R> cache;
return [=](V arg) mutable {
if (cache.find(arg) == cache.end()) {
cache[arg] = fn(arg);
}
return cache[arg];
};
}
typedef unsigned long long ull;
std::function<ull(ull)> fib = [](int n) {
return (n == 0 || n == 1) ?
1ull :
fib(n - 1) + fib(n - 2);
};
int main()
{
fib = memo(fib);
for (int i = 0; i < 90; ++ i)
std::cout << fib(i) << std::endl;
}
|
This works as expected, because now fib itself is redefined. And for this to be possible, it was
necessary to change fib's way of definition, as you can't redefine a normal function in C++.
EDIT:
DTSCode (below) wrote:thanks for this... once again im grateful that there are
smarter people than me willing to share their knowledge. |
There are no smarter people than you.
You are as smart as anyone else.
You just don't know it yet ;)