Path: csiph.com!goblin2!goblin.stu.neva.ru!newsfeed.xs4all.nl!newsfeed7.news.xs4all.nl!newsgate.cistron.nl!newsgate.news.xs4all.nl!post.news.xs4all.nl!not-for-mail Return-Path: X-Original-To: python-list@python.org Delivered-To: python-list@mail.python.org X-Spam-Status: OK 0.004 X-Spam-Evidence: '*H*': 0.99; '*S*': 0.00; 'else:': 0.03; 'method,': 0.07; 'cc:addr:python-list': 0.09; 'implies': 0.09; 'returns,': 0.09; 'rounds': 0.09; 'subclass': 0.09; 'subclasses': 0.09; 'def': 0.13; 'ignore': 0.14; 'argument': 0.15; 'thu,': 0.15; 'value.': 0.15; '"is': 0.16; '1:13': 0.16; 'clear.': 0.16; 'from:addr:rosuav': 0.16; 'from:name:chris angelico': 0.16; 'function).': 0.16; 'helps!': 0.16; 'infinity': 0.16; 'subject:recursion': 0.16; 'sure.': 0.16; 'trap': 0.16; 'value:': 0.16; 'wrote:': 0.16; 'case.': 0.18; '2015': 0.20; 'cc:2**0': 0.20; 'cc:addr:python.org': 0.20; 'aug': 0.20; 'function,': 0.22; 'trying': 0.22; 'am,': 0.23; "python's": 0.23; 'this:': 0.23; 'header:In-Reply-To:1': 0.24; "doesn't": 0.26; '(which': 0.26; 'possibility': 0.27; 'message-id:@mail.gmail.com': 0.27; 'function': 0.28; 'looks': 0.29; 'block,': 0.29; 'dead,': 0.29; 'equally': 0.29; 'fighting': 0.29; 'tail': 0.29; 'convert': 0.29; 'code': 0.30; 'call.': 0.30; 'skip:s 30': 0.31; 'another': 0.32; "can't": 0.32; 'skip:_ 10': 0.32; 'non': 0.32; 'though,': 0.32; 'point': 0.33; 'call,': 0.33; 'effort.': 0.33; 'add': 0.34; 'that,': 0.34; 'received:google.com': 0.35; 'could': 0.35; 'done': 0.35; 'returning': 0.35; 'skip:s 60': 0.35; "isn't": 0.35; 'but': 0.36; 'subject:?': 0.36; 'subject:: ': 0.37; 'skip:s 50': 0.37; 'doing': 0.38; 'version': 0.38; 'skip:s 40': 0.38; 'turned': 0.38; 'someone': 0.38; 'end': 0.39; 'means': 0.39; 'goes': 0.39; 'sure': 0.39; 'where': 0.40; 'still': 0.40; 'your': 0.60; "you'll": 0.61; 'hope': 0.61; 'more': 0.63; 'different': 0.63; 'necessarily': 0.63; 'between': 0.65; 'differences': 0.66; 'here': 0.66; 'worth': 0.67; 'choose': 0.68; 'attacking': 0.84; 'chrisa': 0.84; 'etc,': 0.84; 'recursion,': 0.84; 'recursive.': 0.84; 'ugly,': 0.84; 'subject:this': 0.85; 'absolutely': 0.88; 'to:none': 0.91 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113; h=mime-version:in-reply-to:references:date:message-id:subject:from:cc :content-type; bh=PvIfPXAlUkiYxpbPy9wb2UHJnodMzH/QDnGUoVxQjU0=; b=jhSwyUnupe31HQpxlyBqEViKJ2XtL/y/53+GzSjBHr/PcI46ldwftXdxW/fiNMPEis Z8aED5f26t5hoPY9r850Gc754VraAPdV/uPMmjB2mZSVaR3rUJKtxtI9F/OJ/4pNSmeG bPMtB0d1+wr47qu0Evjtc2zbR+2ypSoMJ15ztzSZ5zyMzkQjdQ39P+EXfSFppvTItAZx r+iUQ5fbknl9qDxNGHHZio5GJF/WdJF3g9nx1Z/FSbgaJeT6TtdIuVHLdhlCG88v5cmi QZSdHSWZLr1EFm6NZPU2JXtw4a5/u3rUcBMxQyD9+eJH0i0mvRkq3eLzCFbnzwOpMYvJ 9N8g== MIME-Version: 1.0 X-Received: by 10.107.31.134 with SMTP id f128mr10025161iof.19.1438789917513; Wed, 05 Aug 2015 08:51:57 -0700 (PDT) In-Reply-To: <53903f1c-a740-4508-9d24-0b0bec9ad339@googlegroups.com> References: <53903f1c-a740-4508-9d24-0b0bec9ad339@googlegroups.com> Date: Thu, 6 Aug 2015 01:51:57 +1000 Subject: Re: Is this an example of tail recursion? From: Chris Angelico Cc: "python-list@python.org" Content-Type: text/plain; charset=UTF-8 X-BeenThere: python-list@python.org X-Mailman-Version: 2.1.20+ Precedence: list List-Id: General discussion list for the Python programming language List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Newsgroups: comp.lang.python Message-ID: Lines: 125 NNTP-Posting-Host: 2001:888:2000:d::a6 X-Trace: 1438789920 news.xs4all.nl 2931 [2001:888:2000:d::a6]:49864 X-Complaints-To: abuse@xs4all.nl Xref: csiph.com comp.lang.python:95024 On Thu, Aug 6, 2015 at 1:13 AM, 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