Fibonacci series recursion error

H

Hans Georg Schaathun

Clearly it makes a difference in any case where you'd hit the recursion
: limit.

What kind of problems make you hit the limit?

Other than when you forget the base case, I mean.

: It's no big deal to do your own unwinding of the recursion to a
: loop, but the code is typically less clear.

Sure. And you have to live with your less clear code when you maintain
the system later.
 
P

Paul Rudin

Hans Georg Schaathun said:
Clearly it makes a difference in any case where you'd hit the recursion
: limit.

What kind of problems make you hit the limit?

Other than when you forget the base case, I mean.

Anytime you have enough data... there are plenty of things that are natural to
represent as recursive data structures, and often you don't know in
advance how much data your code is going to have to deal with.
 
P

Peter Otten

harrismh777 said:
Peter said:
For the record, the one true way to implement the Fibonacci series in
Python is

... a = b = 1
... while True:
... yield a
... a, b = b, a+b # look ma, no temporary variable

[snip]

I created two scripts, stressed them a bit, and then timed:
I called the two scripts fib.py and fib2.py... here they are:

==================begin fib.py============================
#!/home/marcus/bin/python3
def fib():
a=b=1
while True:
yield a
a,b=b,a+b # look ma, no temporary variable

from itertools import islice
flist=list(islice(fib(), 100000))
==================end fib.py==============================


==================begin fib2.py===========================
#!/home/marcus/bin/python3
def fib(i=1):
a=b=1;l=[]
for j in range(0,i):
l.append(a)
a,b = b,a+b # look ma, I can do it too....
return l

list=fib(100000)
==================end fib2.py=============================

[ TIMES ] 1 core, 1.6 Ghz, 3196 bogomips

marcus@shire:~/Python3$ time ./fib.py

real 0m2.775s
user 0m1.908s
sys 0m0.820s


marcus@shire:~/Python3$ time ./fib2.py

real 0m2.796s
user 0m1.916s
sys 0m0.824s



You will notice first that the times are *almost* identical. So,
down under the heart of python, something is wrong. (never mind)

Well, fib(10**5)[-1] has over 20000 digits, so I'd expect the runtime to be
dominated by long-integer arithmetic. And that is identical for both
scripts. You will get some improvement from switching to gmpy.
But the really interesting thing I found is that when I coded away
the temporary reference, 100ms of user time moved over to sys time. doh,
must be that 'somewhere' a 'temporary' reference had to be created...
and it doesn't look like it's optimized very well...

But, this should also go to show you... that there is *never* just
one obvious way to do anything... contrary to the ZEN of Python... ;-)

I take that as an indication that you are not Dutch ;)
On the other hand, I am very much interested in "yield," because of
its obvious persistent state, On the other hand, I don't like you fib()
function because it does not encapsulate the fib generator. I suppose
you are considering whatever module is holding the fib() code as the
encapsulation, but I thought the idea from the OP was to create a
generator that could be called and return a list... this is a little
goofy:
list(islice(fib(), X))

when what was wanted is:

list = fib(X)

I would not seriously recommend list(islice(generator, stop)) in that case,
either, but I think my fib() is a good building block:
.... items = []
.... def f(n):
.... if n <= len(items):
.... return items[:n]
.... items.extend(islice(g, n-len(items)))
.... return items[:n]
.... return f
........ a = b = 1
.... while True:
.... yield a
.... a, b = b, a + b
........ for x in _fib():
.... print "calculating", x
.... yield x
....
fib = cached_series(_noisy())
fib(0) []
fib(1)
calculating 1
[1]calculating 1
calculating 2
calculating 3
calculating 5
[1, 1, 2, 3, 5]
calculating 8
[1, 1, 2, 3, 5, 8]
 
H

harrismh777

Thomas said:
You can have both with the following:
def fib(max=None):
a = b = 1
while max is None or a <= max:
yield a
a, b = b, a+b
from itertools import islice
flist = list(islice(fib(), 100000))
flist2 = list(fib(100000))

Yes, yes, yes... but what I realized was really bugging me is the import
of islice... I don't like it... ; however, I agree that we should
combine Otten's ideas with my own... good collaboration... but like this:

