Python - Calculate combinations of different values as a sum - python

Given a list of tuples as following:
values = [
('a', 'b', 'c'),
('d', 'e'),
('f', 'g', 'h')
]
I'd like to calculate different combinations of those values, but not as a cartesian product, rather as a sum on some custom rules. To clarify, if we calculate the cartesian product between those tuples, we will get 3*2*3 = 18 different combinations. But my desire is to get something like this:
combinations = [
('a', 'd', 'f'),
('a', 'e', 'g'),
('a', 'e', 'h'),
('b', 'd', 'f'),
('b', 'e', 'g'),
('b', 'e', 'h'),
('c', 'd', 'f'),
('c', 'e', 'g'),
('c', 'e', 'h')
]
So the resulting list contains 9 different combinations instead of 18.
Example with 4 tuples:
values = [
('a', 'b', 'c'),
('d', 'e'),
('f', 'g', 'h'),
('i', 'j', 'k', 'l')
]
The result would be
combinations = [
('a', 'd', 'f', 'i'),
('a', 'e', 'g', 'j'),
('a', 'e', 'h', 'k'),
('a', 'e', 'h', 'l'),
('b', 'd', 'f', 'i'),
('b', 'e', 'g', 'j'),
('b', 'e', 'h', 'k'),
('b', 'e', 'h', 'l'),
('c', 'd', 'f', 'i'),
('c', 'e', 'g', 'j'),
('c', 'e', 'h', 'k'),
('c', 'e', 'h', 'l'),
]
To Explain the logic for the outputs further:
In both inputs, the first tuple is behaving as it would in a cartesian product.
However, all the other tuples except the first are being iterated (or zipped) together. Additionally, if one of the tuples being iterated together "runs out of values" so to speak, we use the last value in the tuple instead.
What would be the efficient way to achieve this?

With the extra example provided, we can figure out how the logic will look. Essentially, the first row is being treated specially and used in the normal "cartesian product" sense.
However, the rest of the rows are being effectively extended to the largest length, and being zipped together. Coding that up, it can look something like follows:
from itertools import product
def extend_to_max_len(tup, length):
'''extends a tuple to a specified length by
filling the empty spaces with last element of given tuple
'''
fill_count = length - len(tup)
return (*tup, *[tup[-1]]*fill_count)
def non_cartesian_sum(values):
'''Expects a list of tuples.
gives the output according to the custom rules:
top: first row: to be used for cartesian product with zip of remaining rows
bottom: remaining rows: extended to longest length before zipping
'''
if len(values) < 2:
print("Check length of input provided")
return None
top = values[0]
bottom = values[1:]
max_len = max(len(row) for row in bottom)
bottom = [extend_to_max_len(row, max_len) for row in bottom]
out = [(first, *rest) for first, rest in product(top, zip(*bottom))]
return out
values = [
('a', 'b', 'c'),
('d', 'e'),
('f', 'g', 'h'),
('i', 'j', 'k', 'l')
]
out = non_cartesian_sum(values)
print(out)
Output:
[('a', 'd', 'f', 'i'),
('a', 'e', 'g', 'j'),
('a', 'e', 'h', 'k'),
('a', 'e', 'h', 'l'),
('b', 'd', 'f', 'i'),
('b', 'e', 'g', 'j'),
('b', 'e', 'h', 'k'),
('b', 'e', 'h', 'l'),
('c', 'd', 'f', 'i'),
('c', 'e', 'g', 'j'),
('c', 'e', 'h', 'k'),
('c', 'e', 'h', 'l')]
Note that you may want to add more input validation as required, before using this function for your use case.

This works for the data provided.
values = [
('a', 'b', 'c'),
('d', 'e'),
('f', 'g', 'h')
]
length_of_1 = len(values[1])
length_of_2 = len(values[2])
output = []
for item0 in values[0]:
for i in range(max(length_of_1, length_of_2)):
if i >= length_of_1:
item1 = values[1][-1]
else:
item1 = values[1][i]
if i >= length_of_2:
item2 = values[2][-1]
else:
item2 = values[2][i]
triple = (item0, item1, item2)
output.append(triple)
for tup in output:
print(tup)
Output:
('a', 'd', 'f')
('a', 'e', 'g')
('a', 'e', 'h')
('b', 'd', 'f')
('b', 'e', 'g')
('b', 'e', 'h')
('c', 'd', 'f')
('c', 'e', 'g')
('c', 'e', 'h')

