All possible exact sums from multiple arrays - python

I've been given the following problem and my code has become a horrible mess and I'm quite stuck.
You are given an allowance to spend at some shops (something like $15, just some positive integer)
You may only spend this money under two conditions:
1) You buy exactly one item from each shop.
2) You spend all of your money (nothing left over, and no debt)
Find all possible ways you could have purchased items to satisfy the above.
In actuality, you're given a budget int and some array like
9 and [[1,2,3],[0,5],[2,3,8]]
where each inner array lists the prices of items in the a shop. There can be arbitrarily many shops with arbitrarily many items. Costs cannot be negative, but they can be free!
The expected solution here would be:
[[1,5,3],[2,5,2],[1,0,8]]
As each array contains one item from each shop, each totals 9, and all possibilities are present.
Just to make it more difficult, speed is paramount.
The following is my code that has descended into madness and a near complete lack of functionality:
def Stepper(bud,LOL):
n=len(LOL)
lasts=[]
indices=[0 for j in range(n)]
focus=0
funds=[0 for j in range(n+1)]
funds[0]=bud
sols=[]
moveable=[]
for j in range(n):
length=len(LOL[j])
if(length==0):
return []
lasts.append(length-1)
if(moveable==[]):
if(length==1):
funds[j+1]=funds[j]-LOL[j][0]
else:
moveable.append(j)
focus=j
while(moveable!=[]):
while(LOL[focus][indices[focus]] > funds[focus]):
indices[focus]+=1
if(indices[focus]==lasts[focus]):
if(moveable[-1]==focus):
moveable.remove(focus)
if(moveable==[]):
if(focus<n-1):
moveable.append(focus+1)
funds[focus+1]=funds[focus]-LOL[focus][indices[focus]]
#print(n,lasts,indices,focus,moveable,funds,sols)
if((funds[focus+1]!=0) and (focus<n-1)):
focus+=1
indices[focus]=0
else:
if(funds[focus+1]==0):
for j in range(focus+1,n):
indices[j]=lasts[j]
sols.append(list(indices))
if(moveable[-1]==n-1):
moveable.remove(n-1)
if(moveable!=[]):
focus=moveable[-1]
indices[focus]+=1
if(indices[focus]==lasts[focus]):
if(moveable[-1]==focus):
moveable.remove(focus)
if(moveable==[]):
if(focus<n-1):
moveable.append(focus+1)
funds[focus+1]=funds[focus]-LOL[focus][indices[focus]]
focus+=1
indices[focus]=0
return(sols)
where bud is the budget and LOL is the list of lists (the shops and prices)

