Combinations of restricted set of integers - python

How to generate the list of combinations with allowed_ints that sums to goal ?
Examples:
allowed_ints=[1,2], goal=4
combinations = [[1,1,1,1],[1,1,2],[2,1,1],[2,2],[1,2,1]]
allowed_ints=[5, 6], goal=13
combinations = []
What I've made so far don't works.
def combinations(allowed_ints, goal):
if goal > 0:
for i in allowed_ints:
for p in combinations(allowed_ints, goal-i):
yield [i] + p
else:
yield []
print list(combinations([1, 2],3))
[[1, 1, 1], [1, 1, 2], [1, 2], [2, 1], [2, 2]] # not what I want

Using you function try this:
def combinations(allowed_ints, goal):
if goal > 0:
for i in allowed_ints:
for p in combinations(allowed_ints, goal-i):
if sum([i] + p) == goal:
yield [i] + p
else:
yield []
print list(combinations([1, 2],3))
Outputs:
[[1, 1, 1], [1, 2], [2, 1]]

I know you've already selected an answer, but I wanted to offer an alternative that's not as similar to your code. If you wanted to use recursion, I would suggest something like this:
def combinations(allowed_ints, list, goal):
# base case: you're done
if sum(list) == goal:
print list
# if overshoot, stop here
elif sum(list) > goal:
pass
else:
for i in allowed_ints:
new_list = list[:]
new_list.append(i)
combinations(allowed_ints, new_list, goal)
combinations([1, 2],[],4)
It's no better than the suggested answer, but uses a different approach.

You should include 3 conditions like
goal == 0 then recursion worked
goal < 0 then recursion failed
goal >= 0 and list elements end then recursion failed
By the way you can do it all using recursion. No need of loop, recursion can also do loop

Related

How to find all partial solutions of a list recursively

