python scooping and recursion - python

I am struck in a small recursive code. I have printed output and it prints fine but when I try to put a counter to actually count my answers, it gives me scooping errors.
total = 0
def foo(me, t):
if t<0:
return
if t==0:
total = total+1
return
for i in range(1, me+1):
total = total+1
return foo(i, t-i)
it says local variable referenced before assignment, well, I am trying to refer total in the first line.... Its not about global variables, I have tried to use global as well but in vain.
This is pure scooping issue, any ideas?

As mentioned by others, you need the global statement for total. Also, as noted by Svante, the for loop is unnecessary as coded since i is always 1. So, with an equivalent version of your code:
total = 0
def foo(me, t):
global total
if t < 0:
return
total = total + 1
if t == 0:
return
return foo(1, t-1)
foo(99, 100)
print total
It should be easier to see that foo(99, 100) will indeed be 101 since you're essentially counting down from 100 to 0. I'm not sure why you think otherwise?

You forgot to make sure to set total as a global in your function. You said "I have tried to use global as well but in vain." but when I try it below it doesn't throw any error:
total = 0
def foo(me, t):
global total
if t<0:
return
if t==0:
total = total+1
return
for i in range(1, me+1):
total = total+1
return foo(i, t-i)

I'm not sure you really know what you are trying to do...
(at least if you say that adding the global keyword gives incorrect results, but silences the errors)
you do need the statement "global total" if you are going to to try to reference total (which you are doing)
(what are you expecting when you execute foo(99, 100)?)
maybe your boundary conditions are wrong?
'cause with the arguments (99, 100)
foo will skip the two if statements
loop in the following loop:
for i in range(1, 100):
total += 1
return foo(i, 100-i)
which really is equivalent to
else:
total += 1
return foo(1, 99)
(like Svante was saying)
based on your two if conditions
foo(1,99) will correctly generate total += 100
(99 times it will execute your "else" statement bringing the total to 100, and then finally it will reach t == 0 and execute the last final if where it will push the total to your "incorrect" 101)
you should also use elif for your second case

As a generic advice, recursion should always use return values, and not global variables. Recursion has already its own load of complexity, increasing it with side-effects and not clear interfaces will make it even worse.
You should try something in the lines of this:
def foo(me, t):
if t<0:
return 0
if t==0:
return foo(me, t+1)
return foo(me-1, t)
for i in range(1, me+1):
tot = foo(i, t-i)
return tot
Note: this code is wrong, it will not solve your problem and it will not even work on its own; I put it just to give a kind of idea of how to design a recursion that is easier to manage.

Thanks people, thanks youarf, Liffredo.
I was wrong, I didnt notice that I was returning before adding-up things, loop was only running once, I have fixed the code. It is like this:
def foo(me, t):
if t<0:
return 0
if t==0:
return 1
toreturn = 0
for i in range(1, me+1):
toreturn = toreturn + foo(i, t-i)
return toreturn
This snippet is for this problem at http://projecteuler.net/index.php?section=problems&id=76

Related

Number of recursive function calls

I'm a total beginner at Python and I was wondering how to get the number of recursive function calls ?
is it 2^n (if n is the argument given)
The amount of calls completely depends on the algorithm. Sometimes it is 2**n, but other times it could be n! or a quadratic, or anything else.
To work out how many it is, you can you use my method. This might get shunned by some other users, but using a global counter is a quick and easy way to count the amount of function calls.
function_calls = 0
def fibonacci_recursive(n):
global function_calls
function_calls += 1
if n == 0:
return 1
elif n == 1:
return 1
else:
return fibonacci_recursive(n-1) + fibonacci_recursive(n-2)
ans = fibonacci_recursive(20)
print("There were",function_calls,"function calls.")

Variable Scope Issue in Python