Try this
values = [
('a', 'b', 'c'),
('d', 'e'),
('f', 'g', 'h')
]
combination = [(a,b,c) for a in values[0] for b in values[1] for c in values[2]]
print(combination)

Related

How to Pair elements from a list without repeating the same combination?

I have a list of elements, that I would like to group (of size 2,3,4 etc.) and find some unique combinations in each iteration. I have the following snippet, that forms combinations of size group_size of members.
I would like to know how can I avoid duplicate combinations in the new iterations.
For group_size > 2, I want to also avoid any two elements of members repeating. Let's say: group_size = 3; then ['A', 'B', 'C'] is accepted, but any other combination of ['A', 'B',~] or ['B', 'C',~] or ['A', 'C',~] is not accepted in the future iterations, where '~' represents any element other than ['A', 'B', 'C'].
import random
from itertools import zip_longest
members = ['A', 'B', 'C', 'D', 'E', 'F', 'U', 'V', 'W', 'X', 'Y', 'Z']
group_size = 2
for i in range(10):
random.shuffle(members)
pairs_loc = [iter(members)] * group_size
pairs = zip_longest(*pairs_loc)
print(*pairs)
Honestly I'm not sure I understood correctly what you want to do, but let me try, maybe it is useful to you all the same.
For the first point Python already has what (I believe that) you're looking for: itertools.combinations.
For the second point we need some code. One note: I'm sure you realize that with this second requirement you will have some cases when not all members appear in at least one combination: e.g., with 12 members and a groupsize > 6.
The code:
def select_combos(members, groupsize):
assert groupsize > 1
shuffle(members)
if groupsize == 2:
return list(combinations(members, 2))
finalcombos = []
usedcombos = []
for c in combinations(members, groupsize):
tempcombos = list(combinations(c, 2))
for c2 in tempcombos:
if c2 in usedcombos:
break
else:
usedcombos += tempcombos
finalcombos.append(c)
return finalcombos
m = ['A', 'B', 'C', 'D', 'E', 'F', 'U', 'V', 'W', 'X', 'Y', 'Z']
select_combos(m, 2)
[('C', 'A'), ('C', 'Z'), ('C', 'Y'), ('C', 'E'), ('C', 'W'), ('C', 'B'), ('C', 'U'), ('C', 'X'), ('C', 'D'), ('C', 'V'), ('C', 'F'), ('A', 'Z'), ('A', 'Y'), ('A', 'E'), ('A', 'W'), ('A', 'B'), ('A', 'U'), ('A', 'X'), ('A', 'D'), ('A', 'V'), ('A', 'F'), ('Z', 'Y'), ('Z', 'E'), ('Z', 'W'), ('Z', 'B'), ('Z', 'U'), ('Z', 'X'), ('Z', 'D'), ('Z', 'V'), ('Z', 'F'), ('Y', 'E'), ('Y', 'W'), ('Y', 'B'), ('Y', 'U'), ('Y', 'X'), ('Y', 'D'), ('Y', 'V'), ('Y', 'F'), ('E', 'W'), ('E', 'B'), ('E', 'U'), ('E', 'X'), ('E', 'D'), ('E', 'V'), ('E', 'F'), ('W', 'B'), ('W', 'U'), ('W', 'X'), ('W', 'D'), ('W', 'V'), ('W', 'F'), ('B', 'U'), ('B', 'X'), ('B', 'D'), ('B', 'V'), ('B', 'F'), ('U', 'X'), ('U', 'D'), ('U', 'V'), ('U', 'F'), ('X', 'D'), ('X', 'V'), ('X', 'F'), ('D', 'V'), ('D', 'F'), ('V', 'F')]
select_combos(m, 5)
[('W', 'V', 'C', 'U', 'E'), ('W', 'A', 'X', 'B', 'F'), ('V', 'A', 'D', 'Y', 'Z')]
EDIT
Now that it's clearer, the request for group size 2 is equivalent to scheduling a round-robin tournament, so we can use the standard circle method here.
One quick and dirty implementation of the rotation:
def rotate(roster):
half = (len(roster)+1)//2
t=roster[1]
roster[1] = roster[half]
for j in range(half, len(roster)-1):
roster[j] = roster[j+1]
roster[-1] = roster[half-1]
for j in range(half-1, 1, -1):
roster[j] = roster[j-1]
roster[2] = t
for i in range(half):
print(f'{roster[i]}-{roster[i+half]} ', end = '')
print()
members = ['A', 'B', 'C', 'D', 'E', 'F', 'U', 'V', 'W', 'X', 'Y', 'Z']
shuffle(members)
for r in range(len(members)):
rotate(members)
At each iteration this will rotate the roster one step and print the pairings. Note that at the n-th iteration the roster, and hence the pairings, will be the same as at the start.
You can use a dictionary of sets to keep track of the pairings that have already been used in previous groups. Then assemble a new group based on the eligible members that you constrain with each addition to the group. Note that it is possible to hit a dead-end so your group forming logic needs to be able to reset itself and start over with different random members:
import random
members = ['A', 'B', 'C', 'D', 'E', 'F', 'U', 'V', 'W', 'X', 'Y', 'Z']
remaining = {M:set(members)-{M} for M in members} # unused pairs by member
group_size = 3
for _ in range(10):
more = set() # set of members that can be added to group
group = [] # current group
while len(group)<group_size:
if len(more)+len(group)<group_size: # group not feasible
more = {m for m,r in remaining.items() if r} # reset
group = []
m = random.sample(more,1)[0] # select eligible member
group.append(m) # add to group
more &= remaining[m] # constrain next members
print(group)
for m in group: # track unused pairs
remaining[m].difference_update(group)
['B', 'Z', 'Y']
['X', 'U', 'W']
['Y', 'U', 'C']
['D', 'Y', 'W']
['B', 'X', 'A']
['X', 'E', 'D']
['B', 'V', 'F']
['D', 'V', 'Z']
['E', 'A', 'F']
['A', 'C', 'Z']

