This post Finding Combinations to the provided Sum value presents the function subsets_with_sum(). It finds a combination of values in an array which the sum is equal to a given value. But since the post is over 6 years old, I made this post to ask: how does this function work? What is it doing?
def subsets_with_sum(lst, target, with_replacement=False):
x = 0 if with_replacement else 1
def _a(idx, l, r, t):
if t == sum(l): r.append(l)
elif t < sum(l): return
for u in range(idx, len(lst)):
_a(u + x, l + [lst[u]], r, t)
return r
return _a(0, [], [], target)
To start, I'll just add some line breaks to make it a bit easier to read:
def subsets_with_sum(lst, target, with_replacement=False):
x = 0 if with_replacement else 1
def _a(idx, l, r, t):
if t == sum(l):
r.append(l)
elif t < sum(l):
return
for u in range(idx, len(lst)):
_a(u + x, l + [lst[u]], r, t)
return r
return _a(0, [], [], target)
First thing you'll notice is that the subsets_with_sum defines a function and calls it once in the last return statement. The first element is your list from which you sample values, the second is the target sum, and third is an parameter that tells the function whether a single element from lst can be used multiple times.
Because the _a is defined within the subsets_with_sum it is enclosed with the values passed to subsets_with_sum - this is important, lst is the same for every recursive call of _a. The second thing to notice is that l is a list of values, whereas r is a list of lists. Importantly, l is a different object for each _a call (that's due to l + [lst[u]] resulting in a copy of l concatenated with [lst[u]]). However, r denotes the same object for each _a call - r.append(l) modifies it in place.
I'll start with with_replacement == True because I find it simpler. With the first call _a is passed 0, [], [], target. It then checks if t == sum(l) and appends it to r, hence the only elements of r will be lists that sum to t (or target). If sum(l) that recursive branch is discarded by returning None. If the elements of l sum to a value lesser then target then for each element from the lst the element is appended to a copy of l and _a is called with that list. Here's an amended version of the function that prints out the steps, so we can inspect what happens:
def subsets_with_sum(lst, target, with_replacement=False):
x = 0 if with_replacement else 1
def _a(idx, l, r, t, c):
if t == sum(l):
r.append(l)
elif t < sum(l):
return
for u in range(idx, len(lst)):
print("{}. l = {} and l + [lst[u]] = {}".format(c, l, l + [lst[u]]))
_a(u + x, l + [lst[u]], r, t, c+1)
return r
return _a(0, [], [], target, 0)
Calling subsets_with_sum([1,2,3], 2, True) the following gets printed (I re-ordered and separated the printed lines a bit so they're easier to read):
0. l = [] and l + [lst[u]] = [1]
0. l = [] and l + [lst[u]] = [2]
0. l = [] and l + [lst[u]] = [3]
1. l = [1] and l + [lst[u]] = [1, 1]
1. l = [2] and l + [lst[u]] = [2, 2]
1. l = [2] and l + [lst[u]] = [2, 3]
1. l = [1] and l + [lst[u]] = [1, 2]
1. l = [1] and l + [lst[u]] = [1, 3]
2. l = [1, 1] and l + [lst[u]] = [1, 1, 1]
2. l = [1, 1] and l + [lst[u]] = [1, 1, 2]
2. l = [1, 1] and l + [lst[u]] = [1, 1, 3]
You can see that the right column (l + [lst[u]]) at a level c is equal to the left column (l) at a level c + 1. Furthermore, observe that [3] - the last line where c == 0 doesn't make it past the elif t < sum(l), hence it doesn't get printed. Similarly, [2] at c == 1 gets appended to r, but nonetheless the branch continues to the next level, since one or more elements of the lst might be equal to 0, hence appending them to [2] might result in another list that sums to the target.
Also, observe that for each list at certain level c, whose last element is lst[u] will appear len(lst) - u-times at the next level, c + 1, because that's the number of combinations of the list for each lst[z], where z >= u.
So what happens with with_replacement == False (the default case)? Well, in that case each time lst[u] is added to l and sum(l) < t, so that that specific instance of l continues to the next recursion level only lst[z] can be added to the list, where z > u, since u + 1 is passed to the respective _a call. Let's look what happens when we call subsets_with_sum([1,2,3], 2, False):
0. l = [] and l + [lst[u]] = [1]
0. l = [] and l + [lst[u]] = [3]
0. l = [] and l + [lst[u]] = [2]
1. l = [1] and l + [lst[u]] = [1, 1]
1. l = [1] and l + [lst[u]] = [1, 2]
1. l = [1] and l + [lst[u]] = [1, 3]
1. l = [2] and l + [lst[u]] = [2, 2]
1. l = [2] and l + [lst[u]] = [2, 3]
2. l = [1, 1] and l + [lst[u]] = [1, 1, 1]
2. l = [1, 1] and l + [lst[u]] = [1, 1, 2]
2. l = [1, 1] and l + [lst[u]] = [1, 1, 3]
Now observe that for each list at certain level c, whose last element is lst[u] will appear len(lst) - u - 1-times at the next level, c + 1, because that's the number of combinations of the list for each lst[z], where z > u. Compare this with the with_replacement=True analyzed above.
I suggest you play around with it somewhat more until you understand it better. Regardless of substitution, what is important to realize is that the return statements always return to the caller. The first call of the _a returns to the subsets_with_sum which then returns to the caller (presumably you or some other function). All other (recursive) calls to _a return to the previous instances of _a calls which just discard the returned value (or more precisely, don't bother to do anything with it). The reason you get back the right r is because it behaves somewhat like a global value - each _a call that finds a solution, adds it to the same r.
Finally, the subsets_with_sum doesn't work exactly like I'd expect it to based on its desired functionality. Try the following two calls: subsets_with_sum([2,2,1], 5, True) and subsets_with_sum([2,2,1,1,1], 5, False).
Related
Recently a friend of mine asked me to explain a strange behavior in a piece of code originally intended to count permutations using recursion. There were many improvements that could be made to the code, which I noted, but these seemed to not have any real impact.
I simplified the code down to the following, which reproduces only the problem, and not the permutations.
def foo(bar, lst):
if(bar == 1):
lst.append(0)
return
print(lst)
foo(1, lst)
print(lst)
foo(2, [])
The output is
[]
[0]
I tried lst += [0] or deleting lst after appending 0, but these did not help. Doing lst2 = lst.copy() followed by lst2.append(0) gave the expected result of two []s, however. I am confused as to why appending 0 (or any value) to lst where bar == 1 would have an effect on the lst where bar == 2. I do not consider myself a total beginner to Python, and I usually can determine the behavior of local variables. This has baffled me though. An explanation would be really appreciated.
In case you want the original code, though I don't think it'll give much more info, here it is:
A = 0
A2 = 0
NN = 3
def P(N, C):
global A
Temp = [X for X in range(1, NN + 1) if X not in C]
if(N == 1):
C.append(None)
A += 1
return
for e in Temp:
C.append(e)
P(N - 1, C)
del C[-1]
def P2(N, C):
global A2
Temp = [X for X in range(1, NN + 1) if X not in C]
if(N == 1):
A2 += 1
return
for e in Temp:
C.append(e)
P2(N - 1, C)
del C[-1]
P(NN, [])
P2(NN, [])
print(A, A2, sep = " ")
print(A == A2)
Although there are lots of questions that have already been asked and answered regarding heap implementation in python, I was unable to find any practical clarifications about indexes. So, allow me to ask one more heap related question.
I'm trying to write a code that transforms a list of values into a min-heap and saves swapped indexes. Here is what I have so far:
def mins(a, i, res):
n = len(a)-1
left = 2 * i + 1
right = 2 * i + 2
if not (i >= n//2 and i <= n):
if (a[i] > a[left] or a[i] > a[right]):
if a[left] < a[right]:
res.append([i, left])
a[i], a[left] = a[left], a[i]
mins(a, left, res)
else:
res.append([i, right])
a[i], a[right] = a[right], a[i]
mins(a, right, res)
def heapify(a, res):
n = len(a)
for i in range(n//2, -1, -1):
mins(a, i, res)
return res
a = [7, 6, 5, 4, 3, 2]
res = heapify(a, [])
print(a)
print(res)
Expected output:
a = [2, 3, 4, 5, 6, 7]
res = [[2, 5], [1, 4], [0, 2], [2, 5]]
What I get:
a = [3, 4, 5, 6, 7, 2]
res = [[1, 4], [0, 1], [1, 3]]
It's clear that there is something wrong with indexation in the above script. Probably something very obvious, but I just don't see it. Help out!
You have some mistakes in your code:
In heapify the first node that has a child, is at index (n - 2)//2, so use that as start value of the range.
In mins the condition not (i >= n//2 and i <= n) does not make a distinction between the case where the node has just one child or two. And i==n//2 should really be allowed when n is odd. Because then it has a left child. It is so much easier to just compare the value of left and right with n. It is also confusing that in heapify you define n as len(a), while in mins you define it as one less. This is really good for confusing the reader of your code!
To avoid code duplication (the two blocks where you swap), introduce a new variable that is set to either left or right depending on which one has the smaller value.
Here is a correction:
def mins(a, i, res):
n = len(a)
left = 2 * i + 1
right = 2 * i + 2
if left >= n:
return
child = left
if right < n and a[right] < a[left]:
child = right
if a[child] < a[i]: # need to swap
res.append([i, child])
a[i], a[child] = a[child], a[i]
mins(a, child, res)
def heapify(a, res):
n = len(a)
for i in range((n - 2)//2, -1, -1):
mins(a, i, res)
return res
I'm having trouble with accessing objects inside my 2D list in Python. Basically what I'm doing is that I have a 2D list called recueil (french for collection) that stores Reflection objects. The Reflection class has a valeur(=value) and an indice(=index) attribute, were the value is what I get when I apply the ref function (don't have it currently set up) to the reflection with a lower index (the first row of Reflections have one index, and the nth row of Reflections have n indexes, which explains the indice[]).
def main():
a = [1, 3, 5]
b = [2, 4, 6]
n = 3
recueil = [[Reflection([None],0) for colonne in range(N(ligne))] for ligne in range(n)]
recueil[0][0].valeur = b[0] - a[0]
recueil[0][1].valeur = b[1] - a[1]
recueil[0][2].valeur = b[2] - a[2]
for i in range(n):
print(recueil[0][i].valeur)
print(recueil[0][i].indice)
init_indices(recueil, n)
remplir_indices(recueil, n)
def N(n):
return 3 * (2 ** n)
def init_indices(recueil,n):
for i in range(n):
for j in range(N(i)):
recueil[i][j] = [0] * (i+1)
class Reflection(object):
indice = [0]
valeur = 0
def __init__(self, indice, valeur):
self.indice = indice
self.valeur = valeur
The problem I'm having is that in remplir_indices(=fill_indexes), I'm trying to make it so the initial sets have the indexes from 1 to n (so recueil[0][0].indice = 1, ..., recueil[0][n-1].indice = n) but it's treating recueil[][] as a list instead of the object stored in it. Any ideas as to what my error is?
def remplir_indices(recueil, n):
for i in range(n):
if i == 0:
for j in range(n):
recueil[i][j].indice[0] = j
else:
return 0
This looks like an indentation issue:
def remplir_indices(recueil, n):
for i in range(n):
if i == 0:
for j in range(n):
recueil[i][j].index[0] = j
# recueil[i][j].indice[0] = j
return 0
Hello I have a List with a lot of element in it. These are numbers and ordered but some numbers are missing.
Example: L =[1,2,3,4,6,7,10]
Missing: M = [5,8,9]
How can I find missing numbers in Python?
Take the difference between the sets:
set(range(min(L),max(L))) - set(L)
If you are really crunched for time and L is truly sorted, then
set(range(L[0], L[-1])) - set(L)
This function should do the trick
def missing_elements(L):
s, e = L[0], L[-1]
return sorted(set(range(s, e + 1)).difference(L))
miss = missing_elements(L)
Here you are:
L =[1,2,3,4,6,7,10]
M = [i for i in range(1, max(L)) if i not in L]
# If 0 shall be included replace range(1, max(L)) to range(max(L))
With a comprehension it would look like this:
L = [1,2,3,4,6,7,10]
M = [i for i in range(min(L), max(L)+1) if i not in L]
M
#[5,8,9]
And a fun one, just to add to the bunch:
[i for a, b in zip(L, L[1:]) for i in range(a + 1, b) if b - a > 1]
L =[1,2,3,4,6,7,10]
R = range(1, max(L) + 1)
> [1,2,3,4,5,6,7,8,9,10]
M = list(set(R) - set(L))
> [5,8,9]
Note that M will not necessarily be ordered, but can easily be sorted.
I have a list of variables, and a function object and I would like to assign the correct number of variable depending on the function.
def sum2(x,y):
return x + y
def sum3(x,y,z):
return x + y + z
varList = [1,2,3]
so if f = sum2 , I would like it to call first 2 elements of varList, and if f = sum3 , to call it with 3 elements of the function.
This can be done in a single function, if you are always returning the sum of all the passed arguments.
def sum1(*args):
return sum(args)
This is just utilizing positional arguments, as you don't appear to need to explicitly set individual values. It is also most flexible than the solution provided by ZdaR, as you don't need to know ahead of time the maximum number of arguments you can receive.
Some examples:
>>> print sum1(1, 2, 3)
6
>>> print sum1(1)
1
>>> print sum1(-1, 0, 6, 10)
15
Use the inspect module as follows:
import inspect
n2 = len(inspect.getargspec(sum2)[0])
n3 = len(inspect.getargspec(sum3)[0])
sum2(*varList[0:n2])
sum3(*varList[0:n3])
getargspec returns a 4-tuple of (args, varargs, keywords, defaults). So the above code works if all your args are explicit, i.e. not * or ** args. If you have some of those, change the code accordingly.
You may use default initialization, You should keep in mind the maximum number of variables that could be passed to this function. Then create a function with that number of parameters but initializing them with 0, because a+0 = a(in case some parameters are missing it will replace then with 0 which won't affect the results.)
def sum1(a=0, b=0, c=0, d=0):
return a+b+c+d
print sum1(1)
>>> 1
print sum1(1, 2)
>>> 3
print sum1(1, 2, 3)
>>> 6
print sum1(1, 2, 3, 4)
>>> 10
However, if you call the function with more than 4 arguments, it would raise error statement
Also as suggested by #CoryKramer in the comments you can also pass your varlist = [1, 2, 3, 4] as a parameter :
print sum1(*varlist)
>>> 10
Keeping in mind that the len(varlist) should be less than the number of parameters defined.
A general solution:
To get the number of argument, you can use f.func_code.co_argcount and than pass the correct elements from the list:
def sum2(x,y):
return x + y
def sum3(x,y,z):
return x + y + z
varlist = [2,5,4]
[f(*varlist[:f.func_code.co_argcount]) for f in [sum2,sum3]]
>> [7, 11]
You can check if f is a function with the is keyword
def sum2(x, y):
return x + y
def sum3(x, y, z):
return x + y + z
varList = [1, 2, 3]
f = sum2
if f is sum2:
sum = f(varList[0], varList[1])
print('Sum 2: ' + str(sum))
# Prints: 'Sum 2: 3'
f = sum3
if f is sum3:
sum = f(varList[0], varList[1], varList[2])
print('Sum 3: ' + str(sum))
# Prints: 'Sum 3: 6'
A dict of functions:
def two(l):
return l[0] + l[1]
def three(l):
return l[0] * l[1] + l[2]
funcs = {2:two, 3:three}
l = [1, 2, 3]
print len(l)
print funcs[len(l)](l)