1

Does someone know of an algorithm to make a simple recursion to a tail one? More specificaly, how would you apply the algorithm to the following code?

namespace Testing
{
    class Program
    {
        static void Main(string[] args)
        {
           Console.WriteLine(match("?**", "aaa"));
           Console.WriteLine(match("*#*?", "aa1$a1a1"));
           Console.WriteLine(match("*#*", "aa11"));
           Console.WriteLine(match("??*", "0110"));
           Console.WriteLine(match("", "abc"));
           Console.WriteLine(match("???", ""));
           Console.ReadLine();

        }


        public static bool match(string p, string s)
        {
            if (p.Length == 0)
                return true;
            if (p.Length > s.Length)
                return false;

            bool firstLetterMatches = false;
            char nextCharInStr = s[0];
            switch (p[0])
            {
                case '*':
                    firstLetterMatches = 'a'<= nextCharInStr && nextCharInStr <= 'z';
                    break;

                case '#':
                    firstLetterMatches = '0'<= nextCharInStr && nextCharInStr <= '9';
                    break;

                case '?':
                    firstLetterMatches = ('a'<= nextCharInStr && nextCharInStr <= 'z') ||
                                         ('0'<= nextCharInStr && nextCharInStr <= '9');
                    break;

                default:
                    return false;
            }

            return match(p,s.Substring(1)) ||  
                (firstLetterMatches && match(p.Substring(1),s.Substring(1)));
        }
    }


}

Thanks!

leppie
  • 115,091
  • 17
  • 196
  • 297
Elad Benda
  • 35,076
  • 87
  • 265
  • 471
  • 1
    Since the MS C# compiler doesn't *emit* tail-calls, and *even if it did* the CLI isn't *forced* to honour them, you would be looking at a more manual approach if you wanted to avoid using the stack. Dare I mention `goto`? – Marc Gravell Aug 22 '11 at 21:05
  • @Marc - I think a manual stack might be a better option. Another thing they'll want to avoid is the repeated copying of the string. – ChaosPandion Aug 22 '11 at 21:07
  • @ChaosPandion oh sure you'd use an `charIndex` variable that you increment. A manual stack is useful when the data quantity is unpredictable - not sure you need that here. – Marc Gravell Aug 22 '11 at 21:09
  • 1
    I don't see why you guys assume that the guy wants a C#-specific answer (with regard to performance) just because he wrote the question in C# -- I'd assume he is just using C# as an example to ask about the general problem of reorganizing functions to be tail-recursive. – mqp Aug 22 '11 at 21:18
  • @Marc Gravell: No `goto` needed, just a `while(true)` loop :) See my answer. – leppie Aug 22 '11 at 21:22
  • 1
    @mquander tagged C# with examples in C#? I think we can assume C#... – Marc Gravell Aug 22 '11 at 22:01
  • @Marc: I guess so. My interpretation is mostly based on the supposition that this example looks sort of like it's probably a general classroom introduction to the idea of tail recursion. Either way, I guess we answered his question. – mqp Aug 22 '11 at 22:04

4 Answers4

6

I'm assuming that you're asking because you actually have a real-world problem with blowing the stack. It looks like you're doing a string manipulation here recursing on one-smaller substrings. This is potentially extremely inefficient and dangerous. Strings can easily be so long that the recursive algorithm blows the stack, and because strings are immutable but not persistent, you're creating a new string every time you call substring; that's going to create minimum O(n^2) bytes of strings that have to be copied around.