I have a box with a maximum weight capacity. I am given a list of weights of the items that I am supposed to fit in the box. I need all solutions that get to, or as close as possible to, the maximum capacity. What I mean is that I should not be able to add another item to any of the partial solutions without going over the maximum capacity.
If I am given the following list of weights:
[1,2,3,4,5]
If the maximum capacity is 9, the solutions should be (I may have missed one, but you get the point):
[[4,3,2], [5,3,1], [5,4], [3,2,1], [4,2,1], [4,3,1], [5,3]]
Here's my recursive algorithm, I think I am getting close but I can't figure out how to fix it.
def findSubset(alist, maxim):
if maxim <= 0:
return [[]]
if len(alist) == 0:
return []
alist2 = alist[1:]
include = findSubset(alist2, maxim-alist[0])
for s in include:
s.append(alist[0])
return include + findSubset(alist2, maxim)
Current output is:
[[4, 3, 2, 1], [5, 3, 2, 1], [5, 4, 2, 1], [5, 4, 3, 1], [5, 3, 1], [5, 4, 1], [4, 3, 2], [5, 3, 2], [5, 4, 2], [5, 4, 3], [5, 4]]
My advice would be to iterate on all element of the list, while building a list of possible solutions
if the element is < maxim, recurse on a sublist on following elements for maxim - elt, and for each element of the result, append the element to it and append all to the resulting list
if the element is == maxim, add the singleton list containing the element to the resulting list
Code is:
def findSubset(alist, maxim):
res = []
if maxim <= 0:
return [[]]
if len(alist) == 0:
return []
for i, elt in enumerate(alist):
if elt < maxim:
res.extend([ [elt] + l for l in findSubset(alist[i+1:], maxim-elt)])
elif elt == maxim:
res.append([elt])
return res
It gives
>>> findSubset(lst, 9)
[[1, 3, 5], [2, 3, 4], [4, 5]]
Above code only gives exact solutions. In case where there are no exact solution, it should be extended to give best approaching solutions:
def findApproachSubset(alist, maxim):
for i in range(maxim, 0, -1):
res = findSubset(alist, i)
if len(res) > 0:
return res
return []
For example:
>>> findSubset([1, 4, 5, 6], 8)
[]
>>> findApproachSubset([1, 4, 5, 6], 8)
[[1, 6]]
because here the best solution is for 7 instead of 8.
This is written using Haskell. As it seems that this is homework, there is no point giving you the complete answer.
We use three separate functions, the function f can be made recursive as it is just map
xs = [1, 2, 3, 4, 5]
perms [] = []
perms xs = xs : perms (tail xs)
All you need to know is that : means cons i.e 1:[2] = [1,2]
In python, we could write it as:
def perms(xs):
if xs:
return [xs] + perms(xs[1:])
else:
return []
so perms xs is just [[1,2,3,4,5],[2,3,4,5],[3,4,5],[4,5],[5]]
takeWhileLessThan _ [] = []
takeWhileLessThan n (x:xs) =
if n-x < 0 then [] else (x : takeWhileLessThan (n-x) xs)
and takeWhileLessThan uses recursion. It operates on a single list, keeping track of the current sum.
f n xs = map (takeWhileLessThan n) (perms xs)
> [[1,2,3],[2,3,4],[3,4],[4,5],[5]]
The final function maps the recursive function over the list of lists. If you then wanted all values and wanted it to be recursive, just write another function...
if you are allowed for loops, then this method would work well. The only recursion is the main function.
def f(n, xs, solutions):
if xs:
m = n
ys = []
for x in xs:
if (m-x) < 0:
break
else:
ys.append(x)
m = m-x
solutions.append(ys)
return f(n, xs[1:], solutions)
else:
return solutions
which gives:
>>> f(9, [1,2,3,4,5], [])
[[1, 2, 3], [2, 3, 4], [3, 4], [4, 5], [5]]
def recursive_function(current_node: int, nodes_left: list, maximum: int, trace: list) -> list:
sum_so_far = sum(trace)
if current_node is not None:
trace.append(current_node)
else:
current_node = 0
if sum_so_far + current_node > maximum:
# That means, that current trace is already to big
# no need to search further this path
return False
elif sum_so_far + current_node == maximum:
# Bingo! The perfect set of nodes!
# No need to look further this path
return {tuple(sorted(trace))}
else:
# We still haven't reached maximum value
# let's check if we can pack some more nodes to trace
results = set()
for node in nodes_left:
nodes_copy = nodes_left.copy()
nodes_copy.remove(node)
traces = recursive_function(node, nodes_copy, maximum, trace.copy())
if traces:
# This path gave us some legitimate results, we collect them
results.update(traces)
if results:
# At least one possible path gave us results, we pass them on
return results
# There was nothing left in nodes_left that we could add and not
# cross the maximum limit. The path leading to this moment fits
# our requirements.
return {tuple(sorted(trace))}
def findSubset(alist, maxim) -> list:
results = recursive_function(None, alist, maxim, [])
return results
Why do I do this {tuple(sorted(trace))}? So I don't keep different permutations of same results in my memory.
Time cost: n!
Memory cost: n

How does 'yield' work in this permutation generator?

def perm_generator(lst):
if len(lst) == 1:
yield lst
else:
for i in range(len(lst)):
for perm in perm_generator(lst[:i] + lst[i+1:]):
yield [lst[i]] + perm
This code has been bugging me, since I don't understand how the yields connect to each other. My understanding was that yield acts like a return, but it stops temporarily until it is called again. How do these yields work?
It might help seeing a version that doesn't use generators:
def perm_generator(lst):
res = []
if len(lst) == 1:
return [lst]
else:
for i in range(len(lst)):
for perm in perm_generator(lst[:i] + lst[i+1:]):
res.append([lst[i]] + perm)
return res
gen = perm_generator([1,2,3])
print gen # prints [[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]]
As you can see - this is not a "find and replace" of "return" with "yield". In the "return" version we needs to accumulate the result while in the "yield" version all that needs to be done is to "yield" the current permutation.

Python - Inner list concatenation

