Is this an example of tail recursion?

Chris Angelico rosuav at gmail.com
Wed Aug 5 17:51:57 CEST 2015


On Thu, Aug 6, 2015 at 1:13 AM,  <jennyfurtado2 at gmail.com> wrote:
> I am trying to learn differences between tail recursion and non tail recursion.

Tail recursion is where you do exactly this:

    return some_function(...)

Absolutely nothing is allowed to happen around or after that function,
and that also means you can't do that inside a try/except/finally
block, nor a with block, etc, etc, etc. It has to be nothing more than
a function call, and you return the exact result of that call.

> Is the following recursive code tail recursive?
> If it is not how to convert it to tail recursion?
> If it is how to convert it to non tail recursion?
>
>    def __init__(self):
>        self.dpw = 0

Not tail recursive - not recursive - doesn't call anything. Trivial case. :)

>    def soldiersVsDefenders(self,soldiers,defenders):
>        # soldiers win
>        if defenders <=0:
>           return 0
>        # castle/defenders win
>        if soldiers <= 0:
>           return self.INFINITY

In these cases, equally trivial - not recursive in any form.

>        # do another round of fighting
>        # 1. Soldiers kill as many defenders
>        defendersLeft = defenders - soldiers
>        # 2. defendersLeft kill as many soldiers
>        soldiersLeft = soldiers - defendersLeft

(Interesting that the attacking soldiers get the first strike.)

>        return 1 + self.soldiersVsDefenders(soldiersLeft,defendersLeft)

This is NOT tail recursion, because you add 1 at the end of it. The
way to make it tail recursive would be to add another argument to the
function, which it would keep adding to; whenever it returns, you
would add the accumulator to the return value:

def soldiersVsDefenders(self,soldiers,defenders, accum=0):
    if defenders <= 0:
        return 0 + accum
    if soldiers <= 0:
        return self.INFINITY + accum
    # other code goes here
    return self.soldiersVsDefenders(soldiersLeft,defendersLeft,1+accum)

Now it's tail recursive. If this looks ugly, it's because it is; tail
recursion often isn't worth the effort.

Note that, as far as Python's concerned, this is a tail call, but
isn't necessarily *recursion* (which implies that you somehow know
you're calling the same function). If someone subclasses your code and
overrides this method, your code will call the subclass's version - if
the subclass calls through to super(), you'll end up with mutual
recursion, but still not a simple case of tail recursion. However, you
could choose to ignore this possibility and manually convert this into
iteration:

def soldiersVsDefenders(self,soldiers,defenders):
    rounds = 0
    while soldiers and defenders:
        # do another round of fighting
        # 1. Soldiers kill as many defenders
        defendersLeft = defenders - soldiers
        # 2. defendersLeft kill as many soldiers
        soldiersLeft = soldiers - defendersLeft
        rounds += 1
    if defenders <= 0:
        return rounds
    return self.INFINITY + rounds

How's that look? Better? Worse?

On to the next function.

>    def oneWave(self,soldiers,defenders,castleHits):
>        # castle is dead, let soldiers play against defenders
>        if castleHits <= 0:
>            defendersLeft = defenders - self.dpw
>            return self.soldiersVsDefenders(soldiers,defendersLeft)

This is a tail call. It's not tail *recursion* because you're calling
a completely different function, but you are indeed calling another
function and directly returning its return value.

>        mini = self.INFINITY
>        for i in range(0,soldiers):
>            if i > defenders:
>                 break
>            soldiersLeft = soldiers - (defenders -i)
>            defendersLeft = defenders - i + self.dpw
>            castleHitsLeft = castleHits - (soldiers -i)
>            mini = min(mini,1 + self.oneWave(soldiersLeft,defendersLeft,castleHitsLeft))
>        return mini

Not sure what the point of all this is, but sure. This clearly isn't
tail recursion, though, as it's doing a whole lot of other work after
the recursive call. Having a loop and recursion like this is pretty
scary, but in terms of "is this or isn't this tail recursive", it's
pretty clear.

>    def playGame(self,soldiers,castleHits,defendersPerWave):
>        self.dpw = defendersPerWave
>        numWaves = self.oneWave(soldiers,0,castleHits)
>        if numWaves >= self.INFINITY:
>           return -1
>        else:
>           return numWaves

And this is, again, no tail call. If the trap for INFINITY becoming -1
were inside oneWave(), then this could be turned into a tail call, but
as it is, there's more work to be done after the other function
returns, so it's not a tail call.

Hope that helps!

ChrisA


More information about the Python-list mailing list