Search code examples
pythoncombinationspython-itertoolsshuffle

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.

  1. I would like to know how can I avoid duplicate combinations in the new iterations.
  2. 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)

Solution

  • 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.