Issues with recursive functions when function is called the second time around - python

I'm trying to create a function that is similar to a shopping list where the function asks the users for 2 inputs, the first one being of the item and the second one being the number of said item the user wants, the only caveat being that the total number of items needs to be exactly 10.
def manualInput():
items = []
noitems = []
x = 1
while sum(noitems) != 10 and sum(noitems) < 10:
try:
i = str(input(f'Item {x}: '))
noi = int(input(f'No. of {i}: '))
items.append(i)
noitems.append(noi)
x = x+1
except ValueError:
print('Check inputs and try again')
items.clear()
noitems.clear()
manualInput()
if sum(noitems) > 10:
print('Number of items need to be exactly 10. Try again')
items.clear()
noitems.clear()
manualInput()
return items, noitems
Now this function works perfectly fine if the user does it perfectly the first time around. However if a ValueError is thrown or the sum of the number of items is greater than 10, the lists always return as empty lists despite the function asking the users for their inputs again.
For example if the user wants to type in Oranges and Apples and wants 6 and 4 of each respectively and manages to input all the information correctly, the first time around, the function returns (['Oranges', 'Apples'], [6, 4]) which is what I want.
In the event of an error however, lets say the user accidently inputs Oranges Apples 6 5 , the function does print out Number of items need to be exactly 10. Try again but when the user inputs all the information again, correctly this time, the function does stop but returns ([], []).
I've tried taking out the item.clear() and noitems.clear() commands but this just results in the function returning (['Oranges', 'Apples'], [6, 5])
So to sum it up I don't know why my lists aren't being appended the second time around when I call the function again in the event of an error. It was my understanding that when u call a function within a function it sort of acts like a loop where the function is run again from the top.