This is a combinatorics problem. Which item from shop 1 combines with which item from shop 2 and which single items from shops 3...n to add up to some number?
Python's standard library has a function which can generate all these combinations for you, saving you the nested for loops. It's the handy itertools.product:
>>> import itertools
>>> budget = 9
>>> shops = [[1, 2, 3], [0, 5], [2, 3, 8]]
>>> list(itertools.product(*shops))
[(1, 0, 2),
(1, 0, 3),
(1, 0, 8),
(1, 5, 2),
(1, 5, 3),
(1, 5, 8),
(2, 0, 2),
(2, 0, 3),
(2, 0, 8),
(2, 5, 2),
(2, 5, 3),
(2, 5, 8),
(3, 0, 2),
(3, 0, 3),
(3, 0, 8),
(3, 5, 2),
(3, 5, 3),
(3, 5, 8)]
Next we want to get rid of all the combinations which do not satisfy our condition (that the prices exactly add up to the total budget). Let's use the built-in filter function to get our solution:
>>> list(filter(lambda prices: sum(prices) == budget, itertools.product(*shops))
[(1, 0, 8), (1, 5, 3), (2, 5, 2)]

If you are allowed to use itertools
import itertools
x = [[1,2,3],[0,5],[2,3,8]]
combinations = list(itertools.product(*x))
for o in combinations:
if sum(o) == 9:
print(o)
Output:
(1, 0, 8)
(1, 5, 3)
(2, 5, 2)
Depends on this question.

If you're still interested in an algorithm this is my solution:
def Stepper(bud, shops, combinations = None):
if combinations is None:
combinations = [[item] for item in shops[0]]
#if empty, populate the list of combinations with list containing every item of the first shop
shops = shops[1:] #remove the shop from the list
if len(shops) == 1: #if only one shop remains
counter = 0
while counter < len(combinations):
comb = combinations[counter] # take a combination
diff = bud - sum(comb) # check how much you have to spend
if diff in shops[0]:
#if the shop have what you're looking for you keep the combination
comb.append(diff)
counter += 1
else:
# there is no way to spend all the money
combinations.remove(comb)
return combinations
new_combinations = list()
for old_combination in combinations:
for item in shops[0]:
comb = old_combination + [item] #create every possible combination mixing the old combinations with the items of the next stop
if sum(comb) < bud: new_combinations.append(comb) # the combination is valid only if you have 0 or more money left
return Stepper(bud,shops[1:],new_combinations) # calculate combinations with the remaining shops
To test it simply call
Stepper(9, [[1,2,3],[0,5],[2,3,8]])

Here is a simple, and probably inefficient, way to solve it.
In [70]: s = [[1,2,3],[0,5],[2,3,8]]
In [71]: n = 9
In [72]: for x in s[0]:
...: for y in s[1]:
...: for z in s[2]:
...: if x+y+z == n:
...: print(x,y,z)
...:
1 0 8
1 5 3
2 5 2
Starting from the first shop, for any option, iterate over the next store, then again, for any option of the second store, iterate over the options of the next store. Sum up all the prices and check if the sum is equal to 9.

Related

Sum element of numbers to get desired result

By using the snippet
import itertools
numbers = [1,2,3,4,5]
results = [7,8]
allcombs = [seq for i in range(len(numbers), 0, -1) for seq in itertools.combinations(numbers, i) if sum(seq) in results]
print(allcombs)
I'm able to get all combinations which give me the desire results. Major problem here is that the number must not repeat. So, instead of result
[(1, 2, 4), (1, 2, 5), (1, 3, 4), (2, 5), (3, 4), (3, 5)]
I need to get
[(1, 2, 4),(3, 5)]
All elements of results doesnt needs to be contained in combination of numbers.
Edit:
1 Solution
usednumbers = []
newresult = []
for comb in allcombs:
if not any(a in usednumbers for a in comb):
newresult.append(comb)
for n in comb:
usednumbers.append(n)
print(newresult)
I would suggest a recursive function that will find the largest combination first and then call itself with the remaining numbers to add other combinations that match the results.
Also, the combinations should be generated with indexes in addition to the numbers themselves to make it easier to determine the remaining values when the list contains duplicates numbers.
from itertools import combinations
def comboToSums(numbers,results,size=None):
if size is None: size = len(numbers)
if size == 0: return []
for combo in combinations(enumerate(numbers),size):
indexes,values = zip(*combo)
if sum(values) not in results: continue
remaining = [n for i,n in enumerate(numbers) if i not in indexes]
return [values] + comboToSums(remaining,results)
return comboToSums(numbers,results,size-1)
output:
numbers = [1,2,3,4,5]
results = [7,8]
c = comboToSums(numbers,results)
print(c) # [(1, 2, 4), (3, 5)]

How to isolate specific rows of a Cartesian Product - Python

I've created a cartesian product and printed from the following code:
A = [0, 3, 5]
B = [5, 10, 15]
C = product(A, B)
for n in C:
print(n)
And we have a result of
(0, 5)
(0, 10)
(0, 15)
(3, 5)
(3, 10)
(3, 15)
(5, 5)
(5, 10)
(5, 15)
Is it possible to check the sum of each set within the cartesian product to a target value of 10? Can we then return or set aside the sets that match?
Result should be:
(0, 10)
(5, 5)
Finally, how can I count the frequency of numbers in my resulting subset to show that 0 appeared once, 10 appeared once, and 5 appeared twice? I would appreciate any feedback or guidance.
You can use a list comprehension like this:
C = [p for p in product(A, B) if sum(p) == 10]
And to count the frequency of the numbers in the tuples, as #Amadan suggests, you can use collections.Counter after using itertools.chain.from_iterable to chain the sub-lists into one sequence:
from collections import Counter
from itertools import chain
Counter(chain.from_iterable(C))
which returns, given your sample input:
Counter({5: 2, 0: 1, 10: 1})
which is an instance of a dict subclass, so you can iterate over its items for output:
for n, freq in Counter({5: 2, 0: 1, 10: 1}).items():
print('{}: {}'.format(n, freq))

How to generate all possible X sets of matchups for N teams split into N/2 pairs?

I wasn't sure how to word this question... Any suggestions or edits would be appreciated!
I am trying to get all possibilities for a league schedule. I have 8 teams that are playing against each other for 12 weeks. Each week there are four time slots at which one pair of teams would play.
How to split a list into pairs in all possible ways gives me a solution to get:
[(0, 1), (2, 3), (4, 5), (6, 7)]
[(0, 1), (2, 3), (4, 6), (5, 7)]
[(0, 1), (2, 3), (4, 7), (5, 6)]
...
[(0, 2), (1, 3), (4, 5), (6, 7)]
[(0, 2), (1, 3), (4, 6), (5, 7)]
[(0, 2), (1, 3), (4, 7), (5, 6)]
and so on. It seems that there are 105 = 15*7 such pairs.
However, these are not all the pairs. Since I have 4 time slots at which teams can play, the order of these pairs inside the lists can change.
Ex:
(0, 1) is the same as (1, 0)
but
[(0, 1), (2, 3), (4, 5), (6, 7)]
is not the same as
[(2, 3), (0, 1), (4, 5), (6, 7)]
I want to ultimately have all possible sets of 12 of these 4-pair matchups where no team will play another team more than 2 times.
If I were to create all the possible schedules by doing:
schedules = list(itertools.combinations(<all_possible_4-pair_matchups>, 12))
that would be very inefficient and would take a very long time to run. This approach does not take into account whether a team has played another team more than twice.
I have this code so far:
# Author: Abraham Glasser, abrahamglasser#gmail.com
#
# This program determines if there's a schedule where
# each team will play at a specific hour exactly 3 times
#
# 12 weeks
# 8 teams
# 4 hours per week
from pandas import *
teams = [i for i in range(8)]
twelve_weeks = [[[-1 for i in range(2)] for j in range(4)] for k in range(12)]
# table to count how many times a team
# has played at a specific time slot
hour_count = [[0 for i in range(4)] for j in range(8)]
# table to count how many times two teams have played each other
game_count = [[0 for i in range(8)] for j in range(8)]
for i in range(8):
# a team cannot play against itself
game_count[i][i] = "X"
# function to update game count
def update_game_count():
for week in twelve_weeks:
for hour in week:
if hour[0] == -1:
pass
else:
game_count[hour[0]][hour[1]] += 1
game_count[hour[1]][hour[0]] += 1
# function to update hour count
def update_hour_count():
for week in twelve_weeks:
for hour in range(4):
pair = week[hour]
for team in teams:
if team in pair:
hour_count[team][hour] += 1
# solution from
# https://stackoverflow.com/questions/5360220/how-to-split-a-list-into-pairs-in-all-possible-ways
def all_pairs(lst):
if len(lst) < 2:
yield lst
return
a = lst[0]
for i in range(1,len(lst)):
pair = (a,lst[i])
for rest in all_pairs(lst[1:i]+lst[i+1:]):
yield [pair] + rest
x = list(all_pairs([0, 1, 2, 3, 4, 5, 6, 7]))
# TAKES TOO LONG AND DOES NOT ACCOUNT
# FOR TEAMS PLAYING MORE THAN TWICE
#
# schedules = list(itertools.combinations(x, 12))
# pretty printing
print("\nThe twelve weeks:")
print(DataFrame(twelve_weeks))
print("\n The hour counts:")
print(DataFrame(hour_count))
print("\n The game counts:")
print(DataFrame(game_count))
Do you have an estimate on the number of solutions?
If this number is too large, then there won't be a way to list all of these pairings.
I did a recursive approach to list all possibilities in some tree-like way.
Bad news: It probably takes too long (did not finish while writing this post).
Good news: It looked fine for 6 teams and 8 weeks (and < 1s time), so maybe you/anyone can recycle some ideas.
Here it is:
import itertools
import numpy as np
n = 6
weeks = 8
def all_pairs(lst):
if len(lst) < 2:
yield lst
return
a = lst[0]
for i in range(1,len(lst)):
pair = (a,lst[i])
for rest in all_pairs(lst[1:i]+lst[i+1:]):
yield [pair] + rest
def recurse(so_far, rem_list, count):
if len(so_far) == weeks: return [so_far]
res = []
for current in range(len(rem_list)):
match = rem_list[current]
new_count = count.copy()
for pair in match:
new_count[pair[0],pair[1]] += 1
new_count[pair[1],pair[0]] += 1
#set of pairs, whcih are not allowed any more
forbidden = {(i,j) for i in range(n) for j in range(n) if new_count[i,j] == 2}
#append current match, remove all now forbidden combinations
res += recurse(so_far + [match], [entry for entry in rem_list[(current+1):] if set(entry) & forbidden == set([])], new_count)
return res
l = list(all_pairs(list(range(n))))
foo = recurse([], l, np.zeros((n,n)))

Eliminating tuples from list of tuples based on a given criterion

So the problem is essentially this: I have a list of tuples made up of n ints that have to be eliminated if they dont fit certain criteria. This criterion boils down to that each element of the tuple must be equal to or less than the corresponding int of another list (lets call this list f) in the exact position.
So, an example:
Assuming I have a list of tuples called wk, made up of tuples of ints of length 3, and a list f composed of 3 ints. Like so:
wk = [(1,3,8),(8,9,1),(1,1,1)]
f = [2,5,8]
=== After applying the function ===
wk_result = [(1,3,8),(1,1,1)]
The rationale would be that when looking at the first tuple of wk ((1,3,8)), the first element of it is smaller than the first element of f. The second element of wk also complies with the rule, and the same applies for the third. This does not apply for the second tuple tho given that the first and second element (8 and 9) are bigger than the first and second elements of f (2 and 5).
Here's the code I have:
for i,z in enumerate(wk):
for j,k in enumerate(z):
if k <= f[j]:
pass
else:
del wk[i]
When I run this it is not eliminating the tuples from wk. What could I be doing wrong?
EDIT
One of the answers provided by user #James actually made it a whole lot simpler to do what I need to do:
[t for t in wk if t<=tuple(f)]
#returns:
[(1, 3, 8), (1, 1, 1)]
The thing is in my particular case it is not getting the job done, so I assume it might have to do with the previous steps of the process which I will post below:
max_i = max(f)
siz = len(f)
flist = [i for i in range(1,max_i +1)]
def cartesian_power(seq, p):
if p == 0:
return [()]
else:
result = []
for x1 in seq:
for x2 in cartesian_power(seq, p - 1):
result.append((x1,) + x2)
return result
wk = cartesian_power(flist, siz)
wk = [i for i in wk if i <= tuple(f) and max(i) == max_i]
What is happening is the following: I cannot use the itertools library to do permutations, that is why I am using a function that gets the job done. Once I produce a list of tuples (wk) with all possible permutations, I filter this list using two parameters: the one that brought me here originally and another one not relevant for the discussion.
Ill show an example of the results with numbers, given f = [2,5,8]:
[(1, 1, 8), (1, 2, 8), (1, 3, 8), (1, 4, 8), (1, 5, 8), (1, 6, 8), (1, 7, 8), (1, 8, 1), (1, 8, 2), (1, 8, 3), (1, 8, 4), (1, 8, 5), (1, 8, 6), (1, 8, 7), (1, 8, 8), (2, 1, 8), (2, 2, 8), (2, 3, 8), (2, 4, 8), (2, 5, 8)]
As you can see, there are instances where the ints in the tuple are bigger than the corresponding position in the f list, like (1,6,8) where the second position of the tuple (6) is bigger than the number in the second position of f (5).
You can use list comprehension with a (short-circuiting) predicate over each tuple zipped with the list f.
wk = [(1, 3, 8), (8, 9, 1), (1, 1, 1), (1, 9, 1)]
f = [2, 5, 8] # In this contrived example, f could preferably be a 3-tuple as well.
filtered = [t for t in wk if all(a <= b for (a, b) in zip(t, f))]
print(filtered) # [(1, 3, 8), (1, 1, 1)]
Here, all() has been used to specify a predicate that all tuple members must be less or equal to the corresponding element in the list f; all() will short-circuit its testing of a tuple as soon as one of its members does not pass the tuple member/list member <= sub-predicate.
Note that I added a (1, 9, 1) tuple for an example where the first tuple element passes the sub-predicate (<= corresponding element in f) whereas the 2nd tuple element does not (9 > 5).
You can do this with a list comprehension. It iterates over the list of tuples and checks that all of the elements of the tuple are less than or equal to the corresponding elements in f. You can compare tuples directly for element-wise inequality
[t for t in wk if all(x<=y for x,y in zip(t,f)]
# returns:
[(1, 3, 8), (1, 1, 1)]
Here is without loop solution which will compare each element in tuple :
wk_1 = [(1,3,8),(8,9,1),(1,1,1)]
f = [2,5,8]
final_input=[]
def comparison(wk, target):
if not wk:
return 0
else:
data=wk[0]
if data[0]<=target[0] and data[1]<=target[1] and data[2]<=target[2]:
final_input.append(data)
comparison(wk[1:],target)
comparison(wk_1,f)
print(final_input)
output:
[(1, 3, 8), (1, 1, 1)]
P.S : since i don't know you want less and equal or only less condition so modify it according to your need.

Return a sequence of a variable length whose summation is equal to a given integer

In the form f(x,y,z) where x is a given integer sum, y is the minimum length of the sequence, and z is the maximum length of the sequence. But for now let's pretend we're dealing with a sequence of a fixed length, because it will take me a long time to write the question otherwise.
So our function is f(x,r) where x is a given integer sum and r is the length of a sequence in the list of possible sequences.
For x = 10, and r = 2, these are the possible combinations:
1 + 9
2 + 8
3 + 7
4 + 6
5 + 5
Let's store that in Python as a list of pairs:
[(1,9), (2,8), (3,7), (4,6), (5,5)]
So usage looks like:
>>> f(10,2)
[(1,9), (2,8), (3,7), (4,6), (5,5)]
Back to the original question, where a sequence is return for each length in the range (y,x). I the form f(x,y,z), defined earlier, and leaving out sequences of length 1 (where y-z == 0), this would look like:
>>> f(10,1,3)
[{1: [(1,9), (2,8), (3,7), (4,6), (5,5)],
2: [(1,1,8), (1,2,7), (1,3,6) ... (2,4,4) ...],
3: [(1,1,1,7) ...]}]
So the output is a list of dictionaries where the value is a list of pairs. Not exactly optimal.
So my questions are:
Is there a library that handles this already?
If not, can someone help me write both of the functions I mentioned? (fixed sequence length first)?
Because of the huge gaps in my knowledge of fairly trivial math, could you ignore my approach to integer storage and use whatever structure the makes the most sense?
Sorry about all of these arithmetic questions today. Thanks!
The itertools module will definately be helpful as we're dealing with premutations - however, this looks suspiciously like a homework task...
Edit: Looks like fun though, so I'll do an attempt.
Edit 2: This what you want?
from itertools import combinations_with_replacement
from pprint import pprint
f = lambda target_sum, length: [sequence for sequence in combinations_with_replacement(range(1, target_sum+1), length) if sum(sequence) == target_sum]
def f2(target_sum, min_length, max_length):
sequences = {}
for length in range(min_length, max_length + 1):
sequence = f(target_sum, length)
if len(sequence):
sequences[length] = sequence
return sequences
if __name__ == "__main__":
print("f(10,2):")
print(f(10,2))
print()
print("f(10,1,3)")
pprint(f2(10,1,3))
Output:
f(10,2):
[(1, 9), (2, 8), (3, 7), (4, 6), (5, 5)]
f(10,1,3)
{1: [(10,)],
2: [(1, 9), (2, 8), (3, 7), (4, 6), (5, 5)],
3: [(1, 1, 8),
(1, 2, 7),
(1, 3, 6),
(1, 4, 5),
(2, 2, 6),
(2, 3, 5),
(2, 4, 4),
(3, 3, 4)]}
The problem is known as Integer Partitions, and has been widely studied.
Here you can find a paper comparing the performance of several algorithms (and proposing a particular one), but there are a lot of references all over the Net.
I just wrote a recursive generator function, you should figure out how to get a list out of it yourself...
def f(x,y):
if y == 1:
yield (x, )
elif y > 1:
for head in range(1, x-y+2):
for tail in f(x-head, y-1):
yield tuple([head] + list(tail))
def f2(x,y,z):
for u in range(y, z+1):
for v in f(x, u):
yield v
EDIT: I just see it is not exactly what you wanted, my version also generates duplicates where only the ordering differs. But you can simply filter them out by ordering all results and check for duplicate tuples.

Categories