Ok, while I maybe wrong, I have the feeling I can solve this without the help of a function.
With this kind of input list:
input=[[1,2,3],[4,5],[7],[5,4,6],[5,4]]
I want a resulting list where inner lists sharing a common element are concatenated:
result=[[1,2,3],[4,5,6],[7]]
I already found the
if any(x in result[j] for x in input[i])
syntax that seems helpful for my problem, but now I'm stuck.
[EDIT]
one of my trials:
input=[[1,2,3],[4,5],[7],[5,4,6],[5,4]]
result=[input[0]]
for i in range(len(input)):
for j in range(len(result)):
if result[j] != input[i]:
if any(x in result[j] for x in input[i]):
result[j].extend(input[i])
result[j]=list(set(result[j]))
print "concatenate ",result[j], input[i]
break
else :
result.append(input[i])
print "append ", result[j], input[i]
break
>>> (bad) result
[[1, 2, 3], [4, 5], [7], [5, 4, 6], [5, 4]]
While if I initialize result with result=[input[-1]] it works:
input=[[1,2,3],[4,5],[7],[5,4,6],[5,4]]
result=[input[-1]]
for i in range(len(input)):
for j in range(len(result)):
if result[j] != input[i]:
if any(x in result[j] for x in input[i]):
result[j].extend(input[i])
result[j]=list(set(result[j]))
print "concatenate ",result[j], input[i]
break
else :
result.append(input[i])
print "append ", result[j], input[i]
break
>>> (good) result
[[4, 5, 6], [1, 2, 3], [7]]
This is a more simple and well-performing approach to solve your problem:
def concat(myinput) :
myinput_copy = [set(elem) for elem in myinput]
result = []
i = 0
# use `while` to avoid `for` problems (you're deleting list elems)
while i < len(myinput_copy) :
result.append(myinput_copy[i])
j = i + 1
while j < len(myinput_copy) :
if result[i] & myinput_copy[j] :
result[i] |= myinput_copy[j]
del myinput_copy[j]
j = i + 1
else :
j += 1
i += 1
return result
print concat([[1,2,3],[4,5],[7],[5,4,6],[5,4]])
It's about two times faster than J.F. Sebastian's answer to the other question, even if the other question wanted a different sorting.
I used myinput because input is a built-in function, and it's better to not masque it.
About sets and their operators: http://docs.python.org/3/tutorial/datastructures.html#sets (Python 3)
By the way, your solution is wrong, user1189129. Try your code with this input to see the problem: input = [[1, 2, 3], [1, 8], [1, 9], [4, 5], [7], [5, 4, 6], [5, 4]]
You can convert your list to plain list (extract all variables from sublist into a bigger list) and then split them into the chunks:
def chunks(l, n):
for i in xrange(0, len(l), n):
yield l[i:i+n]
def plain(lst):
return set([x for y in lst for x in y])
lst = [[1,2,3],[4,5],[7],[5,4,6],[5,4]]
print list(chunks(tuple(plain(l)), 3))

Python generator with external break condition

I need to iterate over ascending sequences x of n (= 5, f.i.) integers, finding all sequences for which a function f(*x) returns True.
Assume that if f_n(*y) is False for a particular y, then f_n(*z) id False for any z with z_i >= y_i. So f_n is monotonic in all its arguments.
This kind of generator function could be used in the following way to determine all ascending sequences of integers that have a sum of squares < 100
for sequence in generate_sequences(5):
if sum_squares_is_at_least(sequence, 100):
# some code to trigger the breaking of the generator loop
else:
print sequence
Clarification:
The problem here is that we need to iterate of n elements individually. Initially, we iterate [1,1,1,1,1] to [1,1,1,1,x], and then we have to continue with [1,1,1,2,2] to [1,1,1,2,y], eventually ending with [a,b,c,d,e]. It seems that the generator should look something like this, but needs some code to break out of the for and/or while loops if necessary (determined externally):
def generate_sequences(length, minimum = 1):
if length == []:
yield []
else:
element = minimum
while True:
for sequence in generate_sequences(length - 1, element):
yield element + [sequence]
element += 1
Example:
For n = 3, and sum of squares no larger than 20, the following sequences would be generated:
[1, 1, 1], [1, 1, 2], [1, 1, 3], [1, 1, 4], [1, 2, 2], [1, 2, 3], [1, 3, 3], [2, 2, 2], [2, 2, 3]
Note that in the general case, I cannot use the information that 4 is the upper bound for each element. This would also seriously impact the running time for larger examples.
Are you looking for itertools.takewhile?
>>> from itertools import takewhile
>>> def gen(): #infinite generator
... i=0
... while True:
... yield range(i,i+5)
... i = i+1
...
>>> [ x for x in takewhile( lambda x:sum(x)<20, gen() ) ]
[[0, 1, 2, 3, 4], [1, 2, 3, 4, 5]]
>>>
import itertools as it
it.takewhile(lambda x: sum_squares_is_at_least(x, 100), generate_sequences(5))
If you are now sure about the 5 in the generate_sequences, then just let it yield the numbers as long as it is called:
def generate_sequences():
i = 0 # or anything
while True:
yield [i, i] # or anything
i = i + 1 # or anything
Then use it this way:
it.takewhile(lambda x: sum_squares_is_at_least(x, 100), generate_sequences())
I would solve it with recursion by starting with a given list then appending another number (with logic to prevent going over sum of squares target)
def makegen(N): #make a generator with max sumSquares: N
def gen(l=[]): #empty list is valid with sum == 0
yield l
if l:
i = l[-1] #keep it sorted to only include combinations not permutations
else:
i = 1 #only first iteration
sumsquare = sum(x*x for x in l) #find out how much more we can add
while sumsquare + i*i < N: #increase the appended number until we exceed target
for x in gen(l+[i]): #recurse with appended list
yield x
i += 1
return gen
calling our generator generator (tee hee :D) in the following fashion allows us to have any maximum sum of squares we desire
for x in makegen(26)():
print x

