Recursive functions not returning lists as expected

R

rickhg12hs

Would a kind soul explain something basic to a python noob?

Why doesn't this function always return a list?

def recur_trace(x,y):
print x,y
if not x:
return y
recur_trace(x[1:], y + [x[0]])

Here are a couple sample runs.
[] [1,2,3]
[1,2,3]

So that worked okay and returned the list [1,2,3].
print(recur_trace([9,8],[1,2,3]))
[9,8] [1,2,3]
[8] [1,2,3,9]
[] [1,2,3,9,8]
None

No list is returned here. Why?
[Using Python 2.6.2]
 
C

Charles

rickhg12hs said:
Would a kind soul explain something basic to a python noob?

Why doesn't this function always return a list?

def recur_trace(x,y):
print x,y
if not x:
return y
recur_trace(x[1:], y + [x[0]])

shouldn't it be
return recur_trace(x[1:], y + [x[0]])
otherwise the recursive call returns nothing

Charles
 
C

Cameron Simpson

| Would a kind soul explain something basic to a python noob?
|
| Why doesn't this function always return a list?
|
| def recur_trace(x,y):
| print x,y
| if not x:
| return y
| recur_trace(x[1:], y + [x[0]])

You need:
return recur_trace(x[1:], y + [x[0]])

Otherwise the function returns None.
--
Cameron Simpson <[email protected]> DoD#743
http://www.cskk.ezoshosting.com/cs/

[...] every time you touch something, if your security systems rely
on biometric ID, then you're essentially leaving your pin number on a
post-it note. - Ben Goldacre, http://www.badscience.net//?p=585
 
R

rickhg12hs

| Would a kind soul explain something basic to a python noob?
|
| Why doesn't this function always return a list?
|
| def recur_trace(x,y):
|   print x,y
|   if not x:
|     return y
|   recur_trace(x[1:], y + [x[0]])

You need:
    return recur_trace(x[1:], y + [x[0]])

Otherwise the function returns None.

Ah, an explicit "return" is required. Thanks!
[To bad there's no tail recursion optimization.] 8-(
 
P

Paul Rudin

rickhg12hs said:
Would a kind soul explain something basic to a python noob?

Why doesn't this function always return a list?

def recur_trace(x,y):
print x,y
if not x:
return y
recur_trace(x[1:], y + [x[0]])

Here are a couple sample runs.
print(recur_trace([],[1,2,3]))
[] [1,2,3]
[1,2,3]

So that worked okay and returned the list [1,2,3].
print(recur_trace([9,8],[1,2,3]))
[9,8] [1,2,3]
[8] [1,2,3,9]
[] [1,2,3,9,8]
None

No list is returned here. Why?
[Using Python 2.6.2]

Without trying it out I'd guess you want a "return" in your last
line. (If python falls out of a function without hitting an explicit
return then None is returned by default.)
 
T

Terry Reedy

| Would a kind soul explain something basic to a python noob?
|
| Why doesn't this function always return a list?
|
| def recur_trace(x,y):
| print x,y
| if not x:
| return y
| recur_trace(x[1:], y + [x[0]])

You need:
return recur_trace(x[1:], y + [x[0]])

Otherwise the function returns None.

Ah, an explicit "return" is required. Thanks!
[To bad there's no tail recursion optimization.] 8-(

That issue is much more complicated than you probably imagine. Here is
part of it, using your toy example of computing y +x. [Code snippets
below are untested and might have typos.]

Write your tail recursive function with the recursive branch first:

def t_r(x,y):
if x:
return t_r(x[1:], y+[x[0]])
else:
return y

and the conversion ('optimization') to while form is trivial:

def t_w(x,y):
while x:
x,y = x[1:], y+[x[0]]
else:
return y

Of course, most would omit the 'else:', which I left to emphasize the
paralellism. More importantly, 'while' loops are relatively rare in
Python because scanning an iterable (a typical use of tail recursion)
can be and usually is done (generically) with for loops instead:

def t_f(x,y):
for o in x:
y = y + [o]
return y

Of course, this, like your original code, wastefully creates numerous
temporary lists (two per iteration) when only one new list is needed for
the entire process. This is easily fixed in the loop version:

def t_f2(x,y):
r = y[:]
for o in x:
r.append(o)
return r

Making that important fix in the recursive version is harder since the
copying should be done exactly once, and not with every recursive call.
I leave it to you to try either of the two main approaches.

Terry Jan Reedy
 
R

rickhg12hs

On 5/4/2010 1:45 AM, rickhg12hs wrote: [snip]
[To bad there's no tail recursion optimization.]  8-(

That issue is much more complicated than you probably imagine.
[snip]

No imagination is necessary - functional languages (e.g., Erlang) do
it quite well.
 
T

Terry Reedy

On 5/4/2010 1:45 AM, rickhg12hs wrote: [snip]
[To bad there's no tail recursion optimization.] 8-(

This is prinarily a space optimization to conserve stack frame space.
The speedup would be minimal. Using while or for loops has the same
space saving with better speedup (slow function are also gone) and
easier optimization in other ways, as I showed.
That issue is much more complicated than you probably imagine.
[snip]

No imagination is necessary - functional languages (e.g., Erlang) do
it quite well.

More complicated in the Python context. Does Erlang use static or
dynamic name binding, or early versus late local name resolution?
Python, by design, resolves global names within a function each time the
function is called. So whether a call is recursive cannot be determined
until the call is made.

It is not difficult for CPython to space-optimize *all* tail calls,
recursive or not. But this would have the bad effect of leaving gaps in
error tracebacks and is not done for that reason. To only space-optimize
simple recursive tail calls would take more time, and one would still
lose traceback information.

If you imagined all of the above, then indeed I underestimated you ;-).

Terry Jan Reedy
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,774
Messages
2,569,596
Members
45,141
Latest member
BlissKeto
Top