It seems you have not understood recursion and the advantage of it.
Your function creates everytime it gets called a new empty items and noitems list. That's why it is impossible to "remember" the last state.
You have to pass them as function arguments - so that the different rounds of calls can share the state of these two lists.
Second, the while loop is an imperative programming construct - recursion replaces the loop - or should replace it. So you have to get rid of it.
Third, naming in programming is one of the most critical and hardest thing.
noitems tells a reader: "there are no items" - but what you mean are 'Number of items" - call it num_items avoid misinterpretable names.
Fourth, programming is communication. Communication requires that you follow conventions of the peer group with/to which you communicate. Writing Python code means you address your code - besides to the machines especially - to the community of people writing Python code (and who have to maintain the machines and your code). - Be it your successor at work, your colleagues, or your instructor in the course. This means: Always follow the Python code conventions and guidelines (style guide https://peps.python.org/pep-0008/ - in other languages - usually the google style guide for that language).
The style guide says: You don't name in python in camelCase, but in snake_case.https://peps.python.org/pep-0008/
So call it manual_input() - not manualInput() (camelCase is JavaScript and Java convention).
def manual_nput(items = [], num_items = []):
if sum(num_items) == 10: # this is the recursion exit condition
return items, num_items # culminating in returning the result
elif sum(num_items) < 10: # these are the different cases
item = str(input(f'Item {len(items)}: '))
num_item = int(input(f'No. of {item}: '))
return manual_input(items + [item], num_items + [num_item])
# they usually end in a recursive call of the function
# with updated arguments
else: # the number of items is > 10
# in this case remove the last item and call the manual_input again
print('Number of items need to be exactly 10. Try again')
return manual_input(items[:-1], num_items[:-1])
# this [:-1] removes the last input and puts the state of
# items and num_items back to the beginning of the previous call

Related

Nothing is output in python recursive function

List item
I'm working on a code that calculates the 'distance' between two configurations of a Flip Cube, The distance between two configurations x and y is the minimum number of steps required to go from x to y, or conversely.
To make that I've created a simpler version that makes something different, this code takes two integer numbers ci and cf. with ci returns an iterable called main_level through the generator called multi, then, it iterates through it searching for the parameter cf, whenever cf is not in main_level the variable steps is increased by 1 and for each element in main_level we repeat the same process done for ci. Finally, when cii==cf the program ends and returns the steps variable, which counts the number of "levels" that we have to go down to find the given parameter cf. This code doesn't have any practical purpose is just a base for the problem I mentioned above.
If I call the distance(ci, cf) function with ci=5, the first two levels are:
{0,3,6,9,12} <-- first level (steps is initialized with 1)
if cf is any of the numbers in the set, the program should end and return steps=1,
if cf is not in that set, the programs form the second level:
{15,18,21,24,27,30,33} and search cf, if cf is there, the program ends and should return steps=2, if not, it forms the third level, and so on. But there is a problem, actually, when I call the distance function with ci=5 and cf= any natural number, and print its value, anything is output, only for cf=0, it outputs step=1. I don't really know what's going on. I would appreciate your help.
Here is the code:
#Base solution to FlipCube problem
def multi(par):
for i in range(par):
yield i*3
steps=1
def distance(ci,cf):
main_level =set(multi(ci))
global steps
def check_main_level(cf):
global steps
nonlocal main_level
def lower_level(config_list):
sett=set()
for i in config_list:
sett.update(q for q in multi(i) if q not in config_list)
nonlocal main_level
main_level=sett
check_main_level(cf)
for i in main_level:
if i==cf:
break
else:
steps+=1
lower_level(main_level)
check_main_level(cf)
return steps
#testing
e= distance(5,0)
print(e)# prints 1, very good
e2= distance(5,9)
print(e2)# should print 1, but doesn't print anything :(
e3= distance(5,27)
print(e3)# should print 2, but doesn't print anything :(
The program does not terminate recursion under all circumstances. The culprit seems to be the for loop in check_main_level. Change the code after your definition of lower_level to:
# code portion of check_main_level
if cf > max(main_level):
steps+=1
lower_level(main_level)
# end of code portion check_main_level (replacing for-loop)
you have an infinity loop, that's why nothing is printed.
You can see it easyly by adding a print :
for i in config_list:
print(i)
sett=set()
sett.update(q for q in list(multi(i)) if q not in config_list)

Curious about effect of recursion of a method

I have written an instance method which uses recursion to find a certain solution. It works perfectly fine except the time when I'm exiting the if-elif block. I call the function itself inside IF block. Also, I have only one return statement. The output from the method is weird for me to understand. Here is the code and the output:
def create_schedule(self):
"""
Creates the day scedule for the crew based on the crew_dict passed.
"""
sched_output = ScheduleOutput()
assigned_assignements = []
for i in self.crew_list:
assigned_assignements.extend(i.list_of_patients)
rest_of_items = []
for item in self.job.list_of_patients:
if item not in assigned_assignements:
rest_of_items.append(item)
print("Rest of the items are:", len(rest_of_items))
if len(rest_of_items) != 0:
assignment = sorted(rest_of_items, key=lambda x: x.window_open)[0]
# print("\nNext assignment to be taken ", assignment)
output = self.next_task_eligibility(assignment, self.crew_list)
if len(output) != 0:
output_sorted = sorted(output, key=itemgetter(2))
crew_to_assign = output_sorted[0][1]
assignment.eta = output_sorted[0][4]
assignment.etd = int(assignment.eta) + int(assignment.care_duration)
crew = next((x for x in self.crew_list if x.crew_number == crew_to_assign), None)
self.crew_list.remove(crew)
crew.list_of_patients.append(assignment)
crew.time_spent = assignment.etd
self.crew_list.append(crew)
self.create_schedule()
else:
print("*" * 80, "\n", "*" * 80, "\nWe were not able to assign a task so stopped.\n", "*" * 80, "\n", "*" * 80)
sched_output.crew_output = self.crew_list
sched_output.patients_left = len(rest_of_items)
elif not rest_of_items:
print("Fully solved.")
sched_output.crew_output = self.crew_list
sched_output.patients_left = 0
print("After completely solving coming here.")
return sched_output
This was the output:
Rest of the items are: 10
Rest of the items are: 9
Rest of the items are: 8
Rest of the items are: 7
Rest of the items are: 6
Rest of the items are: 5
Rest of the items are: 4
Rest of the items are: 3
Rest of the items are: 2
Rest of the items are: 1
Rest of the items are: 0
Fully solved.
After completely solving coming here.
After completely solving coming here.
After completely solving coming here.
After completely solving coming here.
After completely solving coming here.
After completely solving coming here.
After completely solving coming here.
After completely solving coming here.
After completely solving coming here.
After completely solving coming here.
After completely solving coming here.
What I don't understand is that as soon as my list rest_of_items is empty, I assign data to sched_output and return it. However, print statement is being executed for the same number of time as recursion was done. How can I avoid this?
My output is perfectly fine. All I want to do is understand the cause of this behaviour and how to avoid it.
The reason it's printing out 11 times is that you always call print at the end of the function, and you're calling the function 11 times. (It's really the same reason you get Rest of the items are: … 11 times, which should be a lot more obvious.)
Often, the best solution is to redesign things so instead of doing "side effects" like print inside the function, you just return a value, and the caller can then do whatever side effects it wants with the result. In that case, it doesn't matter that you're calling print 11 times; the print will only happen once, in the caller.
If that isn't possible, you can change this so that you only print something when you're at the top of the stack. But in many recursive functions, there's no obvious way to figure that out without passing down more information:
def create_schedule(self, depth=0):
# etc.
self.create_schedule(depth+1)
# etc.
if not depth:
print('After completely solving come here.')
returns sched_output
The last resort is to just wrap the recursive function, like this:
def _create_schedule(self):
# etc.
self._create_schedule()
# etc.
# don't call print
return sched_output
def create_schedule(self):
result = self._create_schedule()
print('After completely solving come here.')
return result
That's usually only necessary when you need to do some one-time setup for the recursive process, but here you want to do some one-time post-processing instead, which is basically the same problem, so it can be solved the same way.
(Of course this is really just the first solution in disguise, but it's hidden inside the implementation of create_schedule, so you don't need to change the interface that the callers see.)
As you call your create_schedule function within itself before the function finishes, once it has gotten to the end and doesn't need to call itself again, each function ends, and hits the "After completely solving coming here.", at the end of the function.
This means that each function, after calling itself, is still running - just stuck at the line where it calls itself - until they have all completed, which is when the paused functions can finish their task, printing out your statement.
You have print("After completely solving coming here.") at the end of your recursive function. That line will be executed once for each recursion.
Consider this simple example, which recreates your issue:
def foo(x):
print("x = {x}".format(x=x))
if x > 1:
foo(x-1)
print("Done.")
Now call the function:
>>> foo(5)
x = 5
x = 4
x = 3
x = 2
x = 1
Done.
Done.
Done.
Done.
Done.
As you can see, on the final call to foo(x=0), it will print "Done.". At that point, the function will return to the previous call, which will also print "Done." and so on.

How to match input with elements in list/dictionary in Python3

I'm very new at coding, and I'm trying to create a shop list with items and prices on it.
That is, once typed in all the items, the function should calculate the sum and stop the moment you exceed the budget.
So I wrote something like:
def shoplist():
list={"apple":30, "orange":20, "milk":60......}
buy=str(input("What do you want to purchase?")
If buy in list:
While sum<=budget:
sum=sum+??
shoplist ()
I really don't know how to match the input of an item with the price in the list...
My first thought is to use 'if', but it's kinda impractical when you have more than 10 items on the list and random inputs.
I'm in desperate need of help....So any suggestions would be nice!! (or if you have a better solution and think me writing it this way is complete garbage... PLEASE let me know what those better solutions are😭😭😭
The code you post will not run in python. list is a builtin and should not be used for a variable name, and is doubly confusing since it refers to a dict object here. input() already returns a str so the cast has no effect. if and while should be lowercase, and there is no indentation, so we have no way of knowing the limits of those statements.
There are so many things wrong, take a look at this:
def shoplist(budget):
prices = {"apple":30, "orange":20, "milk":60}
# Initialise sum
sum = 0
while sum <= budget:
buy = input("What do you want to purchase?")
# Break out of the loop if the user hts <RETURN>
if not buy: break
if buy in prices:
sum += prices[buy] # This gets the price
else:
print("Invalid item", buy)
shoplist(142)
So what have I changed? The budget has to come from somewhere, so I pass it in as a parameter (142, I made that up). I initialise the sum to zero, and I moved the while loop to the outside.
Notice as well lots of whitespace - it makes the code easier to read and has no effect on performance.
Lots of improvements to make. The user should be shown a list of possible items and prices and also how much budget there is left for each purchase. Note as well that it is possible to go over budget since we might only have 30 in the budget but we can still buy milk (which is 60) - we need another check (if statement) in there!
I'll leave the improvements to you. Have fun!
Take a look at this as an example:
# this is a dictionary not a list
# be careful not using python reserved names as variable names
groceries = {
"apple":30,
"orange":20,
"milk":60
}
expenses = 0
budget = 100
cart = []
# while statements, as well as if statements are in lower letter
while expenses < budget:
# input always returns str, no need to cast
user_input = input("What do you want to purchase?")
if user_input not in groceries.keys():
print(f'{user_input} is not available!')
continue
if groceries[user_input] > budget - expenses:
print('You do not have enough budget to buy this')
user_input = input("Are you done shopping?Type 'y' if you are.")
if user_input == 'y':
break
continue
cart.append(user_input)
# this is how you add a number to anotherone
expenses += groceries[user_input]
print("Shopping cart full. You bought {} items and have {} left in your budget.".format(len(cart), budget-expenses))
I've made some changes to your code to make it work, with explanation including using comments indicated by the # symbol.
The two most important things are that all parentheses need to be closed:
fun((x, y) # broken
fun((x, y)) # not broken
and keywords in Python are all lowercase:
if, while, for, not # will work
If, While, For, Not # won't work
You might be confused by True and False, which probably should be lowercase. They've been that way so long that it's too late to change them now.
budget = 100 # You need to initialize variables before using them.
def shoplist():
prices = { # I re-named the price list from list to prices
'apple' : 30, # because list is a reserved keyword. You should only
'orange' : 20, # use the list keyword to initialize list objects.
'milk' : 60, # This type of object is called a dictionary.
} # The dots .... would have caused an error.
# In most programming languages, you need to close all braces ().
# I've renamed buy to item to make it clearer what that variable represents.
item = input('What do you want to purchase? ')
# Also, you don't need to cast the value of input to str;
# it's already a str.
if item in prices:
# If you need an int, you do have to cast from string to int.
count = int(input('How many? '))
cost = count*prices[item] # Access dictionary items using [].
if cost > budget:
print('You can\'t afford that many!')
else:
# You can put data into strings using the % symbol like so:
print('That\'ll be %i.' % cost) # Here %i indicates an int.
else:
print('We don\'t have %s in stock.' % item) # Here %s means str.
shoplist()
A lot of beginners post broken code on StackOverflow without saying that they're getting errors or what those errors are. It's always helpful to post the error messages. Let me know if you have more questions.

Using return instead of yield

Is return better than yield? From what ive read it can be. In this case I am having trouble getting iteration from the if statement. Basically what the program does is take two points, a begin and end. If the two points are at least ten miles apart, it takes a random sample. The final if statement shown works for the first 20 miles from the begin point, begMi. nCounter.length = 10 and is a class member. So the question is, how can I adapt the code to where a return statement would work instead of a yield? Or is a yield statement fine in this instance?
def yielderOut(self):
import math
import random as r
for col in self.fileData:
corridor = str(col['CORRIDOR_CODE'])
begMi = float(col['BEGIN_MI'])
endMi = float(col['END_MI'])
roughDiff = abs(begMi - endMi)
# if the plain distance between two points is greater than length = 10
if roughDiff > nCounter.length:
diff = ((int(math.ceil(roughDiff/10.0))*10)-10)
if diff > 0 and (diff % 2 == 0 or diff % 3 == 0 or diff % 5 == 0)\
and ((diff % roughDiff) >= diff):
if (nCounter.length+begMi) < endMi:
vars1 = round(r.uniform(begMi,\
(begMi+nCounter.length)),nCounter.rounder)
yield corridor,begMi,endMi,'Output 1',vars1
if ((2*nCounter.length)+begMi) < endMi:
vars2 = round(r.uniform((begMi+nCounter.length),\
(begMi+ (nCounter.length*2))),nCounter.rounder)
yield corridor,begMi,endMi,'Output 2',vars1,vars2
So roughdiff equals the difference between two points and is rounded down to the nearest ten. Ten is then subtracted so the sample is taken from a full ten mile section; and that becomes diff. So lets say a roughDiff of 24 is rounded to 20, 20 - 10, diff + begin point = sample is taken from between mi 60 and 70 instead of between 70 and 80.
The program works, but I think it would be better if I used return instead of yield. Not a programmer.
return is not better, it's different. return says "I am done. Here is the result". yield says "here is the next value in a series of values"
Use the one that best expresses your intent.
Using yield makes your function a generator function, which means it will produce a series of values each time its (automatically created) next() method is called.
This is useful when you want to process things iteratively because it means you don't have to save all the results in a container and then process them. In addition, any preliminary work that is required before values can generated only has to be done once, because the generator created will resume execution of your code following the that last yield encountered β€” i.e. it effectively turns it into what is called a coroutine.
Generator functions quit when they return a value rather than yield one. This usually happens when execution "falls off the end" when it will return None by default.
From the looks of your code, I'd say using yield would be advantageous, especially if you can process the results incrementally. The alternative would be to have it store all the values in a container like a list and return that when it was finished.
I use yield in situations where I want to continue iteration on some object. However, if I wanted to make that function recursive, I'd use return.

Python Min-Max Function - List as argument to return min and max element

Question: write a program which first defines functions minFromList(list) and maxFromList(list). Program should initialize an empty list and then prompt user for an integer and keep prompting for integers, adding each integer to the list, until the user enters a single period character. Program should than call minFromList and maxFromList with the list of integers as an argument and print the results returned by the function calls.
I can't figure out how to get the min and max returned from each function separately. And now I've added extra code so I'm totally lost. Anything helps! Thanks!
What I have so far:
def minFromList(list)
texts = []
while (text != -1):
texts.append(text)
high = max(texts)
return texts
def maxFromList(list)
texts []
while (text != -1):
texts.append(text)
low = min(texts)
return texts
text = raw_input("Enter an integer (period to end): ")
list = []
while text != '.':
textInt = int(text)
list.append(textInt)
text = raw_input("Enter an integer (period to end): ")
print "The lowest number entered was: " , minFromList(list)
print "The highest number entered was: " , maxFromList(list)
I think the part of the assignment that might have confused you was about initializing an empty list and where to do it. Your main body that collects data is good and does what it should. But you ended up doing too much with your max and min functions. Again a misleading part was that assignment is that it suggested you write a custom routine for these functions even though max() and min() exist in python and return exactly what you need.
Its another story if you are required to write your own max and min, and are not permitted to use the built in functions. At that point you would need to loop over each value in the list and track the biggest or smallest. Then return the final value.
Without directly giving you too much of the specific answer, here are some individual examples of the parts you may need...
# looping over the items in a list
value = 1
for item in aList:
if item == value:
print "value is 1!"
# basic function with arguments and a return value
def aFunc(start):
end = start + 1
return end
print aFunc(1)
# result: 2
# some useful comparison operators
print 1 > 2 # False
print 2 > 1 # True
That should hopefully be enough general information for you to piece together your custom min and max functions. While there are some more advanced and efficient ways to do min and max, I think to start out, a simple for loop over the list would be easiest.

Categories

Resources