fib3.py 'yields' within a 'for' (better than the while for a couple of
reasons I'll include later after the rebuttal... anyway because the def
with the yield compiles into an iterable we can take advantage of the
iteration protocol directly with the 'for' to either print the list over
time, or to get the list and save it... as so:

====================begin fib3.py=======================================
!/home/marcus/bin/python3
def fib(n=1):
a = b = 1
for j in range(n):
yield a
a, b = b, a+b

# to get the elements of the generator over time
for n in fib(10):
print(n, end=' ')

# or, get the whole list and save it
flist=[]
for n in fib(10):
flist.append(n)

====================end fib3.py=========================================

Or, same technique generally, we can take advantage of the fact that the
def with a 'yield' compiles into an iterable with a next method... all
part of the iteration protocal of python... next in 2.6, and __next__
in 3.2 / ... and the next(N) function provided kindly by somone for
convenience sake meaning N.next() in 2.6 and N.__next__() in 3.2 /
In the following code I've included a try block to catch the iteration
protocol's StopIteration error so that we can use the venerable 'while
True' do forever coding style outside the fib() generator. Again, we can
print the elements over time, or we can save the list as a whole for
later use, as so:

====================begin fib4.py=======================================
#!/home/marcus/bin/python3
def fib(n=1):
a = b = 1
for j in range(n):
yield a
a, b = b, a+b

# get the elements of the generator over time
X = fib(10)
while True:
try:
print(next(X), end=' ')
except StopIteration:
break

# or, get the whole list and save it
Y = fib(10)
flist=[]
while True:
try:
flist.append(next(Y))
except StopIteration:
break

====================end fib4.py=========================================
 
H

Hans Georg Schaathun

Anytime you have enough data... there are plenty of things that are natural to
: represent as recursive data structures, and often you don't know in
: advance how much data your code is going to have to deal with.

Sure, but one would think that if you can fit the data in memory,
then you can also fit the call stack. I can also /imagine/ that one
might run into a limit, but I cannot see any concrete examples where
it is likely.
 
S

Steven D'Aprano

Anytime you have enough data... there are plenty of things that are
natural to : represent as recursive data structures, and often you
don't know in : advance how much data your code is going to have to
deal with.

Sure, but one would think that if you can fit the data in memory, then
you can also fit the call stack. I can also /imagine/ that one might
run into a limit, but I cannot see any concrete examples where it is
likely.


Why? You might have 4000 MB of main memory, and only 2 MB (say?) of call
stack allocated. The call stack can't grow indefinitely. If it does, you
get a stack overflow:

http://www.ehow.com/list_6572027_reasons-stack-overflow.html

which, if you're lucky, will just crash the process. If you're unlucky,
it will lead to an exploit that malware can use to compromise your
machine.

In Python, the virtual machine protects you against stack overflow.
Before the stack overflows, Python raises RecursionError. You can
experiment by using sys.getrecursionlimit and sys.setrecursionlimit, but
be careful, if you increase the limit too high, a deeply recursive
function will overflow the stack.
 
H

Hans Georg Schaathun

On 01 May 2011 09:04:27 GMT, Steven D'Aprano
: Why? You might have 4000 MB of main memory, and only 2 MB (say?) of call
: stack allocated. The call stack can't grow indefinitely. If it does, you
: get a stack overflow:

Of course you do, but you are still only saying that there might be
an application where this might happen because of excessive although
logically correct recursion. You have not given a single example where
it actually happened.

: In Python, the virtual machine protects you against stack overflow.
: Before the stack overflows, Python raises RecursionError. You can
: experiment by using sys.getrecursionlimit and sys.setrecursionlimit, but
: be careful, if you increase the limit too high, a deeply recursive
: function will overflow the stack.

But the recursion limit is mainly there to protect you against faulty
base cases. Obviously, since it measures the number of items on the
stack and not their size.
 
S

Steven D'Aprano

On 01 May 2011 09:04:27 GMT, Steven D'Aprano
: Why? You might have 4000 MB of main memory, and only 2 MB (say?) of
call : stack allocated. The call stack can't grow indefinitely. If it
does, you : get a stack overflow:

Of course you do, but you are still only saying that there might be an
application where this might happen because of excessive although
logically correct recursion. You have not given a single example where
it actually happened.

Just google on "stack overflow crash".

: In Python, the virtual machine protects you against stack overflow. :
Before the stack overflows, Python raises RecursionError. You can :
experiment by using sys.getrecursionlimit and sys.setrecursionlimit, but
: be careful, if you increase the limit too high, a deeply recursive :
function will overflow the stack.

But the recursion limit is mainly there to protect you against faulty
base cases. Obviously, since it measures the number of items on the
stack and not their size.

The Python virtual machine knows how big each entry on the stack needs to
be. (I don't, but it's got to be at least the size of a pointer.) So
"number of items" is just a multiplication away from "size of the items".

In practice the main reason that stacks overflow is because of faulty
base cases in recursion. That's obvious. But in principle, any
sufficiently large number of function calls could overflow the stack. If
the call stack is (say) 1 MB (chosen only to make the maths easier), and
each function call requires (say) a single four-byte entry on the stack,
then you can have a maximum of 250000 function calls before overflowing
the stack.

If you don't like my numbers -- and you probably shouldn't, since I made
them up -- choose your own. But whatever numbers you choose, there *must*
be a maximum number of function calls before the stack overflows.

Not necessarily recursive function calls either: any nested function call
will do. However, it's generally only in recursion that you have more
than a few hundred calls on the stack.

So even if the base case is correct, you can overflow the stack. Given
the naive recursive factorial function:

def fact(n):
if n <= 1: return 1
return n*fact(n-1)

and the theoretical limit above, then fact(250001) will overflow the
stack even though the base case is correct.
 
H

Hans Georg Schaathun

On 01 May 2011 11:56:57 GMT, Steven D'Aprano
: Just google on "stack overflow crash".

And I get loads of examples of stack overflows, but I could not see
anybody linking this to logically correct recursion. Wikipedia, for
instance, mentions two common causes, neither of which has anything
to do with logically correct recursion.

: The Python virtual machine knows how big each entry on the stack needs to
: be. (I don't, but it's got to be at least the size of a pointer.) So
: "number of items" is just a multiplication away from "size of the items".

Does it? In a conventional stack you need to store all the local
variables for the function as well. Thus, there is no limit to how
much space a single element on the stack may require.

: But in principle, any
: sufficiently large number of function calls could overflow the stack.
: (...)
: If you don't like my numbers -- and you probably shouldn't, since I made
: them up -- choose your own. But whatever numbers you choose, there *must*
: be a maximum number of function calls before the stack overflows.

But all of this is in principle, and does not constitute any valid
reason to avoid recursion in practice. If you want to argue that
recursion is a bad thing in /practice/ you need a /practical/ argument.

There is also a limit to how far you can usefully recurse over a limited
data structure.

: def fact(n):
: if n <= 1: return 1
: return n*fact(n-1)
:
: and the theoretical limit above, then fact(250001) will overflow the
: stack even though the base case is correct.

Of course that will overflow, but you are now trying to calculate an
integer of several /million/ bits. Please suggest me a current-day
application where you would want to have and also be able to use such
a number.

There are many ways to crash a system if you want to.

But if you don't deliberately try to crash it, you are much more
likely to crash it because you allocate to much memory in each step
than because the recursion gets to deep. Consequently, because recursion
is usually a clearer form of expression than iterative loops, recursion
may actually be /less/ dangerous.
 
C

Chris Angelico

:  The Python virtual machine knows how big each entry on the stack needs to
:  be. (I don't, but it's got to be at least the size of a pointer.) So
:  "number of items" is just a multiplication away from "size of the items".

Does it?  In a conventional stack you need to store all the local
variables for the function as well.  Thus, there is no limit to how
much space a single element on the stack may require.

In anything less than a useless and trivial demo of recursion, there's
going to be some kind of local state for each call (in the case of a
fibonacci or factorial, that would be the number(s) being multiplied).
Whether they go on the stack or elsewhere, that's additional storage
that gets multiplied out.
There is also a limit to how far you can usefully recurse over a limited
data structure.

Sure. Serialize this Python object in a way that can be given to, say, PHP:
foo={"asdf":"qwer","zxcv":"1234"}; foo["self"]=[1,2,3,foo]
Recurse from self into the list, recurse from there into a
dictionary... Okay, that's a rather naive recursion and fraught with
risk, but there are more complex examples. And watching for cyclic
references would be O(N*N) as you'd need to maintain a full list of
every PyObject* that you've sighted (talking here from the C API, but
the same consideration applies whichever way you do it).
There are many ways to crash a system if you want to.