Get a list with tuple unpacking in a function

I have a given function that takes different inputs (example):
def myfunction(x, y, z):
a = x,y,z
return a
Then, this for loop:
tripples = [('a', 'b', 'c'), ('d', 'e', 'f'), ('g', 'h', 'i'), ('j', 'k', 'm')]
for tripple in tripples:
lst.append(myfunction(*tripple))
lst
Which works like this:
[('a', 'b', 'c'), ('d', 'e', 'f'), ('g', 'h', 'i'), ('j', 'k', 'm')]
I want to run it for i in range(n) and get a list of lists as an output,
for i in range(3):
for tripple in tripples:
lst_lst.append(myfunction(*tripple))
lst_lst
Output:
[('a', 'b', 'c'),
('d', 'e', 'f'),
('g', 'h', 'i'),
('j', 'k', 'm'),
('a', 'b', 'c'),
('d', 'e', 'f'),
('g', 'h', 'i'),
('j', 'k', 'm'),
('a', 'b', 'c'),
('d', 'e', 'f'),
('g', 'h', 'i'),
('j', 'k', 'm')]
Desired output:
[[('a', 'b', 'c'),
('d', 'e', 'f'),
('g', 'h', 'i'),
('j', 'k', 'm')],
[('a', 'b', 'c'),
('d', 'e', 'f'),
('g', 'h', 'i'),
('j', 'k', 'm')],
[('a', 'b', 'c'),
('d', 'e', 'f'),
('g', 'h', 'i'),
('j', 'k', 'm')]]
If it helps of something, full code:
def myfunction(x, y, z):
a = x,y,z
return a
lst = []
lst_lst = []
tripples = [('a', 'b', 'c'), ('d', 'e', 'f'), ('g', 'h', 'i'), ('j', 'k', 'm')]
for tripple in tripples:
lst.append(myfunction(*tripple))
for i in range(3):
for tripple in tripples:
lst_lst.append(myfunction(*tripple))
lst_lst
def myfunction(x, y, z):
a = x,y,z
return a
lst = []
tripples = [('a', 'b', 'c'), ('d', 'e', 'f'), ('g', 'h', 'i'), ('j', 'k', 'm')]
for i in range(3):
lst_lst = []
for tripple in tripples:
lst_lst.append(myfunction(*tripple))
lst.append(lst_lst)
print(lst)
you need to use a temporary list, which saves the result of one loop and then add those result to the final list and in next loop it intialise itself and then again save result from next triples
def myfunction(x, y, z):
a = x,y,z
return a
lst = []
lst_lst = []
tripples = [('a', 'b', 'c'), ('d', 'e', 'f'), ('g', 'h', 'i'), ('j', 'k', 'm')]
for tripple in tripples:
lst.append(myfunction(*tripple))
for i in range(3):
tmp =[]
for tripple in tripples:
tmp.append(myfunction(*tripple))
lst_lst.append(tmp)

