Related
this is my first post here, I'm new to C# and I have some problems with my code.
class Program
{
static void Main(string[] args)
{
#region FindAllNumbersDivisibleBy3
Console.Write("Enter a string of numbers: ");
string Nums = Console.ReadLine();
List<long> arr = new List<long>();
for (int i = 0; i < Nums.Length; i++)
{
for(int j = Nums.Length - 1; j >= i; j--)
{
try
{
string substring = Nums.Substring(i, j);
if (Convert.ToInt64(substring) % 3 == 0)
{
arr.Add(Convert.ToInt64(substring));
}
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}
}
Console.WriteLine("The following numbers are divisble by 3: ");
for (int i = 0; i < arr.Count; i++)
{
Console.WriteLine(arr[i]);
}
Console.ReadLine();
#endregion
}
}
The problem is the following: I'm given a series of numbers, probably too big and inefficient to be stored as an integer, so it's recommended to use a string, and you have to find every single number divisible by three. That could be the entire string, or some sub-strings, or just single digit numbers, etc. I get some conversion errors from the catch exception, as well as something else regarding some length parameter and I don't really understand what's the problem. It's also possible that the for loops' arguments have some errors too, but as far as I'm concerned the problems start in the try block.
Sorry if this is a very dumb question, I'm still in high school so I'm not very good at programming yet. Thank you for your help in advance.
This is still vulnerable to overflows, but it would take a very long string indeed to reach that point:
class Program
{
static void Main(string[] args)
{
Console.Write("Enter a string of numbers: ");
string Nums = Console.ReadLine();
Console.WriteLine("The following numbers are divisble by 3: ");
foreach(var result in DivisibleByThree(Nums))
{
Console.WriteLine(result);
}
Console.ReadKey(true);
}
public static IEnumerable<string> DivisibleByThree(string input)
{
for (int i = 0; i < input.Length; i++)
{
for(int j = input.Length; j > i; j--)
{
string segment = input.Substring(i, j-i);
if (SumOfDigits(segment) % 3 == 0)
{
yield return segment;
}
}
}
}
public static int SumOfDigits(string digits)
{
return digits.Where(c => char.IsDigit(c)).Select(c => c-'0').Sum();
}
}
See it work here:
https://dotnetfiddle.net/KacyAD
And since someone suggested recursion, I thought that'd be fun to try. I didn't quite get as far as I wanted (removing both loops and using recursion as the only repetition mechanism), but this does work:
public static IEnumerable<string> DivisibleByThree(string input)
{
if (input.Length > 1)
{
foreach(var item in DivisibleByThree(input.Substring(0, input.Length-1)))
{
yield return item;
}
}
while(input.Length > 0)
{
if ( SumOfDigits(input) % 3 == 0) yield return input;
input = input.Substring(1);
}
}
But that's the boring recursion. From a pure performance standpoint, it still spends a lot of time summing the same sequences of digits. There's a probably a way to use recursion to preserve prior work on each recursive call, and in that way make this run significantly faster.
That is, rather than start with a big string and check progressively smaller segments, start with the small string and with each check add the sum for the just the additional digit:
public static IEnumerable<string> DivisibleByThree(string input)
{
for(int i = input.Length - 1; i>=0; i--)
{
foreach(var item in DivisibleByThreeR(input.Substring(i, input.Length - i), 0, 0, 0)) yield return item;
}
}
public static IEnumerable<string> DivisibleByThreeR(string input, int startPos, int nextPos, int sum)
{
sum += input[nextPos] - '0';
if (sum % 3 == 0) yield return input.Substring(startPos, nextPos - startPos + 1);
if (++nextPos < input.Length)
{
foreach (var item in DivisibleByThreeR(input, startPos, nextPos, sum)) yield return item;
}
}
I'm not sure this is really any faster. I didn't benchmark or test at all beyond getting the right result. In fact, I suspect the iterators will eat up any improvements over the pure-loop version.
There's also probably a way to move the loop in the outer method also into the recursive function thereby optimize even further. But it was a nice exercise.
Here's my final fiddle if anyone else wants to play:
https://dotnetfiddle.net/dGFWNx
Here's a solution close to your code that uses BigInteger (you need .NET5+). This shall eliminate the risk of running into OverflowException. Please note that there can be duplicates in the output (you didn't say if you want to see them).
using System.Numerics;
class Program
{
static void Main(string[] args)
{
#region FindAllNumbersDivisibleBy3
Console.Write("Enter a string of numbers: ");
string Nums = Console.ReadLine();
List<BigInteger> results = new();
for (int i = 0; i < Nums.Length; i++)
{
for (int j = Nums.Length; j >= i; j--)
{
try
{
string substring = Nums.Substring(i, j - i);
if (BigInteger.TryParse(substring, out var bi) && BigInteger.ModPow(bi, 1, 3).IsZero)
{
results.Add(bi);
}
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}
}
Console.WriteLine("The following numbers are divisible by 3:");
for (int i = 0; i < results.Count; i++)
{
Console.WriteLine(results[i]);
}
Console.ReadLine();
#endregion
}
}
Something that will work even with numbers other than 3 and 9 would be to implement long division and only keep track of the carry remainder (added a Linq version since it seemed like an appropriate problem for it):
static bool IsDivisibleBy(string input, long divBy = 3)
{
long remainder = 0;
foreach (char c in input)
{
var num = Convert.ToInt64(c);
remainder = ((remainder * 10) + num) % divBy;
}
return remainder == 0;
}
static bool IsDivisibleByLinq(string input, long divBy = 3)
{
return input.Select(c => Convert.ToInt64(c))
.Aggregate(0L, (remainder, num) =>
((remainder * 10) + num) % divBy) == 0;
}
I found this example of a merge sort algorithm online on a tutorial webpage and I have been trying to understand ow the code implements the algorithm. The example i found uses recursion and a temporary array to sort the array of unsorted algorithms.
My query is in the final step of the process. When copying the elements of the temporary array into the original array to sort the array. why does the algorithm decrements the right attribute instead of incrementing the left attribute? when i incremented the left left value the algorithm does not work.
class Assignment1
{
static void Main(string[] args)
{
Console.WriteLine("Size of array:");
int n = Convert.ToInt16(Console.ReadLine());
int[] unsorted = new int[n];
for(int i = 0; i < n; i++)
{
Console.WriteLine("Enter a number:");
unsorted[i] = Convert.ToInt16(Console.ReadLine());
}
Console.WriteLine("--------Sort---------");
Recursion_Sort(unsorted, 0, n - 1);
for (int i = 0; i < n; i++)
{
Console.WriteLine(unsorted[i]);
}
}
static public void Merge(int[] numbers, int left, int mid, int right, int n)
{
int[] tempArray = new int[n];
int i, lEnd, size, pos;
lEnd = mid - 1;
pos = left;
size = (right - left + 1);
while ((left <= lEnd) && (mid <= right))
{
if (numbers[left] <= numbers[mid])
{
tempArray[pos] = numbers[left];
pos++;
left++;
}
else
{
tempArray[pos] = numbers[mid];
pos++;
mid++;
}
}
while (left <= lEnd)
{
tempArray[pos] = numbers[left];
pos++;
left++;
}
while (mid <= right)
{
tempArray[pos] = numbers[mid];
pos++;
mid++;
}
Console.WriteLine(tempArray.Length);
for (i = 0; i < size; i++)
{
numbers[right] = tempArray[right];
right--;
}
}
static public void Recursion_Sort(int[] numbers, int left, int right)
{
int mid;
if (right > left)
{
mid = (right + left) / 2;
Recursion_Sort(numbers, left, mid);
Recursion_Sort(numbers, (mid + 1), right);
// we then merge the sorted sub arrays using the merge method
Merge(numbers, left, (mid + 1), right, numbers.Length);
}
}
}
left value is changing during merge and as you have code block
while (left <= lEnd)
{
//...
left++;
}
left will be finally assigned to lEnd + 1(the condition for ending while loop).
Otherwise right is not changing and is the last index of currently manipulated sequence.
Taking the risk of not answering the question like you want it, I would suggest LINQ. This is not merge sort in particular, but rather a concatenation of two arrays and then sorting.
If your array isn't so big that performance matters, you might want to go for this approach, because it's simple and less code (which is always good).
int[] arr1 = new[] { 1, 2, 3, 7, 8, 10 };
int[] arr2 = new[] { 4, 6, 9, 12, 15 };
int[] merged = arr1.Concat(arr2).OrderBy(n => n).ToArray();
Furthermore, I post this if it is interesting for others.
I started learning algorithms and I am trying to implement Quicksort in C#.
This is my code:
class QuickSortDemo
{
public void Swap(ref int InputA, ref int InputB)
{
InputA = InputA + InputB;
InputB = InputA - InputB;
InputA = InputA - InputB;
}
public int Partition(int[] InputArray, int Low, int High)
{
int Pivot = InputArray[Low];
int LoopVariable1 = Low - 1;
int LoopVariable2 = High + 1;
while (true)
{
while (InputArray[--LoopVariable2] > Pivot) ;
while (InputArray[++LoopVariable1] < Pivot) ;
if (LoopVariable1 < LoopVariable2)
{
Swap(ref InputArray[LoopVariable1], ref InputArray[LoopVariable2]);
for (int LoopVariable = Low; LoopVariable <= High; LoopVariable++)
{
Console.Write(InputArray[LoopVariable] + " ");
}
Console.WriteLine();
}
else
{
for (int LoopVariable = Low; LoopVariable <= High; LoopVariable++)
{
Console.Write(InputArray[LoopVariable] + " ");
}
Console.WriteLine();
return LoopVariable2;
}
}
}
public void QuickSort(int[] InputArray,int Low, int High)
{
if (Low < High)
{
int Mid = Partition(InputArray, Low, High);
QuickSort(InputArray, Low, Mid);
QuickSort(InputArray, Mid + 1, High);
}
}
public static void Main()
{
int[] InputArray = { 10, 5, 6, 8, 23, 19, 12, 17 };
QuickSortDemo Demo = new QuickSortDemo();
for (int LoopVariable = 0; LoopVariable < InputArray.Length; LoopVariable++)
{
Console.Write(InputArray[LoopVariable]+" ");
}
Console.WriteLine();
Demo.QuickSort(InputArray, 0, InputArray.Length - 1);
for (int LoopVariable = 0; LoopVariable < InputArray.Length; LoopVariable++)
{
Console.Write(InputArray[LoopVariable] + " ");
}
Console.WriteLine();
}
}
For some reason I can't get this to work when I take the rightmost element in the array as pivot. I don't know what I am doing wrong. It would be really helpful if someone could explain me why this doesn't work when I take my rightmost element as the pivot. From what I learned, this should work for any input and any pivot element. Correct me if I am wrong.
Thank you.
I'm still not entirely sure I understand the question. But I was able to reproduce a problem (infinite recursion) when I change the line of code in the Partition() method from int pivot = inputArray[low]; to int pivot = inputArray[high];, and doing so seems consistent with your narrative:
I can't get this to work when I take the rightmost element in the array as pivot.
If I've understood the question correctly, then the basic problem is that when you change where you get the pivot, you also need to take this into account when returning the new mid-point. Currently, you return loopVariable2, which is correct when picking the pivot from the lower end of the array. But if you switch to picking the pivot from the upper end of the array, you need to return loopVariable2 - 1.
Another problem is that as you are scanning, you unconditionally increment or decrement the respective "loop variable", regardless of whether the current index is already at an element in the wrong partition. You need to check the current element position first, and only adjust the index if that element is in the correct partition.
Here is a correct version of the Partition() method where the pivot is selected using high instead of low:
public int Partition(int[] inputArray, int low, int high)
{
int pivot = inputArray[high];
int loopVariable1 = low;
int loopVariable2 = high;
while (true)
{
while (inputArray[loopVariable2] > pivot) loopVariable2--;
while (inputArray[loopVariable1] < pivot) loopVariable1++;
if (loopVariable1 < loopVariable2)
{
Swap(ref inputArray[loopVariable1], ref inputArray[loopVariable2]);
for (int loopVariable = low; loopVariable <= high; loopVariable++)
{
Console.Write(inputArray[loopVariable] + " ");
}
Console.WriteLine();
}
else
{
for (int loopVariable = low; loopVariable <= high; loopVariable++)
{
Console.Write(inputArray[loopVariable] + " ");
}
Console.WriteLine();
return loopVariable2 - 1;
}
}
}
In either case, note that the effect is to ensure that regardless of the pivot value selected, you always partition the array in such a way to ensure that a new pivot is always selected with each level of recursion, preventing the infinite loop.
By the way, and for what it's worth, I would not implement Swap() as you have. It's an interesting gimmick to do a no-temp-variable swap, but there is no practical benefit to doing so, while it does incur a significant code maintenance and comprehension cost. In addition, it will only work with integral numeric types; what if you want to extend your sort implementation to handle other types? E.g. ones that implement IComparable or where you allow the caller to provide an IComparer implementation?
IMHO a better Swap() method looks like this:
public void Swap<T>(ref T inputA, ref T inputB)
{
T temp = inputA;
inputA = inputB;
inputB = temp;
}
quick sort:
static public int Partition(int [] numbers, int left, int right)
{
int pivot = numbers[left];
while (true)
{
while (numbers[left] < pivot)
left++;
while (numbers[right] > pivot)
right--;
if (left < right)
{
int temp = numbers[right];
numbers[right] = numbers[left];
numbers[left] = temp;
}
else
{
return right;
}
}
}
static public void QuickSort_Recursive(int [] arr, int left, int right)
{
// For Recusrion
if(left < right)
{
int pivot = Partition(arr, left, right);
if(pivot > 1)
QuickSort_Recursive(arr, left, pivot - 1);
if(pivot + 1 < right)
QuickSort_Recursive(arr, pivot + 1, right);
}
}
This question is unlikely to help any future visitors; it is only relevant to a small geographic area, a specific moment in time, or an extraordinarily narrow situation that is not generally applicable to the worldwide audience of the internet. For help making this question more broadly applicable, visit the help center.
Closed 9 years ago.
I am applying the QuickSort algorithm from book introduction to Algorithm i wrote the code,but the output is not sorted correctly
Following is the algorithm
Quicksort(A, p, r)
{
if (p < r)
{
q = Partition(A, p, r)
Quicksort(A, p, q-1)
Quicksort(A, q+1, r)
}
}
Partition(A, p, r)
{
pivot = A[r]
i = p - 1
for j = p to r – 1
{
do if A[j] <= pivot
then
{
i = i + 1
exchange A[i] A[j]
}
}
exchange A[i+1] A[r]
return i+1
}
and here is my code
class Threads<T>
{
static bool IsLessThan(T x, T y)
{
if (((IComparable)(x)).CompareTo(y)<=0)
{
return true;
}
else {
return false;
}
}
public int Partition(T[] myarray, int low, int high)
{
T x = myarray[high];
T y;
int i = low - 1;
int j;
for (j = low; j < high - 1; j++)
{
//**************Added Text after edit,I forgot to put this
if (IsLessThan(myarray[j], x))
{
i++;
y = myarray[i];
myarray[i] = myarray[j];
myarray[j] = y;
}
}
y = myarray[i+1];
myarray[i+1] = myarray[high];
myarray[high] = y;
return i + 1;
}
public void QuickSort(T[] myarray, int low, int high)
{
if (low < high)
{
// int q = Partition(myarray,highh, low);
int q = Partition(myarray,low, high);
QuickSort(myarray, low, q - 1);
QuickSort(myarray, q + 1, high );
}
}
}
Following code shows, how the quicksort method is used
private void button1_Click(object sender, EventArgs e)
{
int[] myarray ={9,8,7,6,5,4,3,2};
textBox1.Text = "";
Threads<int> t1 = new Threads<int>();
t1.QuickSort(myarray, 0, myarray.Length-1);
for(int i=0;i<myarray.Length;i++)
textBox1.Text=textBox1.Text+" , "+myarray[i];
}
I get the Following output when i execute the program
8 , 7 , 6 , 5 , 4 , 3 , 9 , 2
Answer
value of i must be one less than j and low at start in the Partition function
I forgot to Swap myarray[i+1] and myarray[high] outside the loops in Partition Function.
Now the code is working accurately fine for strings ,int ,char etc
Try this:
public int Partition(T []myarray, int low, int high)
{
T x = myarray[high];
int i=low;
int j=high;
while(i< j)
{
while(i<j&& IsLessThan(myarray[i], x))
i++;
myarray[j]=myarray[i];
while(i<j&& IsLessThan(x,myarray[j]))
j--;
myarray[i]=myarray[j];
}
myarray[i] = myarray[high];
return i ;
}
Try some changes and debug your code
I tried following
private static int Partition(int[] input, int left, int right)
{
int pivot = input[right];
int temp;
int i = left;
for (int j = left; j < right; j++)
{
if (input[j] <= pivot)
{
temp = input[j];
input[j] = input[i];
input[i] = temp;
i++;
}
}
input[right] = input[i];
input[i] = pivot;
return i;
}
if Pivot element is middle element
private static List<int> QuickSort(List<int> a, int left, int right)
{
int i = left;
int j = right;
double pivotValue = ((left + right) / 2);
int x = a[Convert.ToInt32(pivotValue)];
int w = 0;
while (i <= j)
{
while (a[i] < x)
{
i++;
}
while (x < a[j])
{
j--;
}
if (i <= j)
{
w = a[i];
a[i++] = a[j];
a[j--] = w;
}
}
if (left < j)
{
QuickSort(a, left, j);
}
if (i < right)
{
QuickSort(a, i, right);
}
return a;
}
First this part looked plain wrong, but even after changing the sort doesn't work
int q = Partition(myarray, high, low);
Low and high should be changed
int q = Partition(myarray, low, high);
I think this question is less about an algorithm, and more about the methods of debugging one. So I used a Quicksort algorithm I found online and tried to implement it in C#. I had problems of my own, but debugged it fairly quickly. The code below has some useful techniques to help with debugging. (The algorithm came from http://rosettacode.org/wiki/Sorting_algorithms/Quicksort.)
Here are my general comments when comparing your code:
It was confusing having an array of ints to test with when dealing with integer indices as well, so I changed my array to strings.
I used a lot of Console.WriteLine so I could trace the steps the algorithm was taking. That's what quickly helped me find that low/high issue.
I didn't not use a variable named i as anything but a for loop index. That's just confusing as all heck to use it as anything else -- don't do that!
Since every QuickSort algortithm had multiple swaps I created a Swap function. Even if you only do it once in your code, it clutters up the purpose of the Partition method to have such a trival pattern in there. To put this another way, why did it make sense for you to make IsLessThan a method but not Swap?
Speaking about IsLessThan, technically you actually wrote a method for IsLessThanOrEqual. Don't name things incorrectly, because if somebody uses that and assumes it is just for Less Then, they'll have a hard time when they have unpredictable results. IsLSE would be understandable by most.
Your for loop is the only place you use the variable j so why declare it outside of the for statement? Just adds an extra line and variable in a scope you don't need it.
Here's my QuickSort. It appears to work:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
string[] myarray = { "g", "b", "e", "f", "a", "d", "c"};
QuickSort<string> t1 = new QuickSort<string>();
t1.Sort(myarray, 0, myarray.Length - 1);
}
}
public class QuickSort<T>
{
static int Compare (T x, T y)
{
return ((IComparable)(x)).CompareTo(y);
}
private void Swap(T[] myarray, int i, int j)
{
// swapping indices just for writeline purposes
if (i > j)
{
int t = i;
i = j;
j = t;
}
Console.WriteLine("Swap: {0}:{1} with {2}:{3}",
i, myarray[i], j, myarray[j]);
T temp = myarray[i];
myarray[i] = myarray[j];
myarray[j] = temp;
Console.WriteLine("Result: {0}", String.Join(",", myarray));
}
private int Partition(T[] myarray, int low, int high, int pivotIndex)
{
T pivotVal = myarray[pivotIndex];
Swap(myarray, pivotIndex, high);
int currentLow = low;
while (low <= high) {
while (Compare(myarray[low], pivotVal) < 0) {
low++;
}
while (Compare(myarray[high], pivotVal) > 0) {
high--;
}
if (low <= high) {
Swap(myarray, low, high);
low++;
high--;
}
}
return low;
}
public void Sort(T[] myarray, int low, int high)
{
if (low < high)
{
Console.WriteLine(("Start: {0}", String.Join(",", myarray));
int pivotIndex = (low + high) / 2;
Console.WriteLine("QuickSort: P: {0}, L: {1}, H: {2}",
pivotIndex, low, high);
pivotIndex = Partition(myarray, low, high, pivotIndex);
Sort(myarray, low, pivotIndex - 1);
Sort(myarray, pivotIndex + 1, high);
}
}
}
Is there a way to keep my Ackerman function from creating a stack over flow is does it for relatively small numbers , i.e. (4,2). This is the error
{Cannot evaluate expression because the current thread is in a stack
overflow state.}
private void Button1Click(object sender, EventArgs e)
{
var t = Ackermann(4,2);
label1.Text += string.Format(": {0}", t);
label1.Visible = true;
}
int Ackermann(uint m, uint n)
{
if (m == 0)
return (int) (n+1);
if (m > 0 && n == 0)
return Ackermann(m - 1, 1);
if (m > 0 && n > 0)
return Ackermann(m - 1, (uint)Ackermann(m, n - 1));
else
{
return -1;
}
}
The best way to avoid StackOverflowException is to not use the stack.
Let's get rid of the negative case, as it's meaningless when we call with uint. Alternatively, what follows here will also work if we make the negative test the very first thing in the method, before the other possibilities are considered:
First, we're going to need a bigger boat:
public static BigInteger Ackermann(BigInteger m, BigInteger n)
{
if (m == 0)
return n+1;
if (n == 0)
return Ackermann(m - 1, 1);
else
return Ackermann(m - 1, Ackermann(m, n - 1));
}
Now success is at least mathematically possible. Now, the n == 0 case is a simple enough tail-call. Let's eliminate that by hand. We'll use goto because it's temporary so we don't have to worry about velociraptors or Dijkstra:
public static BigInteger Ackermann(BigInteger m, BigInteger n)
{
restart:
if (m == 0)
return n+1;
if (n == 0)
{
m--;
n = 1;
goto restart;
}
else
return Ackermann(m - 1, Ackermann(m, n - 1));
}
This will already take a bit longer to blow the stack, but blow it, it will. Looking at this form though, note that m is never set by the return of a recursive call, while n sometimes is.
Extending this, we can turn this into an iterative form, while only having to deal with tracking previous values of m, and where we would return in the recursive form, we assign to n in our iterative form. Once we run out of ms waiting to be dealt with, we return the current value of n:
public static BigInteger Ackermann(BigInteger m, BigInteger n)
{
Stack<BigInteger> stack = new Stack<BigInteger>();
stack.Push(m);
while(stack.Count != 0)
{
m = stack.Pop();
if(m == 0)
n = n + 1;
else if(n == 0)
{
stack.Push(m - 1);
n = 1;
}
else
{
stack.Push(m - 1);
stack.Push(m);
--n;
}
}
return n;
}
At this point, we have answered the OP's question. This will take a long time to run, but it will return with the values tried (m = 4, n = 2). It will never throw a StackOverflowException, though it will end up running out of memory above certain values of m and n.
As a further optimisation, we can skip adding a value to the stack, only to pop it immediately after:
public static BigInteger Ackermann(BigInteger m, BigInteger n)
{
Stack<BigInteger> stack = new Stack<BigInteger>();
stack.Push(m);
while(stack.Count != 0)
{
m = stack.Pop();
skipStack:
if(m == 0)
n = n + 1;
else if(n == 0)
{
--m;
n = 1;
goto skipStack;
}
else
{
stack.Push(m - 1);
--n;
goto skipStack;
}
}
return n;
}
This doesn't help us with stack nor meaningfully with heap, but given the number of loops this thing will do with large values, every bit we can shave off is worth it.
Eliminating goto while keeping that optimisation is left as an exercise for the reader :)
Incidentally, I got too impatient in testing this, so I did a cheating form that uses known properties of the Ackerman function when m is less than 3:
public static BigInteger Ackermann(BigInteger m, BigInteger n)
{
Stack<BigInteger> stack = new Stack<BigInteger>();
stack.Push(m);
while(stack.Count != 0)
{
m = stack.Pop();
skipStack:
if(m == 0)
n = n + 1;
else if(m == 1)
n = n + 2;
else if(m == 2)
n = n * 2 + 3;
else if(n == 0)
{
--m;
n = 1;
goto skipStack;
}
else
{
stack.Push(m - 1);
--n;
goto skipStack;
}
}
return n;
}
With this version, I can get a result of true for Ackermann(4, 2) == BigInteger.Pow(2, 65536) - 3 after a little over a second (Mono, Release build, running on a Core i7). Given that the non-cheating version is consistent in returning the correct result for such values of m, I take this as reasonable evidence of the correctness of the previous version, but I'll leave it running and see.
Edit: Of course, I'm not really expecting the previous version to return in any sensible timeframe, but I thought I'd leave it running anyway and see how its memory use went. After 6 hours it's sitting nicely under 40MiB. I'm pretty happy that while clearly impractical, it would indeed return if given enough time on a real machine.
Edit: Apparently it's being argued that Stack<T> hitting its internal limit of 2³¹ items counts as a sort of "stack overflow", too. We can deal with that also if we must:
public class OverflowlessStack <T>
{
internal sealed class SinglyLinkedNode
{
//Larger the better, but we want to be low enough
//to demonstrate the case where we overflow a node
//and hence create another.
private const int ArraySize = 2048;
T [] _array;
int _size;
public SinglyLinkedNode Next;
public SinglyLinkedNode()
{
_array = new T[ArraySize];
}
public bool IsEmpty{ get{return _size == 0;} }
public SinglyLinkedNode Push(T item)
{
if(_size == ArraySize - 1)
{
SinglyLinkedNode n = new SinglyLinkedNode();
n.Next = this;
n.Push(item);
return n;
}
_array [_size++] = item;
return this;
}
public T Pop()
{
return _array[--_size];
}
}
private SinglyLinkedNode _head = new SinglyLinkedNode();
public T Pop ()
{
T ret = _head.Pop();
if(_head.IsEmpty && _head.Next != null)
_head = _head.Next;
return ret;
}
public void Push (T item)
{
_head = _head.Push(item);
}
public bool IsEmpty
{
get { return _head.Next == null && _head.IsEmpty; }
}
}
public static BigInteger Ackermann(BigInteger m, BigInteger n)
{
var stack = new OverflowlessStack<BigInteger>();
stack.Push(m);
while(!stack.IsEmpty)
{
m = stack.Pop();
skipStack:
if(m == 0)
n = n + 1;
else if(m == 1)
n = n + 2;
else if(m == 2)
n = n * 2 + 3;
else if(n == 0)
{
--m;
n = 1;
goto skipStack;
}
else
{
stack.Push(m - 1);
--n;
goto skipStack;
}
}
return n;
}
Again, calling Ackermann(4, 2) returns:
Which is the correct result. The stack structure used will never throw, so the only limit remaining is the heap (and time of course, with large enough inputs you'll have to use "universe lifetime" as a unit of measurement...).
Since the way it's used is analogous to the tape of a Turing machine, we're reminded of the thesis that any calculable function can be calculated on a Turing machine of sufficient size.
Use memoization. Something like:
private static Dictionary<int, int> a = new Dictionary<int, int>();
private static int Pack(int m, int n) {
return m * 1000 + n;
}
private static int Ackermann(int m, int n) {
int x;
if (!a.TryGetValue(Pack(m, n), out x)) {
if (m == 0) {
x = n + 1;
} else if (m > 0 && n == 0) {
x = Ackermann(m - 1, 1);
} else if (m > 0 && n > 0) {
x = Ackermann(m - 1, Ackermann(m, n - 1));
} else {
x = -1;
}
a[Pack(m, n)] = x;
}
return x;
}
However, this example only shows the concept, it will still not give the correct result for Ackermann(4, 2), as an int is way too small to hold the result. You would need an integer with 65536 bits instead of 32 for that.