Click here to Skip to main content
15,898,134 members
Articles / Programming Languages / C#
Tip/Trick

Recursive function vs. Loop

Rate me:
Please Sign up or sign in to vote.
4.72/5 (16 votes)
22 Jun 2015CPOL2 min read 54.2K   82   10   18
A simple insight into the difference between recursive functions and loops

 

Introduction

Recursive function is a function that calls itself, for me it is a fascinating way to do relatively complex task with few lines of code; for example the factorial can be calculated using this recursive function (for simplicity, I didn't check for negative input).

 

C#
static private double FactorialRecursive(int n)
        {
            if (n == 1)
            {
                return 1;
            }
            return n * FactorialRecursive(n - 1);
        }

 

Here, we added a condition if n == 1 to stop the loop while any number (n) bigger than 1 will call the function for (n-1); which means if n = 5, the result will be 5*4*3*2*1.

 

On the other hand, a loop can be used to do exactly the same as following.

C#
static private double FactorialLoop(int n)
        {
            double result = 1;
            while (n > 1)
            {
                result *= n--;
            }
            return result;
        }

 

The attached code is a simple comparison (computation time) between the two methods which shows that the loop is faster by around 40%; but who cares when the difference is a tiny fraction of the seconds. However, sometimes recursive function fails miserably as shown in the following sections.

Background

I worked recently with a geometry project related to processing lines. Our start point was a database that has two tables; the first table stores line segments while the second tables stores the intersections between these segments as shown in the following picture.

Image 1

Figure 1: a very simplified version of the database

Using the code

Here I am trying to group these segments into different lines based on the intersection table. Simply I will select one segment and add it to a line, then I will grab all touching segments and add them; repeat these steps till I exhaust the line series. Afterwards, I will select the next unprocessed line segments to form the second line so forth.

This can be accomplished using recursive function as following

C#
private static void GroupLineRecursive()
{
    try
    {
        Console.WriteLine("Constructing line using recursive function...");
        using (var db = new SegmentsEntities())
        {
            int lineNumber = 1001;
            foreach (var segment in db.tblSegments)
            {
                if (segment.LineName == null)
                {
                    RecursiveCall(segment, lineNumber);
                    lineNumber++;
                }
            }
            Console.WriteLine("Displaying results for recursive function");
            //NOTE: we have to use local because we didn't save changes to DB
            var result = db.tblSegments.Local.GroupBy(a => a.LineName); //Method syntax

            //Query syntax
            //var result = from segment in db.tblSegments.Local
            //             group segment by segment.LineName into newGroup
            //             select newGroup;
            foreach (var group in result)
            {
                Console.WriteLine("Line #{0}", group.Key);
                foreach (var item in group)
                {
                    Console.WriteLine("\tSegment #{0}", item.SegmentName);
                }
            }
        }
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
    }
}

private static void RecursiveCall(tblSegment segment, int lineNumber)
{
    Console.WriteLine("Processing item #{0}", segment.Id);
    segment.LineName = lineNumber.ToString();
    var unprocessed = from b in segment.tblIntersections
                      where b.tblSegment1.LineName == null
                      select b.tblSegment1;
    foreach (var item in unprocessed)
    {
        RecursiveCall(item, lineNumber);
    }
}

Although the aforementioned code will yield the right results; it will throw StackOverflowException if you have relatively long line (say around 30,000 line segments). The exception is thrown because C#  (beside C++, python) does not optimize tail-recursion (when the last line is the self-call).

In order to overcome this problem, we should use loop function to accomplish the same results as following.

Note: This code has been adopted from Eric Lippert's Blog

C#
private static void GroupLineLoop()
        {
            try
            {
                Console.WriteLine("Constructing line using Loop function...");
                using (var db = new SegmentsEntities())
                {
                    int lineNumber = 1001;
                    foreach (var segment in db.tblSegments)
                    {
                        if (segment.LineName == null)
                        {
                            foreach (var item in LoopCall(segment))
                            {
                                item.LineName = lineNumber.ToString();
                            }
                            lineNumber++;
                        }
                    }
                    Console.WriteLine("Displaying results for loop function");
                    //NOTE: we have to use local because we didn't save changes to DB
                    var result = db.tblSegments.Local.GroupBy(a => a.LineName); //Method syntax

                    //Query syntax
                    //var result = from segment in db.tblSegments.Local
                    //             group segment by segment.LineName into newGroup
                    //             select newGroup;
                    foreach (var group in result)
                    {
                        Console.WriteLine("Line #{0}", group.Key);
                        foreach (var item in group)
                        {
                            Console.WriteLine("\tSegment #{0}", item.SegmentName);
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }

        private static IEnumerable<tblSegment> LoopCall(tblSegment item)
        {
            var seen = new HashSet<tblSegment>();
            var stack = new Stack<tblSegment>();
            seen.Add(item);
            stack.Push(item);
            yield return item;
            while (stack.Count > 0)
            {
                tblSegment current = stack.Pop();
                Console.WriteLine("Processing item #{0}", current.Id);
                var unprocessed = from b in current.tblIntersections
                                  where b.tblSegment1.LineName == null
                                  select b.tblSegment1;
                foreach (tblSegment newItem in unprocessed)
                {
                    if (!seen.Contains(newItem))
                    {
                        seen.Add(newItem);
                        stack.Push(newItem);
                        yield return newItem;
                    }
                }
            }
        }

Here Stack has been used to collect all segments before assign them to a line; I tested this code with 50,000+ segments and it works flawlessly. 

Conclusion

Although recursive function might be appealing, try as much as you can to avoid it. Loops can replace recursive function in all cases but it is faster and more robust.

History

  • June 20th, 2015: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Engineer
Canada Canada
Programming for me is a hobby

Comments and Discussions

 
AnswerDifference Pin
prashantglobe12-May-19 21:22
prashantglobe12-May-19 21:22 
GeneralMy vote of 5 Pin
David A. Gray28-Jun-15 15:21
David A. Gray28-Jun-15 15:21 
GeneralRe: My vote of 5 Pin
Mostafa A. Ali1-Jul-15 15:08
professionalMostafa A. Ali1-Jul-15 15:08 
GeneralRe: My vote of 5 Pin
David A. Gray2-Jul-15 9:33
David A. Gray2-Jul-15 9:33 
That is one way to view it. The truth is that I have been on the wrong end of a few too many stack overflow abends that were directly attributable to a runaway recursive function.

IMO, anybody who insists on writing a recursive function is morally and ethincally bound to do everything in his power to make sure that it converges and starts unrulling well before it exhausts the call stack. That default stack reserve of one megabyte set by your linker sounds big until you start adding up the many things that go into it.

For instance, calling a simple function that has no arguments consumes a minimum of eight bytes in a 32 bit program, and twice that much in a 64 bit program, and usually it's a much bigger hit than that. Multiply that by the typical depth of your call stack, all the way up to int main (int argc, char * argv [ ] ) (which needs an extra 8 or 16 bytes for the two arguments, and is several spots below the top of the call stack), and all the automatic variables that get defined along the way, and you can see that a typical program consumes a good bit of stack before it gets to the top of the recursion stack. The reason that I say that the stack consumption of a basic call is a minimum of 8 bytes (16 for a 64 bit program) is that the startup code of most functions (anything except __fastcall) stashes a minimum of 3 registers, and sometimes as many as 5, at 4 bytes each (8 bytes each for 64 bit).

Then, your recursive routine goes on a stack eating binge. Every iteration of a 32 bit __cdecl OR __stdcall function call consumes at least 20 bytes of stack space, as follows.

4 bytes for the return address
4 bytes for a copy of the base pointer (EBP)
4 bytes for the EBX register
4 bytes for the ESI register
4 bytes for the EDI register

If the recursive function defines local variables, they come out of the stack reserve, too.

Let's say that you have 900,000 bytes of stack reserve when your code enters the recursion loop. Assuming that the function needs the minimum of 20 bytes per iteration, your program dies after 45,000 iterations. That sounds like a lot, but I've seen it happen plenty of times, splattering my code all over the place, and giving me a bad day.
GeneralRe: My vote of 5 Pin
Mostafa A. Ali2-Jul-15 10:40
professionalMostafa A. Ali2-Jul-15 10:40 
GeneralRe: My vote of 5 Pin
David A. Gray2-Jul-15 12:41
David A. Gray2-Jul-15 12:41 
AnswerRe: My vote of 5 Pin
Liju Sankar17-Jul-15 6:05
professionalLiju Sankar17-Jul-15 6:05 
GeneralRe: My vote of 5 Pin
Mostafa A. Ali17-Jul-15 16:46
professionalMostafa A. Ali17-Jul-15 16:46 
GeneralMy vote of 5 Pin
Saravanan21124-Jun-15 18:07
professionalSaravanan21124-Jun-15 18:07 
GeneralRe: My vote of 5 Pin
Mostafa A. Ali24-Jun-15 19:48
professionalMostafa A. Ali24-Jun-15 19:48 
QuestionReasons for loop is better Pin
asdfyfsdg24-Jun-15 14:59
professionalasdfyfsdg24-Jun-15 14:59 
AnswerRe: Reasons for loop is better Pin
Mostafa A. Ali24-Jun-15 17:25
professionalMostafa A. Ali24-Jun-15 17:25 
QuestionMy vote of "needs improvement" Pin
DavidJDriver24-Jun-15 13:38
DavidJDriver24-Jun-15 13:38 
AnswerRe: My vote of "needs improvement" Pin
Mostafa A. Ali24-Jun-15 17:16
professionalMostafa A. Ali24-Jun-15 17:16 
GeneralMy vote of 5 Pin
EiadXP23-Jun-15 2:08
professionalEiadXP23-Jun-15 2:08 
GeneralRe: My vote of 5 Pin
Mostafa A. Ali23-Jun-15 8:15
professionalMostafa A. Ali23-Jun-15 8:15 
GeneralMy vote of 5 Pin
Member 1142813722-Jun-15 23:25
Member 1142813722-Jun-15 23:25 
GeneralRe: My vote of 5 Pin
Mostafa A. Ali23-Jun-15 8:15
professionalMostafa A. Ali23-Jun-15 8:15 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.