I am new to Python and I have been working with it for a bit, but I am stuck on a problem. Here is my code:
def collatz(num,ctr):
if(num != 1):
ctr+=1
if(num%2==0):
collatz(num/2,ctr)
else:
collatz(num*3+1,ctr)
return ctr
test=collatz(9,0)
For any number I put in for num, let's say 9 for instance, and 0 for ctr, ctr always comes out as 1. Am I using the ctr variable wrong?
EDIT:
I am trying to print out how many times the function is recursed. So ctr would be a counter for each recursion.
The variable ctr in your example will always be 1 because of the order of the recursive call stack. As one value of ctr is returned, then the call stack will start returning the previous values of ctr. Basically, at the very last recursive call, the highest value of ctr will be returned. But since the method call at the bottom of the call stack returns the very last value aka the value that will be stored in test, test will always be 1. Let's say I input parameters into collatz that would result in five total calls of the method. The call stack would look like this coming down,
collatz returns ctr --> 5
collatz returns ctr --> 4
collatz returns ctr --> 3
collatz returns ctr --> 2
collatz returns ctr --> 1 //what matters because ctr is being returned with every method call
As you can see, no matter how many times collatz is called, 1 will always be returned because the call at the bottom of the call stack has ctr equaling 1.
The solution can be a lot of things, but it really depends on the purpose of what you're trying to accomplish which isn't clearly stated in your question.
EDIT: If you want ctr to end up being the number of times a recursive call is made, then just assign ctr to the value of the method call. It should look like this,
def collatz(num,ctr):
if(num != 1):
ctr+=1
if(num%2==0):
ctr = collatz(num/2,ctr)
else:
ttr = collatz(num*3+1,ctr)
return ctr
test=collatz(9,0)
I changed your recursive calls to set the value received back from the recursive calls into ctr. The way you wrote it, you were discarding the values you got back from recursing.
def collatz(num,ctr):
if(num != 1):
ctr+=1
if(num%2==0):
ctr=collatz(num/2,ctr)
else:
ctr=collatz(num*3+1,ctr)
return ctr
test=collatz(9,0)
An example:
def collatz(number):
if number % 2 == 0:
print(number // 2)
return number // 2
elif number % 2 == 1:
result = 3 * number + 1
print(result)
return result
n = input("Give me a number: ")
while n != 1:
n = collatz(int(n))
Function parameters in Python are passed by value, not by reference. If you pass a number to a function, the function receives a copy of that number. If the function modifies its parameter, that change will not be visible outside the function:
def foo(y):
y += 1
print("y=", y) # prints 11
x = 10
foo(x)
print("x=", x) # Still 10
In your case, the most direct fix is to make ctr into a global variable. Its very ugly because you need to reset the global back to 0 if you want to call the collatz function again but I'm showing this alternative just to show that your logic is correct except for the pass-by-reference bit. (Note that the collatz function doesn't return anything now, the answer is in the global variable).
ctr = 0
def collatz(num):
global ctr
if(num != 1):
ctr+=1
if(num%2==0):
collatz(num/2)
else:
collatz(num*3+1)
ctr = 0
collatz(9)
print(ctr)
Since Python doesn't have tail-call-optimization, your current recursive code will crash with a stack overflow if the collatz sequence is longer than 1000 steps (this is Pythons default stack limit). You can avoid this problem by using a loop instead of recursion. This also lets use get rid of that troublesome global variable. The final result is a bit more idiomatic Python, in my opinion:
def collats(num):
ctr = 0
while num != 1:
ctr += 1
if num % 2 == 0:
num = num/2
else:
num = 3*num + 1
return ctr
print(collatz(9))
If you want to stick with using recursive functions, its usually cleaner to avoid using mutable assignment like you are trying to do. Instead of functions being "subroutines" that modify state, make them into something closer to mathematical functions, which receive a value and return a result that depends only on the inputs. It can be much easier to reason about recursion if you do this. I will leave this as an exercise but the typical "skeleton" of a recursive function is to have an if statement that checks for the base case and the recursive cases:
def collatz(n):
if n == 1:
return 0
else if n % 2 == 0:
# tip: something involving collatz(n/2)
return #???
else:
# tip: something involving collatz(3*n+1)
return #???
The variable will return the final number of the collatz sequence starting from whatever number you put in. The collatz conjecture says this will always be 1

Calculate the sum of the digits of a number recursively in Python

