-1

Probably not a lot of explanation needed as to what this is, and it even works exactly the way I want it to. My real problem is program termination. I've output traced my return values and the nested for loops I'm using in my main function to step through values. I can see no reason for this happening, but it's taking the program about 10 extra minutes after the last pass through my loops to actually terminate. Although my loop indices are incrementing (because of the precheck), my Ackermann function is evidently not executing an extra iteration (not that I'd want it to anyway). On the other hand, the only logical explanation is that the loop is not breaking, but if that were the case, my Ackermann function should return a new b value. So the only other cause I can think of for this is that it's taking the garbage collection that long to clear my data structures and flush the memory heap. For those not familiar, the idea here is to implement what is traditionally presented as an ultra cumbersome recursive function as an iterative function. Recursively:

Given positive integers m and n: If m = 0, return n + 1; Else If n = 0, return Ackermann(m - 1, 1); Else return Ackermann(m - 1, Ackermann(m, n - 1)). So iteratively, the idea is to use a stack to emulate the recursive function calls so that you can use memory from the heap and you're not dependent on the call stack size, which limits your execution time. I fear I'm overlooking something in my flow that's causing these long delays between the time the computations finish, and the time my program reaches the point where the user makes a clean exit.

Here's my code:

 static void Main(string[] args)
    {
        ulong i, j;
        i = j = 0;
        for (i = 1; i <= 3; i++)
            for (j = 1; j <= 15; j++)
                Console.WriteLine("[{0}] Ackermann({1},{2}) = {3}", DateTime.Now, i, j, Ackermann(i, j));
        Console.WriteLine("Press any key to continue...");
        Console.ReadKey();
    }

    static ulong Ackermann(ulong a, ulong b)
    {
        Stack<ulong> ackStack = new Stack<ulong>();

        ackStack.Push(a);
        while (ackStack.Count > 0)
        {
            a = ackStack.Pop();
            if (a == 0)
                b++;
            else if (b == 0)
            {
                ackStack.Push(--a);
                b = 1;
            }
            else
            {
                ackStack.Push(--a);
                ackStack.Push(++a);
                b--;
            }
        }
        return b;
    }

Thoughts? Side note, this is c#, but this behavior also occurs in C++ compiled with MinGW.

  • Before optimization (possibly it's just heap cleanup you're experiencing), make sure your code is correct. I haven't analyzed it but it looks wrong. – Cheers and hth. - Alf Oct 11 '15 at 08:00
  • I don't doubt there's probably something wrong with the code. I was just curious to see if someone else would entertain my hypothesis of heap cleanup. I'll step through pushes and pops and report on what I find. – SombraGuerrero Oct 11 '15 at 15:49
  • In your `Console.WriteLine` line, the `DateTime.Now` argument is evaluated before the `Ackermann(i, j)` call, so you're logging the start time of each Ackermann call, not the time at which the call finishes. So those 10 minutes are how long the last Ackermann call takes. Garbage collection and memory deallocation likely only take milliseconds here (you're not allocating many objects, just a few large buffers). – Pieter Witvoet Oct 11 '15 at 18:53
  • Ohh, I didn't even think about evaluation order. That's why you just need another set of eyes sometimes! Question about WriteLine then. Is everything evaluated in the order of the format string or is it by argument position? – SombraGuerrero Oct 11 '15 at 19:11
  • According to the C# language specification, function arguments are always evaluated from left to right. This happens before the function is actually called, so the format string can't influence this (it's just another argument itself). An argument that ends up not being used still has to be evaluated. Likewise, an argument that is used multiple times will only be evaluated once. – Pieter Witvoet Oct 11 '15 at 20:10
  • It's funny, I have done a fair amount of coding with formatted output functions, but I never even considered the true meaning behind the calls to such functions before now. I guess it demonstrates how easy it is to take a simple piece of code for granted. – SombraGuerrero Oct 12 '15 at 03:23
  • A formatting function is just like any other function - there are no special rules for them. C# doesn't use lazy evaluation (though you can create functions that accept other functions as arguments if you want to simulate lazy evaluation). – Pieter Witvoet Oct 13 '15 at 21:37

1 Answers1

0

Thank you very much to Pieter Witvoet! It turns out that the order of evaluation in my Writeline was all that was wrong. Only after changing it did it become apparent how much time that was adding to the overall runtime of the whole thing!