But if you don't deliberately try to crash it, you are much more
likely to crash it because you allocate to much memory in each step
than because the recursion gets to deep.  Consequently, because recursion
is usually a clearer form of expression than iterative loops, recursion
may actually be /less/ dangerous.

I'm not sure that recursion is clearer. Recursion is a way of
expressing the two rules:

1! = 1
n! = n * (n-1)!

But iteration is a way of expressing this equivalent rule:

n! = 1 * 2 * 3 * ... * n-1 * n

It really depends what you're trying to say.

Chris Angelico
 
T

Terry Reedy

Of course you do, but you are still only saying that there might be
an application where this might happen because of excessive although
logically correct recursion. You have not given a single example where
it actually happened.

I will. Stack overflow *can* happen with a bad base case. It *will*
happen with correct linear recursion* applied to a large enough
collection on a finite-memory machine (as opposed to an 'infinite'
memory Turing machine).

def count_popable_collection(pop_col):
try:
pop_col.pop()
return count_popable_collection(pop_col) + 1
except (KeyError,IndexError):
return 0

print(count_popable_collection({1,2,3}))
print(count_popable_collection([1,2,3]))

Both calls correctly print 3, but will fail for large enough sets or
lists. I call the above body recursion*. A tail-recursive version

def count_popable_collection2(pop_col, cnt=0):
try:
pop_col.pop()
return count_popable_collection2(pop_col, cnt + 1)
except (KeyError,IndexError):
return cnt