Filter List of Tuples to Exclude from Another List of Tuples which Contains

(Using Python3)
I have a list of tuples, (of strings)
have = [
('a', 'b', 'c', 'd'), ('a', 'b', 'c', 'e'), ('a', 'b', 'c', 'f'), ('a', 'b', 'c', 'g'), ('a', 'b', 'd', 'e'),
('a', 'b', 'd', 'f'), ('a', 'b', 'd', 'g'), ('a', 'b', 'e', 'f'), ('a', 'b', 'e', 'g'), ('a', 'b', 'f', 'g'),
('a', 'c', 'd', 'e'), ('a', 'c', 'd', 'f'), ('a', 'c', 'd', 'g'), ('a', 'c', 'e', 'f'), ('a', 'c', 'e', 'g'),
('a', 'c', 'f', 'g'), ('a', 'd', 'e', 'f'), ('a', 'd', 'e', 'g'), ('a', 'd', 'f', 'g'), ('a', 'e', 'f', 'g'),
('b', 'c', 'd', 'e'), ('b', 'c', 'd', 'f'), ('b', 'c', 'd', 'g'), ('b', 'c', 'e', 'f'), ('b', 'c', 'e', 'g'),
('b', 'c', 'f', 'g'), ('b', 'd', 'e', 'f'), ('b', 'd', 'e', 'g'), ('b', 'd', 'f', 'g'), ('b', 'e', 'f', 'g'),
('c', 'd', 'e', 'f'), ('c', 'd', 'e', 'g'), ('c', 'd', 'f', 'g'), ('c', 'e', 'f', 'g'), ('d', 'e', 'f', 'g')
]
I also have a list of tuples (also strings) which I want to "exclude"
exclude = [('a', 'd'), ('b', 'c')]
I'm trying to find an efficient way to remove any element in have that contains both the elements in each exclude tuple. Ordering does not matter.
My goal is to return something like this:
[
('a', 'b', 'e', 'f'), ('a', 'b', 'e', 'g'), ('a', 'b', 'f', 'g'), ('a', 'c', 'e', 'f'), ('a', 'c', 'e', 'g'),
('a', 'c', 'f', 'g'), ('a', 'e', 'f', 'g'), ('b', 'd', 'e', 'f'), ('b', 'd', 'e', 'g'), ('b', 'd', 'f', 'g'),
('b', 'e', 'f', 'g'), ('c', 'd', 'e', 'f'), ('c', 'd', 'e', 'g'), ('c', 'd', 'f', 'g'), ('c', 'e', 'f', 'g'),
('d', 'e', 'f', 'g')
]
You could convert the exclude tuples to sets and then check for each element of have is the excluded set isn't a subset of it:
excludeSet = [set(e) for e in exclude]
filteredHave = [h for h in have if not any(e for e in excludeSet if e.issubset(h))]

