Adrian Andronic
Adrian Andronic

Reputation: 79

Understanding Recursive Function

I'm working through the book NLP with Python, and I came across this example from an 'advanced' section. I'd appreciate help understanding how it works. The function computes all possibilities of a number of syllables to reach a 'meter' length n. Short syllables "S" take up one unit of length, while long syllables "L" take up two units of length. So, for a meter length of 4, the return statement looks like this:

['SSSS', 'SSL', 'SLS', 'LSS', 'LL']

The function:

def virahanka1(n):
    if n == 0:
        return [""]
    elif n == 1:
        return ["S"]
    else:
        s = ["S" + prosody for prosody in virahanka1(n-1)]
        l = ["L" + prosody for prosody in virahanka1(n-2)]
        return s + l

The part I don't understand is how the 'SSL', 'SLS', and 'LSS' matches are made, if s and l are separate lists. Also in the line "for prosody in virahanka1(n-1)," what is prosody? Is it what the function is returning each time? I'm trying to think through it step by step but I'm not getting anywhere. Thanks in advance for your help!

Adrian

Upvotes: 1

Views: 387

Answers (3)

jocke-l
jocke-l

Reputation: 703

This function says that:

virakhanka1(n) is the same as [""] when n is zero, ["S"] when n is 1, and s + l otherwise. Where s is the same as the result of "S" prepended to each elements in the resulting list of virahanka1(n - 1), and l the same as "L" prepended to the elements of virahanka1(n - 2).

So the computation would be:

When n is 0:
    [""]
When n is 1:
    ["S"]
When n is 2:
    s = ["S" + "S"]
    l = ["L" + ""]
    s + l = ["SS", "L"]
When n is 3:
    s = ["S" + "SS", "S" + "L"]
    l = ["L" + "S"]
    s + l = ["SSS", "SL", "LS"]
When n is 4:
    s = ["S" + "SSS", "S" + "SL", "S" + "LS"]
    l = ["L" + "SS", "L" + "L"]
    s + l = ['SSSS", "SSL", "SLS", "LSS", "LL"]

And there you have it, step by step.

You need to know the results of the other function calls in order to calculate the final value, which can be pretty messy to do manually as you can see. It is important though that you do not try to think recursively in your head. This would cause your mind to melt. I described the function in words, so that you can see that these kind of functions is are descriptions, and not a sequence of commands.

The prosody you see, that is a part of s and l definitions, are variables. They are used in a list-comprehension, which is a way of building lists. I've described earlier how this list is built.

Upvotes: 0

MakeCents
MakeCents

Reputation: 764

I tried to melt my brain. I added print statements to explain to me what was happening. I think the most confusing part about recursive calls is that it seems to go into the call forward but come out backwards, as you may see with the prints when you run the following code;

def virahanka1(n):
    if n == 4:
            print 'Lets Begin for ', n
    else:
            print 'recursive call for ', n, '\n'
    if n == 0:
        print 'n = 0 so adding "" to below'
        return [""]
    elif n == 1:
        print 'n = 1 so returning S for below'
        return ["S"]
    else:
        print 'next recursivly call ' + str(n) + '-1 for S'
        s = ["S" + prosody for prosody in virahanka1(n-1)]
        print '"S" + each string in s equals', s
        if n == 4:
            print '**Above is the result for s**'
        print 'n =',n,'\n', 'next recursivly call ' + str(n) + '-2 for L'
        l = ["L" + prosody for prosody in virahanka1(n-2)]
        print '\t','what was returned + each string in l now equals', l
        if n == 4:
            print '**Above is the result for l**','\n','**Below is the end result of s + l**'
        print 'returning s + l',s+l,'for below', '\n','='*70
        return s + l
virahanka1(4)

Still confusing for me, but with this and Jocke's elegant explanation, I think I can understand what is going on.

How about you?

Below is what the code above produces;

Lets Begin for  4
next recursivly call 4-1 for S
recursive call for  3

next recursivly call 3-1 for S
recursive call for  2

next recursivly call 2-1 for S
recursive call for  1

n = 1 so returning S for below
"S" + each string in s equals ['SS']
n = 2
next recursivly call 2-2 for L
recursive call for  0

n = 0 so adding "" to below
        what was returned + each string in l now equals ['L']
returning s + l ['SS', 'L'] for below
======================================================================
"S" + each string in s equals ['SSS', 'SL']
n = 3
next recursivly call 3-2 for L
recursive call for  1

n = 1 so returning S for below
        what was returned + each string in l now equals ['LS']
returning s + l ['SSS', 'SL', 'LS'] for below
======================================================================
"S" + each string in s equals ['SSSS', 'SSL', 'SLS']
**Above is the result for s**
n = 4
next recursivly call 4-2 for L
recursive call for  2

next recursivly call 2-1 for S
recursive call for  1

n = 1 so returning S for below
"S" + each string in s equals ['SS']
n = 2
next recursivly call 2-2 for L
recursive call for  0

n = 0 so adding "" to below
        what was returned + each string in l now equals ['L']
returning s + l ['SS', 'L'] for below
======================================================================
        what was returned + each string in l now equals ['LSS', 'LL']
**Above is the result for l**
**Below is the end result of s + l**
returning s + l ['SSSS', 'SSL', 'SLS', 'LSS', 'LL'] for below
======================================================================

Upvotes: 1

nneonneo
nneonneo

Reputation: 179707

Let's just build the function from scratch. That's a good way to understand it thoroughly.

Suppose then that we want a recursive function to enumerate every combination of Ls and Ss to make a given meter length n. Let's just consider some simple cases:

  • n = 0: Only way to do this is with an empty string.
  • n = 1: Only way to do this is with a single S.
  • n = 2: You can do it with a single L, or two Ss.
  • n = 3: LS, SL, SSS.

Now, think about how you might build the answer for n = 4 given the above data. Well, the answer would either involve adding an S to a meter length of 3, or adding an L to a meter length of 2. So, the answer in this case would be LL, LSS from n = 2 and SLS, SSL, SSSS from n = 3. You can check that this is all possible combinations. We can also see that n = 2 and n = 3 can be obtained from n = 0,1 and n=1,2 similarly, so we don't need to special-case them.

Generally, then, for n ≥ 2, you can derive the strings for length n by looking at strings of length n-1 and length n-2.

Then, the answer is obvious:

  • if n = 0, return just an empty string
  • if n = 1, return a single S
  • otherwise, return the result of adding an S to all strings of meter length n-1, combined with the result of adding an L to all strings of meter length n-2.

By the way, the function as written is a bit inefficient because it recalculates a lot of values. That would make it very slow if you asked for e.g. n = 30. You can make it faster very easily by using the new lru_cache from Python 3.3:

@lru_cache(maxsize=None)
def virahanka1(n):
    ...

This caches results for each n, making it much faster.

Upvotes: 2

Related Questions