print(count_popable_collection2({1,2,3}))
print(count_popable_collection2([1,2,3]))

is less clear to most people, I think, and, executed as written, fails
at the same point with the same memory error. Try either of the above
with list(range(bignum)) (I am using 3.2).

This does not make linear recursion 'bad', just impractical for general
use on finite-memory machines. While I consider it very useful for
learning, it is unnecessary because it is easy to write an iterative
version. So called tail-recursion optimization saves memory by REMOVING
RECURSION and replacing it with iteration.

def count_popable_collection3(pop_col):
cnt = 0
while True:
try:
pop_col.pop()
cnt += 1
except (KeyError,IndexError):
return cnt

print(count_popable_collection3({1,2,3}))
print(count_popable_collection3([1,2,3]))

Python does not do this automatically because 1) it can be a semantic
change under some circumstances; 2) one who wants the iterative version
can just as easily write it directly; and 3) Python has a better way to
process collections that removes essentially all the boilerplate in the
recursive-call and while-loop versions:

def count_popable_collection4(pop_col):
cnt = 0
for item in pop_col:
cnt += 1
return cnt

print(count_popable_collection4({1,2,3}))
print(count_popable_collection4([1,2,3]))


Binary recursion* is a different case because the exponential growth in
leaf number and hence time limits the useful depth of recursion to well
below the default of 1000.

* linear recursion: usually and at most one recursive call per call
* binary recursion: usually and at most two recursive calls per call
Fib is the best known example.
* tail recursion: base cases return completed calculations
* body recursion: base cases return starting values, often constants
 
B

BartC

Steven D'Aprano said:
I don't know what M Harris thinks he is trying to say either, but the
*naive* Fibonacci recursive algorithm is particularly badly performing
and should be avoided. It's ironic that of the two classic algorithms
used for teaching beginner programmers about recursion, neither is useful
in practice.

Yes, it generates lots of calls.

About 22000 for fib(20), and 330 million for fib(40).

That's why it's popular for benchmarks that measure performance of function
calls. Using an iterative algorithm wouldn't work quite so well...
 