Combinations between one element of each list of combinations [duplicate]

This question already has answers here:
How to get the cartesian product of multiple lists
(17 answers)
Closed 6 years ago.
I have about 12 lists [a, b, c, ... , z] with arbitrary elements and i´ve got a series of combinations through itertools.combinations(iterable, n) resulting in lists of combinations that match each of the original list.
The great deal now is to get a list with all the possible combinations, picking one element(combination) of each combinations list.
One simplified exemple would be:
A = [a,b,c]
B = [d,e,f]
C = [g,h,i]
my_iterable = [A, B, C]
And the output should be:
>>> foo(my_iterable)
(a,d,g), (a,d,h), (a,d,i), (a,e,g), (a,e,h), ... , (c,f,i)
The input iterables, e.g. 'A, B & C', may have variable lengths and foo() may be a generator function.
A = ['a','b','c']
B = ['d','e','f']
C = ['g','h','i']
l = [(a, b, c) for a in A for b in B for c in C]
print(l)
out:
[('a', 'd', 'g'), ('a', 'd', 'h'), ('a', 'd', 'i'), ('a', 'e', 'g'), ('a', 'e', 'h'), ('a', 'e', 'i'), ('a', 'f', 'g'), ('a', 'f', 'h'), ('a', 'f', 'i'), ('b', 'd', 'g'), ('b', 'd', 'h'), ('b', 'd', 'i'), ('b', 'e', 'g'), ('b', 'e', 'h'), ('b', 'e', 'i'), ('b', 'f', 'g'), ('b', 'f', 'h'), ('b', 'f', 'i'), ('c', 'd', 'g'), ('c', 'd', 'h'), ('c', 'd', 'i'), ('c', 'e', 'g'), ('c', 'e', 'h'), ('c', 'e', 'i'), ('c', 'f', 'g'), ('c', 'f', 'h'), ('c', 'f', 'i')]

Zip lists of tuples

I'm doing some stuff with data from files, and I have already zipped every column with its info, but now i want to combine info from other files (where i have zipped the info too) and i don't know how to unzip and get it together.
EDIT:
I have a couple of zip objects:
l1 = [('a', 'b'), ('c', 'd')] # list(zippedl1)
l2 = [('e', 'f'), ('g', 'h')] # list(zippedl1)
l3 = [('i', 'j'), ('k', 'm')] # list(zippedl1)
and i want to unzip like:
unzipped = [('a', 'c', 'e', 'g', 'i', 'k'), ('b', 'd', 'f', 'h', 'j', 'm')]
I wouldn't like to transform the zipped structures to a list, just for memory reasons. I searched and i didn't find something that helps me. Hope you can help me please!.
[sorry about my bad english]
I believe you want to zip an unpacked chain:
# Leaving these as zip objects as per your edit
l1 = zip(('a', 'c'), ('b', 'd'))
l2 = zip(('e', 'g'), ('f', 'h'))
l3 = zip(('i', 'k'), ('j', 'm'))
unzipped = [('a', 'c', 'e', 'g', 'i', 'k'), ('b', 'd', 'f', 'h', 'j', 'm')]
You can simply do
from itertools import chain
result = list(zip(*chain(l1, l2, l3)))
# You can also skip list creation if all you need to do is iterate over result:
# for x in zip(chain(l1, l2, l3)):
# print(x)
print(result)
print(result == unzipped)
This prints:
[('a', 'c', 'e', 'g', 'i', 'k'), ('b', 'd', 'f', 'h', 'j', 'm')]
True
You need to concatenate the lists first:
>>> l1 = [('a', 'b'), ('c', 'd')]
>>> l2 = [('e', 'f'), ('g', 'h')]
>>> l3 = [('i', 'j'), ('k', 'm')]
>>> zip(*(l1 + l2 + l3))
[('a', 'c', 'e', 'g', 'i', 'k'), ('b', 'd', 'f', 'h', 'j', 'm')]

Categories