How to exit recursion when condition is met? - python

I am trying to write a function which prints the minimum number of steps involved to make starting number to target number with multiply by 2 and subtract by 1.
However, I am getting the error:
RecursionError: maximum recursion depth exceeded in comparison
Here's the code I've written:
def no_steps(start,target,sofar):
if start == target:
return sofar
elif start < target and start > 0:
no_steps(start*2,target,sofar+'*2 ')
no_steps(start-1,target,sofar+'-1 ')
print(no_steps(2,6,''))
May I know what am I doing wrong and is there any issue with the code?

My interpretation is: starting from 2 (start), which sequence of multiplications by 2 and subtractions by 1 will lead to 6 (target) with the least amount of such operations, meaning not exactly always in that order.
On the one side you have a recursion prob (which is what you see in your output) due to missing implementation of correct return statements. On the other, there is also some logic missing to achieve the least amount of computation steps. My combined solution (for the recursion and the minimum num of steps) would be:
def no_steps(start, target, sofar):
if start == target:
return sofar
if 0 < start and 0 < target:
if (target % 2 == 0) and target > start:
return no_steps(start, target // 2, '*2 ' + sofar)
else:
return no_steps(start, target + 1, '-1 ' + sofar)
return sofar
no_steps(1, 6, '')
'*2 *2 -1 *2 '
no_steps(2, 6, '')
'*2 -1 *2 '
no_steps(3, 6, '')
'*2 '
no_steps(4, 6, '')
'-1 *2 '
no_steps(5, 6, '')
'-1 -1 *2 '
no_steps(6, 6, '')
''
no_steps(7, 6, '')
'-1 '
no_steps(2, 17, '')
'*2 -1 *2 -1 *2 -1 *2 -1 '
EDIT: fixed the previously flawed logic based on the answer by PabloRuiz.

Don't use recursion for this problem, I came up with a much better solution reversing the problem:
def no_steps(start, target, sofar=''):
while target != start:
if target%2 == 0 and target > start:
target //= 2
sofar = '*2' + sofar
else:
target +=1
sofar = '-1' + sofar
print(sofar)
no_steps(2, 17, '')
Note that it assumes that target is bigger than start, if is not just change the order of them to be so.
PD from here a bad answer, but still the first one I posted:
You need to stop the recursion, you can do something like:
def no_steps(start,target, path='', it=0):
if it < 10 and not reached:
it+=1
if start == target:
print(f'REACHED! in {it} iterations with {path}')
elif start < 2*target and start > 0:
no_steps(start*2,target, path=path+'*2', it=it)
no_steps(start-1,target, path=path+'-2', it=it)
else:
print('Start going beyond target')
no_steps(2,6)
You can also add an infamous global variable to stop other branches to continue growing, or just design another algorithm.

You make a common mistake with recursion, i.e you assume that return will collapse all the calls in-between original call and the one that returns something - it won't.
Let's say you run your program for start = 3 and target = 6. no_steps(start*2,target, path=path+'*2', it=it) will return a final result - back to the start = 3 call. And then you ignore this result and call no_steps(start-1,target, path=path+'-2', it=it). Which will in turn call your function with start = 4 and 1 and so forth until infinity (or recursion limit, hence the error). Once you have some result, you need to return it all the way up to the original call.
Another problem in your logic is you want to check all the possible options but have no way of catching an infinite loop like 2->4->3->2->4...

Related

while loop in line 36 won't exit despite fulfilling condition

The outer while loop should exit when current_digit and powers[digit_of_powers] both equal 26 but the while-loop doesn't exit as it should.
As a proof of what I'm claiming, see for yourself, the last line never executes. The program is stuck in the stopped while loop.
I'm new to programming so I may do daft mistakes
#enter a number in base 10 and find how it's written in choosen base... or at least thats what it should do
import math
def find_powers(num, base, start_num): #stackoverflow experts, this function is not important for my problem
power = 1 # power starts as a '1' each iteration
while start_num + power <= num:
power *= base
if start_num != num:
power /= base
powers.append(round(math.log(power, base)))
find_powers(num, base, start_num + power)
num = 7892 #enter choosen number in base 10 here
base = 10 #enter choosen base here. I put ten because its much easier to check the program with a base I'm familiar with
powers = []
find_powers(num, base, 0)
print(powers)
print('')
check = 0 #this script is not necessary, it's just a way for me to check if it can find the input of the def find_powers function from its output.
for i in powers: # It if it does then the function still work
check += base**i
print(check)
print('')
string = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
digits = []
# Two things to check here
# 1. if powers[i] misses a digit or not.
# If it does, digits.append(0)
# 2. How many times the same power comes (it'll always be in row)
digit_of_powers = 0
while digit_of_powers != len(powers):
digit_of_powers += 1
print('digits_of_powers = ' + str(digit_of_powers))
current_digit = powers[digit_of_powers]
print('current_digit = ' + str(current_digit))
print('powers[digit_of_powers] = ' + str(powers[digit_of_powers]))
print('')
digit_thatll_go_in_digits = 1
while current_digit == powers[digit_of_powers]:
digit_thatll_go_in_digits += 1
digit_of_powers += 1
print('digit_thatll_go_in_digits = ' + str(digit_thatll_go_in_digits))
print('digits_of_powers = ' + str(digit_of_powers))
print(digits)
print('while ' + str(digit_of_powers) + ' != ' + str(len(powers)))
print('while ' + str(current_digit) + ' == ' + str(powers[digit_of_powers]))
print('')
digits.append(digit_thatll_go_in_digits)
print('fin du while')
print('')
print('this last line never executes for some reason')
You are increasing (conditionally) digit_of_powers twice, but condition is exact value while digit_of_powers != len(powers): (not equal)
It means that your code can jump over this condition and run further.
For example, if len(powers) == 10, digit_of_powers + 1 + 1 will increase from 9 to 11 and can be never the same.
Try this:
while digit_of_powers <= len(powers): (if it starts with 1)
or
while digit_of_powers < len(powers): (if it starts with 0)

Binary search in Python results in an infinite loop

list = [27 , 39 , 56, 73, 3, 43, 15, 98, 21 , 84]
found = False
searchFailed = False
first = 0
last = len(list) - 1
searchValue = int(input("Which number are you looking for? "))
while not found and not searchFailed:
mid = (first + last) // 2
if list[mid] == searchValue:
found = True
else:
if first >= last :
searchFailed = True
else:
if list[mid] > searchValue:
last = mid - 1
else:
last = mid + 1
if found:
print("Your number was found at location", mid)
else:
print("The number does not exist within the list")
The code runs properly when I execute it while searching for 27 (the first number), but any other number just results in an infinite loop.
I believe the loop runs smoothly on the first iteration since if I change the value of first to 1, the code correctly finds the position of 39 but repeats the infinite loop error with all the other numbers after that (while 27 "does not exist within the loop" which makes sense). So I suppose the value of mid is not getting updated properly.
Several points to cover here. First, a binary search needs sorted data in order to work. As your list is not sorted, weirdness and hilarity may ensue :-)
Consider, for example, the unsorted [27 , 39 , 56, 73, 3, 43, 15, 98, 21] when you're looking for 39.
The first midpoint is at value 3 so a binary search will discard the left half entirely (including the 3) since it expects 39to be to the right of that3. Hence it will never find 39`, despite the fact it's in the list.
If your list is unsorted, you're basically stuck with a sequential search.
Second, you should be changing first or last depending on the comparison. You change last in both cases, which won't end well.
Third, it's not usually a good idea to use standard data type names or functions as variable names. Because Python treats classes and functions as first-class objects, you can get into a situation where your bindings break things:
>>> a_tuple = (1, 2) ; a_tuple
(1, 2)
>>> list(a_tuple) # Works.
[1, 2]
>>> list = list(a_tuple) ; list # Works, unintended consequences.
[1, 2]
>>> another_list = list(a_tuple) # No longer works.
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'list' object is not callable
Covering those issues, your code would look something like this (slightly reorganised in the process):
my_list = [3, 15, 21, 27, 39, 43, 56, 73, 84, 98]
found = False
first, last = 0, len(my_list) - 1
searchValue = int(input("Which number are you looking for? "))
while not found:
if first > last:
break
mid = (first + last) // 2
if my_list[mid] == searchValue:
found = True
else:
if my_list[mid] > searchValue:
last = mid - 1
else:
first = mid + 1
if found:
print("Your number was found at location", mid)
else:
print("The number does not exist within the list")
That works, according to the following transcript:
pax> for i in {1..6}; do echo; python prog.py; done
Which number are you looking for? 3
Your number was found at location 0
Which number are you looking for? 39
Your number was found at location 4
Which number are you looking for? 98
Your number was found at location 9
Which number are you looking for? 1
The number does not exist within the list
Which number are you looking for? 40
The number does not exist within the list
Which number are you looking for? 99
The number does not exist within the list
First of all, do not use any reserved word (here list) to name your variables. Secondly, you have a logical error in the following lines:
if list[mid] > searchValue:
last = mid - 1
else:
last = mid + 1
In the last line of the above snippet, it should be first = mid + 1
There are very good answers to this question, also you can consider this simpler version adapted to your case:
my_list = [3, 15, 21, 27, 39, 43, 56, 73, 84, 98] # sorted!
left, right = 0, len(my_list) # [left, right)
search_value = int(input("Which number are you looking for? "))
while left + 1 < right:
mid = (left + right) // 2
if my_list[mid] <= search_value:
left = mid
else:
right = mid
if my_list[left] == search_value: # found!
print("Your number was found at location", left)
else:
print("The number does not exist within the list")
The problem with your function is that in Binary Search the array or the list needs to be SORTED because it's one of the most important principal of binary search, i made same function working correctly for you
#low is the first index and high is the last index, val is the value to find, list_ is the list, you can leave low as it is
def binary_search(list_: list, val,high: int, low: int = 0):
mid = (low+high)//2
if list_[mid] == val:
return mid
elif list_[mid] <= val:
return binary_search(list_, val, high+1)
elif list_[mid] >= val:
return binary_search(list_, val, high, low-1)
and now here's the output
>>> binary_search(list_, 21, len(list_)-1)
>>> 2
what will happen here is that first it will calculate the middle index of the list, which i think of your list is 5, then it will check whether the middle value is equal to the value given to search, and then return the mid index, and if the mid index is smaller than the value, then we will tell it to add one to high index, and we did the comparison with index and value because as i told you, list needs to be sorted and this means if index is greater or equal to the mid index then the value is also surely greater than the middle value, so now what we will do is that we will call the same function again but this time with a higher high which will increase the mid point and if this time middle index is equal to value, then its gonna return the value and going to do this untill mid is equal to value, and in the last elif it says if middle value is greater than value, we will call same function again but lower the low i.e which is 0 and now -1, which will reduce the mid point and this whole process will continue untill mid is equal to value

Identifying the recursion conditions for Python String

I have this string 0123456789 and I want to use recursion to create a method that returns
'09182736455463728190'
So basically the above says that first I get the first num from the left and then the first from the right, and add them to the string, then I get the second from the left and the second from the right, etc.
When I reach the middle, I start adding to the final string the values of the initial string, but now int the opposite order. So 546372, etc. So Starting from the edges I add first the most left and then the most right element.
Starting from the middle and moving to the edges, I favour the right side element first.
I cannot come up with the recursion relationship
Here it is. The base case is when the final string length is twice the original string length. This signals the end of recursion and returns the final string. The recursive step consists of advancing the start index forward and the end index backward on the original string:
def transform_sequence(original, final = '', start_index = 0, end_index = -1):
#base case:
if len(final) == 2* len(original):
return final
#recursion
final += original[start_index] + original[end_index]
return transform_sequence(original, final, start_index + 1, end_index - 1)
print(transform_sequence('0123456789'))
#output: 09182736455463728190
If you want to use recursion to handle this, you'd better try to solve it from top-down like this
def rec(nums):
if len(nums) == 0:
return ''
elif len(nums) == 1:
return nums * 2
else:
first = nums[0]
last = nums[-1]
return ''.join([first, last, rec(nums[1:-1]), last, first])
if __name__ == '__main__':
nums = '0123456789'
print(rec(nums)) # 09182736455463728190
nums = '012'
print(rec(nums)) # 021120
nums = '0'
print(rec(nums)) # 00
Fun problem and great way to learn about recursion. We can use inductive reasoning to ensure our program is valid for all input strings -
if the input s is empty, return the empty result
(inductive) s is not empty. get the first and last characters, a and b, and prepend/append them to the result of the sub-problem, s[1:-1]
We can easily encode this in python -
def first_and_last(s):
return (s[0], s[-1])
def puzzle(s):
if not s:
return "" # 1
else:
(a,b) = first_and_last(s) # 2
return a + b + puzzle(s[1:-1]) + b + a
We can see it working below -
print(puzzle("0123456789"))
print(puzzle("12345"))
09182736455463728190
152433334251
When then puzzle input is an odd length, notice how the middle character is repeated four times. When we reach the sub-problem puzzle("3"), a=3 and b=3, and the result of a + b + puzzle("") + b + a is 3333.
If you wish to handle this situation differently, we could modify puzzle
def puzzle(s):
if len(s) < 2: # <- if input is empty or singleton string
return s # <- return s
else:
(a,b) = first_and_last(s)
return a + b + puzzle(s[1:-1]) + b + a
print(puzzle("0123456789"))
print(puzzle("12345"))
09182736455463728190
152434251 # <-
The nice answer from Menglong Li presents another option for dealing with puzzle inputs of an odd length. I encourage you to see it :D
I'd take a different approach to this problem and assume the argument is a sequence rather than a str. This simplifies our logic, allowing us to pass str to the initial call, but the recursive calls can pass list:
def puzzle(sequence):
if len(sequence) > 1:
first, *middle, last = sequence
return first + last + puzzle(middle) + last + first
return sequence[0] if sequence else ""
if __name__ == '__main__':
print(puzzle("0123456789"))
print(puzzle("12345"))
print(puzzle("012"))
print(puzzle("0"))
OUTPUT
> python3 test.py
09182736455463728190
152434251
02120
0
>
If you approach this as processing the first character (placing it at the beginning and end) and recursing with the inverted remainder of the string then your exit condition will be an empty string:
def mirror(S):
return "" if not S else S[0]+mirror(S[:0:-1])+S[0]
print(mirror("0123456789")) # 09182736455463728190
If you want to avoid generating new (inverted) strings on every recursion, you could also implement it using an index that you carry along and map to alternating start/end relative positions:
def mirror(S,i=0):
j = (i+1)//2 * (-1)**i
return S[j]+mirror(S,i+1)+S[j] if i<len(S) else ""

If, else return else value even when the condition is true, inside a for loop

Here is the function i defined:
def count_longest(field, data):
l = len(field)
count = 0
final = 0
n = len(data)
for i in range(n):
count = 0
if data[i:i + l] is field:
while data[i - l: i] == data[i:i + l]:
count = count + 1
i = i + 1
else:
print("OK")
if final == 0 or count >= final:
final = count
return final
a = input("Enter the field - ")
b = input("Enter the data - ")
print(count_longest(a, b))
It works in some cases and gives incorrect output in most cases. I checked by printing the strings being compared, and even after matching the requirement, the loop results in "OK" which is to be printed when the condition is not true! I don't get it! Taking the simplest example, if i enter 'as', when prompted for field, and 'asdf', when prompted for data, i should get count = 1, as the longest iteration of the substring 'as' is once in the string 'asdf'. But i still get final as 0 at the end of the program. I added the else statement just to check the if the condition was being satisfied, but the program printed 'OK', therefore informing that the if condition has not been satisfied. While in the beginning itself, data[0 : 0 + 2] is equal to 'as', 2 being length of the "field".
There are a few things I notice when looking at your code.
First, use == rather than is to test for equality. The is operator checks if the left and right are referring to the very same object, whereas you want to properly compare them.
The following code shows that even numerical results that are equal might not be one and the same Python object:
print(2 ** 31 is 2 ** 30 + 2 ** 30) # <- False
print(2 ** 31 == 2 ** 30 + 2 ** 30) # <- True
(note: the first expression could either be False or True—depending on your Python interpreter).
Second, the while-loop looks rather suspicious. If you know you have found your sequence "as" at position i, you are repeating the while-loop as long as it is the same as in position i-1—which is probably something else, though. So, a better way to do the while-loop might be like so:
while data[i: i + l] == field:
count = count + 1
i = i + l # <- increase by l (length of field) !
Finally, something that might be surprising: changing the variable i inside the while-loop has no effect on the for-loop. That is, in the following example, the output will still be 0, 1, 2, 3, ..., 9, although it looks like it should skip every other element.
for i in range(10):
print(i)
i += 1
It does not effect the outcome of the function, but when debugging you might observe that the function seems to go backward after having found a run and go through parts of it again, resulting in additional "OK"s printed out.
UPDATE: Here is the complete function according to my remarks above:
def count_longest(field, data):
l = len(field)
count = 0
final = 0
n = len(data)
for i in range(n):
count = 0
while data[i: i + l] == field:
count = count + 1
i = i + l
if count >= final:
final = count
return final
Note that I made two additional simplifications. With my changes, you end up with an if and while that share the same condition, i.e:
if data[i:i+1] == field:
while data[i:i+1] == field:
...
In that case, the if is superfluous since it is already included in the condition of while.
Secondly, the condition if final == 0 or count >= final: can be simplified to just if count >= final:.

How to speed up this code with Cython?

def check(barcode):
if not len(barcode) == 13:
return False
digits = list(map(int, barcode))
# 1. Add the values of the digits in the
# even-numbered positions: 2, 4, 6, etc.
even_sum = digits[1] + digits[3] + digits[5] + digits[7] + digits[9] + digits[11]
# 2. Multiply this result by 3.
even_sum_three = even_sum * 3
# 3. Add the values of the digits in the
# odd-numbered positions: 1, 3, 5, etc.
odd_sum = digits[0] + digits[2] + digits[4] + digits[6] + digits[8] + digits[10]
# 4. Sum the results of steps 2 and 3.
total_sum = even_sum_three + odd_sum
# 5. The check character is the smallest number which,
# when added to the result in step 4, produces a multiple of 10.
next_ten = (math.ceil(total_sum / 10)) * 10
check_digit = next_ten - total_sum
# 6. if the check digit and the last digit of the
# barcode are OK return true;
if check_digit == digits[12]:
return True
else:
return False
I have the above code. It calculates the checksum of an ean number. I tried to speed it by using Cython. This code gets used in a loop. The variable barcode is a string.
But I failed to improve the speed. I tried:
np.array(list(map(int, barcode))) - That made it slightly slower
np.ceil() instead of math.ceil() - Also made it slightly slower
cdef bool def check(.... - Also did not help
What else could I do?
You could start by optimizing your function itself. Having a look at
# 5. The check character is the smallest number which,
# when added to the result in step 4, produces a multiple of 10.
next_ten = (math.ceil(total_sum / 10)) * 10
check_digit = next_ten - total_sum
you could also do something like next_ten = 10 - total_sum % 10 to get the check character. There you got rid of the math.ceil. As extra, this code also works in Python 2. So at the end, collapsing all your code, you could end up with just doing
def check(barcode):
if not len(barcode) == 13:
return False
return not (3*sum(map(int,barcode[1::2]))+sum(map(int,barcode[::2])))%10
Of course you could also use list comprehension or itertools, but in my testing there was not much difference to account for. But the "most" efficient way to do it, was not to use any fancy python programming technique at all:
def check(bc):
if not len(bc) == 13:
return False
return not (3*(int(bc[1])+int(bc[3])+int(bc[5])+int(bc[7])+int(bc[9])+int(bc[11]))+int(bc[0])+int(bc[2])+int(bc[4])+int(bc[6])+int(bc[8])+int(bc[10])+int(bc[12]))%10
A note at the end: With all my tests, Python 2 was faster.

Categories

Resources