(Also, it looks like you're doing some sort of longest-matching-subsequence pattern; as mquander points out, it is likely that some sort of memoization strategy will help with the time complexity; it often does with this sort of problem.)

To solve the string allocation problem you can pass around instead of a string, a string and the index that is to be treated as the beginning of the string. Now you're merely incrementing an integer, rather than allocating and copying a string.

In order to solve your recursion problem, there are a number of techniques you can use. I wrote a series of articles about various ways to turn simple tree-recursive algorithms into algorithms that consume heap instead of call stack space. Some of them are in JScript, but the ideas are easily translatable to C#.

Finally, in C# 5 we will be introducing an "await" keyword which causes the compiler to do a continuation passing style transformation on the program. The intention of this keyword is to make asynchronous programming easier, but a side effect of it is that it makes stackless programming much easier too. If you're interested, download the Community Technology Preview that we've released, and you can automatically transform your program into one that consumes no stack.

OK, so, the articles on turning recursive algorithms into algorithms that consume heap, not stack, start here:

http://blogs.msdn.com/b/ericlippert/archive/2005/07/27/recursion-part-one-recursive-data-structures-and-functions.aspx

All my articles on continuation passing style are here: (start from the bottom)

http://blogs.msdn.com/b/ericlippert/archive/tags/continuation+passing+style/

And the ones on the asynchrony are here: (again, start from the bottom)

http://blogs.msdn.com/b/ericlippert/archive/tags/async/

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • 1
    Wow, it did not occur to me that `await` and `async` could be used outside the context of asynchrony to do this. Very cool. – mqp Aug 22 '11 at 22:25
  • @mquander: yep. "await" is just "call/cc" with a funny hat on. See http://stackoverflow.com/questions/4070237/how-could-the-new-async-feature-in-c-5-0-be-implemented-with-call-cc/4071015#4071015 for details. – Eric Lippert Aug 22 '11 at 22:42
  • Iterator blocks are another way to do the same thing - and are often quite easy to implement. I suppose you can think of them as weak coroutines ... which are a form of CPS. – LBushkin Aug 24 '11 at 20:57
  • @LBushkin: Indeed. We tried for a long time to come up with a unifying abstraction that would make iterator blocks and async blocks "the same thing" but couldn't make it look nice. – Eric Lippert Aug 24 '11 at 22:18
1

Sort of. You can make any recursive algorithm tail-recursive, awkwardly, by converting it into continuation-passing style. The effect is just to take the call stack and pass it around explicitly. But that won't give you the benefit you're probably thinking of, which is to be able to discard prior state after recursive calls to save space. You're just putting the state somewhere else.

The real question might be: Can you change any recursive algorithm to require only constant space, potentially by way of using tail recursion? The answer, of course, is maybe. Typically, recursive functions that use tree recursion (where recursive calls branch into multiple deeper recursive calls) might be hard to transform this way. Your algorithm fits this description.

(I initially suggested memoizing match or using DP for this problem, which would speed it up, but I guess that wouldn't actually save you space. Oh well.)

mqp
  • 70,359
  • 14
  • 95
  • 123
  • Well, .NET won't actually optimize it, but it'll be "eligible" for TCO. That's what I interpreted his question to be asking, not about how he could get it to run efficiently in C# specifically. – mqp Aug 22 '11 at 21:24
0

The simple way is to using a while(true) loop. Example:

public static bool match(string p, string s)
{
  while (true)
  {
    // normal code
    ...
    // tail call handling
    // instead of return match(x, y)
    var t1 = x; // need to use temps for evaluation of x
    var t2 = y; // same here
    p = t1;
    s = t2;
    continue; 
  }
}

This process is commonly known as TCE (or tail call elimination).

Update:

I missed the || at the end. You cannot convert this, as no calls to match are in a tail position. || requires the result to be evaluated before returning. The method could be perhaps rewritten to avoid that.

leppie
  • 115,091
  • 17
  • 196
  • 297
  • 1
    I'd be interested to see how you would actually implement this, because his algorithm is tree recursive -- it needs to recurse again with the initial string if the first letter matches but the match fails later. It doesn't seem like putting it all in a loop would allow you to maintain enough state to get that correct. – mqp Aug 22 '11 at 21:23
  • @mquander: You cannot apply this to non-tail recursion. Just the actual tail call of `return match` can be handled. The all other recursion, you need to use the stack. – leppie Aug 22 '11 at 21:25
  • @mquander: Thanks for letting me look again. Indeed, there are no methods calls in the tail position, so this transformation cannot be applied. It only works for proper tail recursive calls. – leppie Aug 22 '11 at 21:30
-1

C# doesn't current support tail recursion See this related question on the matter

Also check out this msdn article on a similar technique called trampolining

Community
  • 1
  • 1
Chris McGrath
  • 1,727
  • 3
  • 19
  • 45