Search code examples
c#recursiondigit

Recursive function to reverse the digit of an integer number


My goal is to write a recursive program that reverse the digits of an integer number. When I test the first element, the code works. However, it doesn't work well for the other two cases, e.g. it for reverse(456) it prints 321654 and for reverse(731) it prints 321654137. I think the issue is with the public static variable reverted.

Can someone please help me to understand the problem and fix it?

using System;  
public class Program
{
    public static void Main(string[] args)
    {
        reverse(123);
        reverse(456);
        reverse(731);
    }

    public static int reverted=0;

    public static void reverse(int number)
    {
        if (number!=0)
        {
            int remainder = number % 10;
            reverted = (reverted*10)+remainder;
            reverse(number/10);
        } 
        else
            Console.WriteLine(reverted);
    }
}

Solution

  • My goal is to write a recursive program that reverse the digits of an integer number.

    Since that is a goal that no one in industry would likely wish to attain, odds are good this is for a class. In particular, no sensible person would solve this problem, even if they had it, with recursion. So this is a lousy problem to teach recursion, because the non-recursive solution is so straightforward.

    Odds are also good that you do not understand recursion very well. This is in some sense a great exercise because it requires that you use a special technique, called an accumulator, to achieve a recursive solution.

    Moreover, your solution, even if you fixed the bug, does not produce the required number. It prints it, and that is completely different. We wish to be able to produce the result, not merely display it.

    (This then brings up the interesting question of how to deal with numbers that fit into an int but whose reverses do not, like 1000000009. Let's ignore those issues for the time being.)

    So let's start with the basics. Every recursive method has the same structure:

    • Are we in the base case? If yes, return the result.
    • We're not in the base case.
    • Break the problem up into smaller problems
    • Solve each problem recursively
    • Combine the solutions to solve the larger problem
    • Return the result.

    Let's naively apply this pattern to your problem and see what happens.

    The base case is easy:

    • the reverse of any non-negative number from 0 to 9 is itself.

    What's the recursive case? Suppose we have 157

    • Divide the number by ten to get a smaller number, 15
    • Reverse the smaller number -- 51
    • Append the low order digit of the original number as the high order digit of the reversed smaller number, 751 --- wait, how do we do that exactly?

    It's not clear how to perform that last operation.

    We need to be smarter.

    Here's what you do. (I've left out the error checking that rejects negative numbers.)

    static int ReverseHelper(int number, int accumulator) =>
      number == 0 ? 
        accumulator : 
        ReverseHelper(number / 10, accumulator * 10 + number % 10);
    
    public static int Reverse(int number) =>
      ReverseHelper(number, 0);
    

    Let's look at some cases.

    Suppose number is 0. Then ReverseHelper returns 0, good. Zero works.

    Suppose number is 3. Then we call ReverseHelper(3, 0) which calls ReverseHelper(0, 3), which returns 3. One-digit numbers work.

    Suppose number is 35. We call ReverseHelper(35, 0), which calls ReverseHelper(3, 5), which calls ReverseHelper(0, 53), which returns 53. Two digit numbers work.

    And so on.

    Exercise: There is a straightforward way to deal with the problem of reversing an int where the reversal does not fit into an int; what is it?

    You see I hope why this technique is called using an accumulator. The desired result is accumulated gradually as the recursion proceeds, and then returned when the recursion reaches its base case.

    Now, notice how this technique relates to your original program. You are using a static field as your accumulator. Never do that! Ever! Recursive methods should not depend for their correctness on external state. Instead, pass the value that you will be consuming recursively down into the recursive call to a helper method.

    Study this technique carefully. This is a powerful technique for writing recursive programs. In functional languages, such an operation is called fold or reduce; look to those for further inspiration. In C# it is called Aggregate and is used to produce a summary result on a sequence.

    The recursive pattern using accumulators is:

    • Are we in the base case? If yes, return the result from the accumulator.
    • We're not in the base case.
    • Break the problem up into smaller problems
    • Solve each problem recursively, by accumulating information about the solution into the accumulator on the recursive calls.
    • Combine the solutions to solve the larger problem
    • Return the result.

    Why is this technique so valuable? In C# this is less important, but in the tail recursive functional languages the compiler can often optimize recursive methods that use accumulators into more efficient code than non-tail-recursive methods, so that's something else to research.

    It is valuable in C# because tail recursive methods that use accumulators can easily be turned into non-recursive methods by a simple process of program transformation.

    Let's see how. We have:

    static int ReverseHelper(int number, int accumulator) =>
      number == 0 ? 
        accumulator : 
        ReverseHelper(number / 10, accumulator * 10 + number % 10);
    

    Let's rewrite that as a statement:

    static int ReverseHelper(int number, int accumulator)
    {
      if (number == 0)
        return accumulator;
      else
        return ReverseHelper(number / 10, accumulator * 10 + number % 10);
      }
    }
    

    Now let's make explanatory variables for all the subexpressions:

    static int ReverseHelper(int number, int accumulator)
    {
      if (number == 0)
        return accumulator;
      else 
      {
        int newNumber = number / 10;
        int newAccumulator = accumulator * 10 + number % 10;
        return ReverseHelper(newNumber, newAccumulator);
      }
    }
    

    So far so good. But now we notice that we can turn the whole thing into a loop!

    static int ReverseHelper(int number, int accumulator)
    {
      while (true) 
      {
        if (number == 0)
          return accumulator;
        else 
        {
          int newNumber = number / 10;
          int newAccumulator = accumulator * 10 + number % 10;
          number = newNumber;
          accumulator = newAccumulator;
          // And now the loop starts over!
        }
      }
    }
    

    And of course we see that this now has the form of the straightforward non-recursive solution to the problem.

    This should give you a powerful insight into the nature of recursive programs:

    • Every recursive program can be translated into a tail-recursive program, though it is not always easy to see how.
    • Every tail-recursive program can be translated into a non-recursive loop.
    • The converse is also true! Every program with a loop can be translated into a tail-recursive program!

    Again, study these simple techniques carefully. Understanding the relationship between recursive and non-recursive programs is a key tool in CS theory.