python3 holding on to data after recursion - python-3.x

I wrote some code that found the fastest way of getting the sum of a number by adding numbers that were part of a list together ex:
bestSum(3, [800, 1, 3]) would return [3] because that would be the best way of getting 3 (first number) with the numbers provided would be simply adding 3. Code:
def bestSum(target, lst, mochi = {}):
if target in mochi:
return mochi[target]
if target == 0:
return []
if target < 0:
return None
shortestCombination = None
for i in lst:
remainderCombination = bestSum(target - i, lst, mochi)
if remainderCombination is not None:
remainderCombination = [*remainderCombination, i]
if shortestCombination is None or len(remainderCombination) < len(shortestCombination):
shortestCombination = remainderCombination
mochi[target] = shortestCombination
return shortestCombination
I ran into this issue where data would be saved between times I ran the code, for example if I run just
print(bestSum(8, [4])
It Returns
[4, 4]
However if I run
print(bestSum(8, [2, 3, 5]))
print(bestSum(8, [4]))
it returns:
[5, 3]
[5, 3]
Am I doing something wrong here? Is this potentially a security vulnerability? Is there a way to make this return correctly? What would cause python to do something like this?

This is documented behavior when using mutables as default arguments (see "Default parameter values are evaluated from left to right when the function definition is executed.").
As discussed in the documentation, "A way around this is to use None as the default, and explicitly test for it in the body of the function".
[While documented, I only learned about it here on SO a couple of days ago]

Related

Using Recursion to get lists of original list in Python returns error

I am given a task to use recursion so that any list that is given to it say p would be return in the desired output as shown at the end without disturbing or altering the contents of p. When I run this function it says that local variable (y) is referenced without assignment i.e. len(y)==0 but I have already assigned y outside the function init. Can someone help identify what's wrong?
y=[] #To copy contents of x in it so that x is not modified
z=[] # To return it as a list required in the question
p=[1,2,3]
def inits(x):
if len(y)==0 and len(z)==0:
y=[i.copy for i in x] #Copying contents of x into x so that it remains unchanged
if len(z)==len(x)+1: #z list is one plus x list because it has [] in it
print(z)
else:
z.append(y.copy())
if len(y)!=0: #This is done so that when y=[] it does not return error for y.pop(-1)
y.pop(-1)
inits(x)
inits(p)
#Desired Output
[[1,2,3],[1,2],[1],[]]
If you don't need the last empty element then feel free to remove the else block.
Your y variable is also not needed as for me
z = [] # To return it as a list required in the question
def foo(param):
if len(param):
z.append([i for i in param])
foo(param[0:-1])
else:
z.append([])
foo([1, 2, 3])
print(z)
[[1, 2, 3], [1, 2], [1], []]

Return terminates for loop

I am trying to define a following function that should return all odd numbers between two given numbers, however the return terminates the for loop such that only the first number is returned. How do I overcome this?
def oddNumbers(l,r):
L=[]
for i in range (l,r+1):
L.append(i)
for i in range (len(L)):
if L[i] % 2 !=0:
return(L[i])
else:
continue
Since you're starting with Python, I recommend you to read PEP 8 in order to know more about the style guide for Python in general. Hope you enjoy your Python journey!
Regarding your function, as you mentioned, Python terminates the execution of a program when a return is found. It's not a basic concept, but Python has something called generators (you can check more here) that you can use to easy fix your function:
def odd_numbers(l, r):
for i in range (l, r + 1):
if i % 2 == 0:
continue
else:
yield i
See that I simplified the function a bit (just one for loop is necessary). The most important part is the change from return to yield. That's the part where you create a generator. It's basically a "lazy" (memory efficient) return that doesn't terminate the execution.
if generators is too difficult for you, you can try this.
code:
def odd_numbers(l,r):
ans = []
for i in range (l,r+1):
if i % 2 == 1:
ans.append(i)
return ans
print(odd_numbers(1,10))
result:
[1, 3, 5, 7, 9]
A more pythonic to do it by using list comprehension
code:
def odd_numbers_list_comprehension(l,r):
return [i for i in range (l,r+1) if i % 2 == 1]
print(odd_numbers_list_comprehension(1,10))
result:
[1, 3, 5, 7, 9]

How search an unordered list for a key using reduce?

I have a basic reduce function and I want to reduce a list in order to check if an item is in the list. I have defined the function below where f is a comparison function, id_ is the item I am searching for, and a is the list. For example, reduce(f, 2, [1, 6, 2, 7]) would return True since 2 is in the list.
def reduce(f, id_, a):
if len(a) == 0:
return id_
elif len(a) == 1:
return a[0]
else:
# can call these in parallel
res = f(reduce(f, id_, a[:len(a)//2]),
reduce(f, id_, a[len(a)//2:]))
return res
I tried passing it a comparison function:
def isequal(x, element):
if x == True: # if element has already been found in list -> True
return True
if x == element: # if key is equal to element -> True
return True
else: # o.w. -> False
return False
I realize this does not work because x is not the key I am searching for. I get how reduce works with summing and products, but I am failing to see how this function would even know what the key is to check if the next element matches.
I apologize, I am a bit new to this. Thanks in advance for any insight, I greatly appreciate it!
Based on your example, the problem you seem to be trying to solve is determining whether a value is or is not in a list. In that case reduce is probably not the best way to go about that. To check if a particular value is in a list or not, Python has a much simpler way of doing that:
my_list = [1, 6, 2, 7]
print(2 in my_list)
print(55 in my_list)
True
False
Edit: Given OP's comment that they were required to use reduce to solve the problem, the code below will work, but I'm not proud of it. ;^) To see how reduce is intended to be used, here is a good source of information.
Example:
from functools import reduce
def test_match(match_params, candidate):
pattern, found_match = match_params
if not found_match and pattern == candidate:
match_params = (pattern, True)
return match_params
num_list = [1,2,3,4,5]
_, found_match = reduce(test_match, num_list, (2, False))
print(found_match)
_, found_match = reduce(test_match, num_list, (55, False))
print(found_match)
Output:
True
False

why for loop is used to generate numbers through function?

I can't get why for i in gen(100): print(i) is being used here. when i replace print(i) with print(gen(i)) it start giving memory location. I do know that yield is used for one time storage but how exactly is it working?
def gen(num):
i = 0
while i<num:
x=i
i+=1
if x%7 == 0:
yield x
for i in gen(100):
print(i)
yield is not used for one-time storage. yield makes a function return a generator
A generator is an iterable object (which means you can use it in place of any sequences such as list(gen()), for i in gen(), etc.). You can also pass it to the next() built-in function that advances a generator one step forward (makes it begin or start where it left off and run to the first yield it hits). It also returns the yielded value
def gen():
for i in range(5):
yield i
print(list(gen())) # prints [0, 1, 2, 3, 4]
print(next(gen())) # prints 0
gn = gen()
print(next(gn)) # prints 0
print(list(gn)) # prints [1, 2, 3, 4]
print(next(gn)) # raises StopIteration, because the generator is
# exhausted (the generator function ran to completion)
The reason why you're getting a memory address from print(gen(i)) is because you're actually printing a generator object, not the value it produces. So that's why generators first have to be iterated somehow

Python 3.x - function args type-testing

I started learning Python 3.x some time ago and I wrote a very simple code which adds numbers or concatenates lists, tuples and dicts:
X = 'sth'
def adder(*vargs):
if (len(vargs) == 0):
print('No args given. Stopping...')
else:
L = list(enumerate(vargs))
for i in range(len(L) - 1):
if (type(L[i][1]) != type(L[i + 1][1])):
global X
X = 'bad'
break
if (X == 'bad'):
print('Args have different types. Stopping...')
else:
if type(L[0][1]) == int: #num
temp = 0
for i in range(len(L)):
temp += L[i][1]
print('Sum is equal to:', temp)
elif type(L[0][1]) == list: #list
A = []
for i in range(len(L)):
A += L[i][1]
print('List made is:', A)
elif type(L[0][1]) == tuple: #tuple
A = []
for i in range(len(L)):
A += list(L[i][1])
print('Tuple made is:', tuple(A))
elif type(L[0][1]) == dict: #dict
A = L[0][1]
for i in range(len(L)):
A.update(L[i][1])
print('Dict made is:', A)
adder(0, 1, 2, 3, 4, 5, 6, 7)
adder([1,2,3,4], [2,3], [5,3,2,1])
adder((1,2,3), (2,3,4), (2,))
adder(dict(a = 2, b = 433), dict(c = 22, d = 2737))
My main issue with this is the way I am getting out of the function when args have different types with the 'X' global. I thought a while about it, but I can't see easier way of doing this (I can't simply put the else under for, because the results will be printed a few times; probably I'm messing something up with the continue and break usage).
I'm sure I'm missing an easy way to do this, but I can't get it.
Thank you for any replies. If you have any advice about any other code piece here, I would be very grateful for additional help. I probably have a lot of bad non-Pythonian habits coming from earlier C++ coding.
Here are some changes I made that I think clean it up a bit and get rid of the need for the global variable.
def adder(*vargs):
if len(vargs) == 0:
return None # could raise ValueError
mytype = type(vargs[0])
if not all(type(x) == mytype for x in vargs):
raise ValueError('Args have different types.')
if mytype is int:
print('Sum is equal to:', sum(vargs))
elif mytype is list or mytype is tuple:
out = []
for item in vargs:
out += item
if mytype is list:
print('List made is:', out)
else:
print('Tuple made is:', tuple(out))
elif mytype is dict:
out = {}
for i in vargs:
out.update(i)
print('Dict made is:', out)
adder(0, 1, 2, 3, 4, 5, 6, 7)
adder([1,2,3,4], [2,3], [5,3,2,1])
adder((1,2,3), (2,3,4), (2,))
adder(dict(a = 2, b = 433), dict(c = 22, d = 2737))
I also made some other improvements that I think are a bit more 'pythonic'. For instance
for item in list:
print(item)
instead of
for i in range(len(list)):
print(list[i])
In a function like this if there are illegal arguments you would commonly short-cuircuit and just throw a ValueError.
if bad_condition:
raise ValueError('Args have different types.')
Just for contrast, here is another version that feels more pythonic to me (reasonable people might disagree with me, which is OK by me).
The principal differences are that a) type clashes are left to the operator combining the arguments, b) no assumptions are made about the types of the arguments, and c) the result is returned instead of printed. This allows combining different types in the cases where that makes sense (e.g, combine({}, zip('abcde', range(5)))).
The only assumption is that the operator used to combine the arguments is either add or a member function of the first argument's type named update.
I prefer this solution because it does minimal type checking, and uses duck-typing to allow valid but unexpected use cases.
from functools import reduce
from operator import add
def combine(*args):
if not args:
return None
out = type(args[0])()
return reduce((getattr(out, 'update', None) and (lambda d, u: [d.update(u), d][1]))
or add, args, out)
print(combine(0, 1, 2, 3, 4, 5, 6, 7))
print(combine([1,2,3,4], [2,3], [5,3,2,1]))
print(combine((1,2,3), (2,3,4), (2,)))
print(combine(dict(a = 2, b = 433), dict(c = 22, d = 2737)))
print(combine({}, zip('abcde', range(5))))

Resources