S

Steven D'Aprano

On 01 May 2011 11:56:57 GMT, Steven D'Aprano
: Just google on "stack overflow crash".

And I get loads of examples of stack overflows, but I could not see
anybody linking this to logically correct recursion. Wikipedia, for
instance, mentions two common causes, neither of which has anything to
do with logically correct recursion.

Ah, I see where you're coming from now! You think I'm arguing *against*
the use of recursion. Not at all. Earlier in this thread, I said:

"Consequently, the naive recursive function is ridiculously slow and
memory-hungry.

This seems to have give rise to a myth that recursion should be avoided.
What needs to be avoided is *badly thought out* recursion."

[Emphasis in original.]



: The Python virtual machine knows how big each entry on the stack
needs to : be. (I don't, but it's got to be at least the size of a
pointer.) So : "number of items" is just a multiplication away from
"size of the items".

Does it? In a conventional stack you need to store all the local
variables for the function as well. Thus, there is no limit to how much
space a single element on the stack may require.

To be honest, I don't know what Python does with local variables. But I
*guess* it uses a constant-sized record which points to the locals,
because that's how I'd do it :)

I do know that objects in CPython all live in the heap, not on the stack,
so even if local variables are placed directly on the stack, that's only
a pointer to the object, not the entire object.


[...]
But all of this is in principle, and does not constitute any valid
reason to avoid recursion in practice. If you want to argue that
recursion is a bad thing in /practice/ you need a /practical/ argument.

Okay. In *practice*, all else being equal, recursion is less efficient
than iteration, especially in Python which doesn't optimize tail-
recursion. So if you have a choice between two algorithms which are
equally simple and clear, and one is recursive and the other uses
iteration, the one with iteration will be more efficient.

However, and equally in practice, it's unusual to have two equally simple
algorithms. Usually one or the other will be much simpler than the other,
and you should avoid "optimizing" code based on hypothetical
considerations and instead prefer simple code over complicated, unless
absolutely necessary.

Given a choice between a complicated iterative algorithm and a simple
recursive version, there's no prima facie reason to expect the recursive
version to *necessarily* be slower than iteration in Python *merely*
because it uses recursion. As always, if speed is an issue, profile and
identify the bottlenecks before deciding how to fix them.
 
T

Terry Reedy

Yes, it generates lots of calls.

About 22000 for fib(20), and 330 million for fib(40).

Using the standard double recursion implementation of fib, ncf(n)
(number of calls to fib() for fib(n)) requires ncf(n-2) + ncf(n+1) + 1
calls. The +1 is for the call to fib(n) itself). So it grows a bit
faster than fib(n).

Fib(n) is actually calculated as the sum of fib(n) 1s: 1+1+1....+1
fib(n) times as 1 is the only non-0 base case and there is nothing else
added or multiplied in non-base cases. To put it another way, there are
fib(n) leaf nodes labeled 1 in the unbalanced tree generated by the
recursion.
 
H

Hans Georg Schaathun

Sure. Serialize this Python object in a way that can be given to, say, PHP:
: foo={"asdf":"qwer","zxcv":"1234"}; foo["self"]=[1,2,3,foo]
: Recurse from self into the list, recurse from there into a
: dictionary... Okay, that's a rather naive recursion and fraught with
: risk, but there are more complex examples. And watching for cyclic
: references would be O(N*N) as you'd need to maintain a full list of
: every PyObject* that you've sighted (talking here from the C API, but
: the same consideration applies whichever way you do it).

Wouldn't cyclic references give infinite recursion? And remain
infinitive if you recode it iteratively?

: I'm not sure that recursion is clearer. Recursion is a way of
: expressing the two rules:
:
: 1! = 1
: n! = n * (n-1)!
:
: But iteration is a way of expressing this equivalent rule:
:
: n! = 1 * 2 * 3 * ... * n-1 * n
:
: It really depends what you're trying to say.

True. There is a place for everything. However, in the example
above, you can map the recursive definition directly into python
without further ado. In order to express the one-liner in python,
as iteration, you need to introduce additional elements, namely
a state (index variable). Hence, recursion is clearer by being
close to the language you would normally use to describe the
problem.
 