I'm having troubles with a recursive function in Python. The objective is for the function to calculate the sum of the digits of a number recursively.
This is what I have so far -- I realise that this version isn't as succinct as it could be, but right now I'm just trying to understand why it isn't working as is:
total = 0 #global variable declaration
def digit_sum(n):
global total #to be able to update the same variable at every level of recursion
total += n % 10 #adding the last digit to the total
n //= 10 #removing the last digit of the number
if n < 10:
total += n
return total
else:
digit_sum(n)
print 'The return value of the function is: ', digit_sum(12345)
print 'The final value stored in total is: ', total
I obtain the following output:
The return value of the function is: None
The final value stored in total is: 15
My function is somewhat working, since the final value stored in the global variable total is correct, but printing the function output returns None instead of 15.
Could you please help me understand why?
Thank you.
Interesting problem, and an interesting solution! Let me debug with a more simple number - 421.
On first call, total is assigned the value 1 and n becomes 42. The else branch gets executed.
On second call, total gets value of 3 and n becomes 4. The if branch is executed and the value total = 7 is returned.
So, why are we seeing the None? Let's inspect the call-stack:
> digit_sum(n = 421)
> > digit_sum(n = 42) # call to digit_sum from inside digit_sum
> -< 7 # value returned by inner/second call
> None
As you can notice, the value being returned by the second call is received by the first call, but the first call doesn't return the value being returned by the second call, so that's why you are seeing None.
But why does't first call return the value being returned by the second call?
Because of this line:
else:
digit_sum(n)
You are calling the function a second time, but you are not returning its return value.
Hope it helps! :)
The problem is that you didn't add a return statement in your else clause.
Adding 'return digit_sum(n)' should solve your problem:
if n < 10:
total += n
return total
else:
return digit_sum(n)
Example
When you have a recursive function (I'll take n! as example), calls are made until you reach a 'base case' (2 in n! and for you if n<10).
Let's take a look at factorial:
def fact(n):
if(n<=2):
return n
else:
return n*fact(n-1)
Without the return statement in else clause, if you ask for fact(4), this will also return none.
Here are the 'calls' with the return statement:
return (4*fact(3))
return (4*(3*fact(2)))
return (4*(3*(2)))
Which gives 24.
Here are those without:
(4*fact(3))
(4*(3*fact(2)))
(4*(3*(2)))
So the calculus is made, but nothing is returned.
I hope this will help you to understand.
NB: Here is a factorial implementation where recursivity is explained.
my solution is
def f(n):
if n/10 == 0:
return n
return n%10 + f(n/10)
output:
f(12345) = 15

Project Euler #4 with python. What;s wrong with my code?

I am trying to do project euler problem 4 using python. The problem statement goes like this:
A palindromic number reads the same both ways. The largest palindrome made from the product of two 2-digit numbers is 9009 = 91 × 99.
Find the largest palindrome made from the product of two 3-digit numbers.
I wrote down a solution for it:
s=0
x=100
y=100
list=[]
z=x*y
def palindrome():
while z>=1:
s=s*10+z%10
z=z/10
if z==s:
list.append(z)
while x<=999:
while y<=999:
palindrome()
y=y+1
x=x+1
y=100
print list
It ended up giving an error along the lines of 'z referenced beyond assignment'.
I searched for a solution to this error before finally deciding to use the syntax 'global' to bypass this error.
s=0
x=100
y=100
list=[]
z=x*y
def palindrome():
global z
global s
global x
global y
global list
while z>=1:
s=s*10+z%10
z=z/10
if z==s:
list.append(z)
while x<=999:
while y<=999:
palindrome()
y=y+1
x=x+1
y=100
print list
Now it doesn't give an error, but it gives an empty list as output. I tried to debug the code by inserting print statements in between. The loops appear to work fine, as 'x' and 'y' print all the values they are supposed to. However, I get an empty list as an output to the print list command and 'z' does not apparently change values and is stuck at 100000 despite me using while loops to change the values of x and y.
I am at a loss on how to proceed from here.
The error you got was probably:
UnboundLocalError: local variable 'z' referenced before assignment
This means that z was not defined, at least not within the palindrome() function. Your solution of adding the global keyword is technically correct. However, as others have pointed out already, use of globals makes the code hard to follow.
It's not clear to me what palindrome() is supposed to do. Is it supposed to check if a number is a palindrome? Generate palindrome numbers? To fix this problem, you should think about structuring your code. There are many ways to do this, of course, and with time you will find your own style.
My advice, then, is to think about how you would solve this in general. If you don't know the solution, coding won't help you. Sometimes, when solving problems like this one, I write functions without declaring their bodies. You can do this top-down or bottom-up, both work. For example:
def is_palindrome(n):
""" Check if n is a palindrome number. """
pass
def multiples_of_3_digits():
""" Return all numbers that are the product of two 3-digit numbers ."""
pass
def main():
print max(n for n in multiples_of_3_digits() if is_palindrome(n))
This way you can focus on solving the problem, then on the actual coding. Maybe you will add helper functions or realize you can solve the problem in a more efficient way, but it's a start. Good luck!
min=100
max=999
max_palindrome = 0
for a in range(min,max + 1):
for b in range(a + 1, max + 1):
prod = a*b
if prod > max_palindrome and str(prod)==(str(prod)[::-1]):
max_palindrome = prod
print max_palindrome
Here we are only concerned with the maximum palindrome, and so we don’t spend any time storing other palindromes once they are known to be non-maximum. Also, the if statement first checks if the given product is larger than the maximum known palindrome before using the string cast and list slice to check whether or not the number is even a palindrome. This should speed up our code a bit since the greater than comparison will often fail, regardless of whether the product in question is a palindrome. When we run this, we get the following.
906609
Alternate Way:
I would discourage you to use the global variables because of the reasons pointed out by others. I would also like you to refer to Andre's approach as it will teach you to organize yourself. In this approach too I will be using 2 functions is_palindrome(num) [to check if the number is palindrome or not] and find_max_palindrome [to find the largest palindrome]
def is_palindrome(num):
reversed = 0
original = num
if num < 10:
return True
if num % 10 == 0:
return False
while num >= 1:
reversed = (reversed * 10) + (num % 10)
num = num/10
if original == reversed:
return True
else:
return False
def find_max_palindrome():
max_palindrome = 0
a = 999
b = 999
prod = 0
while a > 99:
b = 999
while b >= a:
prod = a*b
if prod > max_palindrome and is_palindrome(prod):
max_palindrome = prod
b = b -1
a = a - 1
return max_palindrome
print find_max_palindrome()

How do I double my step variable for each for-loop iteration in Python 3?

I'm new to Python, and I'm playing around with recursive functions just for practice.
I made the following algorithm which takes a number as x and halves it until it is equal to 1. n is the number of times x has been halved.
def binary(x, n = 0):
print(n,":",x)
x = x // 2
n += 1
if x > 0:
binary(x, n)
return x
return x
I'm trying to make a loop that will call binary() with multiple values for x. I want my step to be doubled each iteration. I have it working with a while loop like the one below.
i = 1
while i < 1000000000:
print("when x is", i, ":")
binary(i)
i += i
For some reason though, I can't seem to achieve the same thing with a For loop. Here's what I have now.
for i in range(1,1000):
print("when x is", i, ":")
binary(i)
i += i
In the code above, i += i does not seem to effect the i in my header. I know that range() takes a third parameter called step, but I've tried this:
for i in range(1,1000, i += i):
# statements
This gives me a name error, and says "i is not defined".
Most of my experience with programming is in JavaScript and C#. In both of those languages I wouldn't of had any trouble doing this.
How would I get this to work in a For loop using Python 3?
The third parameter of range is indeed step. But you should use it with a computed value like:
for i in range(1,1000,2):
#statements
The reason why your i += i didn't worked is because, under the hood, the for-loop is executing something similar to i = next(...) at the end of an iteration, overiding your last increment.
Edit
You can achieve the desired effect using a generator, but it kinda kills the "trying to avoid while-loops" thing:
def myrange(start, stop):
i = start
while i < stop:
yield i
i += i
for i in myrange(1, 1000):
print(i)
Anyway, while-loops are perfectly valid constructs and I’d personnally go with one in this case. Do not forget that the for-loop has a rather different semantic in python than in both languages you’re used to. So trying to use a for-loop because you are able to do so with javascript seems like a bad idea if all what you need is a while-loop.
range can step by a fixed amount, but not a variable amount. Use a while-loop to increment i by i:
i += i
You could replace the while-loop with an iterator, such as:
import itertools as IT
for i in (2**i for i in IT.count()):
if i >= 1000000000: break
print("when x is", i, ":")
binary(i)
but I don't think this has any advantage over a simple while-loop.
If all you're doing is doubling i, then why not just raise it to the power?
for p in range(int(1000000000**0.5)):
print(binary(2**p)

Categories

Resources