0

I recently stumbled across the Ackermann function, which uses a kind of "nested recursion" to calculate a value. I implemented my own take on the function in C++, which caches intermediate results to speed up the calculation (compare implementation without caching).

Question:

ackermann will run out of stack space eventually. How should you implement a function which does "deep recursion" (calls itself many times), without running out of stack space?

My implementation:

Play with it!

#include <iostream>
#include <map>
#include <tuple>

std::map<std::tuple<int, int>, int> cache;

int ackermann(int n, int m)
{
    if (cache.count(std::tuple<int, int>(n, m)))
    {
        return cache.at(std::tuple<int, int>(n, m));
    }
    if (n == 0)
    {
        cache.insert(std::pair<std::tuple<int, int>, int>(std::tuple<int, int>(n, m), m + 1));
        return m + 1;
    }
    else
    {
        if (m == 0)
        {
            int tmp = ackermann(n - 1, 1);
            cache.insert(std::pair<std::tuple<int, int>, int>(std::tuple<int, int>(n-1, 1), tmp));
            return tmp;
        }
    }
    int tmp = ackermann(n, m - 1);
    cache.insert(std::pair<std::tuple<int, int>, int>(std::tuple<int, int>(n, m - 1), tmp));
    int tmp2 = ackermann(n - 1, tmp);
    cache.insert(std::pair<std::tuple<int, int>, int>(std::tuple<int, int>(n - 1, tmp), tmp2));
    return tmp2;
}

int main()
{
    for (int i = 0; i < 7; ++i)
    {
        for (int j = 0; j < 7; ++j)
        {
            std::cout << "ackermann of i=" << std::to_string(i) << ", j=" << std::to_string(j) << " is " << std::to_string(ackermann(i, j)) << '\n';
        }
    }
}

Output:

ackermann of i=0, j=0 is 1
ackermann of i=0, j=1 is 2
ackermann of i=0, j=2 is 3
ackermann of i=0, j=3 is 4
ackermann of i=0, j=4 is 5
ackermann of i=0, j=5 is 6
ackermann of i=0, j=6 is 7
ackermann of i=1, j=0 is 2
ackermann of i=1, j=1 is 3
ackermann of i=1, j=2 is 4
ackermann of i=1, j=3 is 5
ackermann of i=1, j=4 is 6
ackermann of i=1, j=5 is 7
ackermann of i=1, j=6 is 8
ackermann of i=2, j=0 is 3
ackermann of i=2, j=1 is 5
ackermann of i=2, j=2 is 7
ackermann of i=2, j=3 is 9
ackermann of i=2, j=4 is 11
ackermann of i=2, j=5 is 13
ackermann of i=2, j=6 is 15
ackermann of i=3, j=0 is 5
ackermann of i=3, j=1 is 13
ackermann of i=3, j=2 is 29
ackermann of i=3, j=3 is 61
ackermann of i=3, j=4 is 125
ackermann of i=3, j=5 is 253
ackermann of i=3, j=6 is 509
ackermann of i=4, j=0 is 13
ackermann of i=4, j=1 is 65533
Segmentation fault (core dumped)
User12547645
  • 6,955
  • 3
  • 38
  • 69
  • 1
    Any recursion can be converted to iteration + state. – stark Jan 31 '20 at 21:41
  • Keep in mind that "calls itself many times" means "puts a copy of itself on the stack many times". So running out of stack space is a normal result when "many" is big enough. You'll need more creative thinking like whatever inspired you to cache results. – JaMiT Jan 31 '20 at 21:41
  • 1
    The interesting property of the Ackermann function is how insanely quickly it grows. [According to Wikipedia](https://en.wikipedia.org/wiki/Ackermann_function#Table_of_values) the next value that you should get is `2^65536 - 3` which is a number that would be about 64kB large if stored exactly. For comparison the size of the largest standard integer type is typically 64 *bits*.You will not be able to calculate that number without implemening some custom arbitrary length integer type. So even if you resolve the stack overflow by writing the program iteratively, it will not help. – walnut Jan 31 '20 at 21:43
  • The value for `i=4`/`j=3` is going to already be so large that it is physically impossible to store it in its full uncompressed decimal representation on any physically possible system. (In my previous comment I should have said "8kB or 8192 bytes", not "64 kB".) – walnut Jan 31 '20 at 21:45
  • Okay. So this is less about the stack and more about the value. Still, the question remains interesting to me. How would you convert it into an interation + state @stark? – User12547645 Jan 31 '20 at 21:47

0 Answers0