C

Chris Angelico

 Sure. Serialize this Python object in a way that can be given to, say, PHP:
:  foo={"asdf":"qwer","zxcv":"1234"}; foo["self"]=[1,2,3,foo]

Wouldn't cyclic references give infinite recursion?  And remain
infinitive if you recode it iteratively?

Well, I don't know of a decent non-recursive way to process a
recursive structure. Incidentally, this example is almost directly
from some working code of ours; I have a C function that recurses over
a Python dictionary and aborts as soon as it's found "too much" data
(for a fairly arbitrary definition of "too much", but one that's
deliberately designed to prevent infinite recursion). Since every
element in the dictionary can be a list/dict, and every element of
those can be too, there's no non-recursive way to do it, other than by
doing the recursion yourself:

# partly pseudocode
searchme=[foo]
while len(searchme):
cur=get_next_elem(searchme[-1])
if cur==end_of_list: searchme[-1:]=[]
else: if can_be_recursed_into(cur): searchme.append(cur)
else: output(cur)

This would work, more or less, but it merely trades "true" recursion
for the searchme[] stack. Yes, it's iterative. No, it hasn't abolished
recursion.
True.  There is a place for everything.  However, in the example
above, you can map the recursive definition directly into python
without further ado.  In order to express the one-liner in python,
as iteration, you need to introduce additional elements, namely
a state (index variable).  Hence, recursion is clearer by being
close to the language you would normally use to describe the
problem.

True, and you could abolish a lot of temporary variables by turning
them into parameters to recursive calls. But you've abolished nothing.

The recursive factorial is very similar to:
reduce(`*,range(1,n+1))
The iterative is very similar to:
reduce(`*,xrange(1,n+1))
One of 'em stacks up all the numbers and the other gets 'em as it
needs 'em. Fundamentally, there's really not a lot of difference. By
piling up the numbers on your stack, you just change the format of
your saved state, you don't actually save anything.

Chris Angelico
 
C

Chris Angelico

reduce(`*,range(1,n+1))
reduce(`*,xrange(1,n+1))

Whoops, forgot which language I was using. Back-tick functions not
being available, these need to be:

reduce(operator.mul,range(1,n+1))
reduce(operator.mul,xrange(1,n+1))

Chris Angelico
 
R

rusi

For the record, the one true way to implement the Fibonacci series in Python
is


...     a = b = 1
...     while True:
...             yield a
...             a, b = b, a+b # look ma, no temporary variable

Not any claim to 'the one true pythonic way' but fib can be written in
a clean recursive way with linear space-time behavior asz follows:

Dijkstra explained that fib is an 2nd order recurrence
-- fib(n) defined in terms of fib (n-1) and fib(n-2)
whereas programming loops and recursion are 1st order -- state at
iteration n defines state at iteration n+1.

Converting the 2nd order fib relation to a 1st order one trivially
gives a linear program.
The key insight from Dijkstra is that all we need to do is to move
from a recursive function returning fibonacci nos to one returning
pairs of adjacent ones.

def fibpair(n):
# returns (fib(n), fib(n+1))
if n==0:
return (1,1)
else:
(a,b) = fibpair(n-1)
return (b, a+b)

After that fib is just this:

def fib(n):
a,b = fibpair(n)
return a;
 
R

rusi

The number of calls is given by a recursive function with a similar form
as that of Fibonacci. As far as I know, it doesn't have a standard name,
but I'll call it R(n):

R(n) = R(n-1) + R(n-2) + 1, where R(0) = R(1) = 1

Changing your definition slightly to the following:

def r(n):
if n==0 or n==1: return 0
else: return r(n-1) + r(n-2) + 1


This r counts the number of times the '+' is done in the original
(naive) fib.

We see it is the same as fib (off by 1)
[fib(n) for n in range(10)] [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
[r(n) for n in range(10)] [0, 0, 1, 2, 4, 7, 12, 20, 33, 54]

So it does not need a new name :)
 

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

Forum statistics

Threads
473,773
Messages
2,569,594
Members
45,121
Latest member
LowellMcGu
Top