Python list traversal with gaps

Hi I have a multidimensional list such as:
my_list = [[1,2,3,1,2],[1,0,3,1,2],[1,0,0,0,2],[1,0,3,0,2]]
where 0 represents a gap between two pieces of data.
What I need to do is iterate through the list and keep track of how many gaps are in each sublist and throw away the zeros. I think the best way is to break each sublist into chunks where there are zeros so I end up with smaller lists of integers and a number of gaps. Ideally, to form a new list which tells me the length of each chunk and number of gaps (i.e. chunks -1), such as:
new_list = [[5, 0], [[1, 3], 1], [[1, 1], 1], [[1, 1, 1], 2]]
or probably better:
new_list = [[5], [1, 3], [1, 1], [1, 1, 1]]
and I will know that the gaps are equal to len(chunk).
EDIT:
However, leading and trailing zeros do not represent gaps. i.e. [0,0,1,2] represents one continuous chunk.
Any help much appreciated.
itertools.groupby() is perfect for this:
from itertools import groupby
my_list = [[1,2,3,1,2],[1,0,3,1,2],[1,0,0,0,2],[1,0,3,0,2]]
new_list = [[len(list(g)) for k, g in groupby(inner, bool) if k] for inner in my_list]
Result:
>>> new_list
[[5], [1, 3], [1, 1], [1, 1, 1]]
The result contains the length of each non-zero chunk for each sublist, so for example [1,0,3,1,2] gives [1,3], so there are two chunks (one gap). This matches your second output format.
Here is my humble code without any imports:
The algorithm is slightly long:
def toggle(n):
return n != 0
def chunk_counter(L):
"""
list -> list
"""
chunk_list = []
pivots = []
for j in range(len(L)):
if j == 0 and toggle(L[0]):
pivots.append(j)
elif toggle(L[j]) and toggle(L[j]) != toggle(L[j-1]):
pivots.append(j)
for m in range(len(pivots)):
k = 0
if m == len(pivots)-1:
bound = len(L)
else:
bound = pivots[m+1]
p = 0
while p in range(bound - pivots[m]):
if toggle(L[pivots[m] + p]):
k += 1
p += 1
else:
p += 1
chunk_list.append(k)
return chunk_list
def chunks(L):
"""
(list of lists) -> list of lists
"""
new_list = []
for i in range(len(L)):
new_list.append(chunk_counter(L[i]))
return new_list
So, you may try the function chunks() on your list:
>>> L = [[1,2,3,1,2],[1,0,3,1,2],[1,0,0,0,2],[1,0,3,0,2], [0,0,1,2]]
>>> chunks(L)
[[5], [1, 3], [1, 1], [1, 1, 1], [2]]
Here's a recursive definition (a replacement for Chunk Counter):
counter_list = []
def counter(L):
k = 0
while(k < len(L) and L[k] != 0):
k +=1
counter_list.append(k)
if k == len(L):
print counter_list
else:
counter(L[k+1:])

Categories