简体   繁体   中英

List all combinations but with restriction

I've found the way to list all possible combinations of n elements grouped in sets of k elements. From maths, number is easy: n!/(k! * (nk)!) and python code is really simple using itertools:

>>> import itertools
>>> a = [1,2,3,4]
>>> list(itertools.combinations(a,3))
[(1, 2, 3), (1, 2, 4), (1, 3, 4), (2, 3, 4)]

But how to implement a restriction like: list only groups with only m elements in common ? so in previous example, for m=1, result should be:

[(1, 2, 3)]

With 5 elements and m=1:

>>> b=[1,2,3,4,5]
>>> list(itertools.combinations(b,3))
[(1, 2, 3), (1, 2, 4), (1, 2, 5), (1, 3, 4), (1, 3, 5), (1, 4, 5), (2, 3, 4), (2, 3, 5), (2, 4, 5), (3, 4, 5)]

So my result is:

[(1, 2, 3), (1, 4, 5)]

Practical application of this question is how to group people, considering that only m people can repeat in result groups. Idea is to find groups with different people, to avoid groups of 'friends'. Imagine this for an school activity, repeated few days, where we want to ensure avoiding people to repeat with others as much as possible.

One can use for loop to compare first combination with rest (using set intersection function "&" ):

def getsets(a, n):              # FUNCTION FOR THIS PURPOSE
    combos = list(itertools.combinations(a,n))  # GET ALL COMBINATIONS
    print(combos[0])            # PRINT FIRST COMBINATION
    for i in combos[1:]:        # FOR EACH OF REST
        if len(set(combos[0]) & set(i)) == 1:       # IF ONLY 1 ITEM COMMON WITH INITIAL
            print(i)            # PRINT IT

Testing:

getsets([1,2,3,4], 3)
print("---------")
getsets([1,2,3,4,5], 3)

Output:

(1, 2, 3)   # INITIAL SET ONLY
---------
(1, 2, 3)   # INITIAL SET
(1, 4, 5)   # ONLY '1' IS COMMON WITH FIRST SET
(2, 4, 5)   # ONLY '2' IS COMMON WITH FIRST SET
(3, 4, 5)   # ONLY '3' IS COMMON WITH FIRST SET
{1, 2, 3}   # ONLY '4' IS COMMON WITH FIRST SET

You can use for loop:

m = 1
combos = list(itertools.combinations(a,3))
result = []
for combo in combos:
    if result:
        if all([sum([1 for item in combo if item in tup]) == 1 for tup in result]):
            result.append(combo)
    else:
        result.append(combo)
result
#[(1, 2, 3), (1, 4, 5)]

This way you will be able to control number of mutual members and get desired output.

It seems to be, that logical equivalent of result that you've got is something like this:

a = range(1,6)
shared_elem_count = 1
group_count = 3

combinations = itertools.combinations(a, group_count)
results = []

for combination in combinations:
    res_union = set().union(*results)
    intersection = set(combination).intersection(res_union)
    if len(intersection) <= shared_elem_count:
        results.append(combination)

As in your example tuples don't share more than m common elements with any of other available. But as it's pointed in comments, that solution is strictly tide to order of operations. First tuple determines what next tuples would be acceptable.

Mind the fact, that what you said in your comment is a problem which is little broader: grouping n people in groups of count k that don't have more than m people in common will give significantly more solutions than those that we have 'not filtered out'. For example, you've pointed that for m = 1 and a = (1,2,3,4,5) solution is:

(1,2,3),(1,4,5)

...but actually there is many more solutions, for example:

(2,3,4),(1,2,5)
(3,4,5),(1,2,3)
(1,4,5),(2,3,4)

So I'm not completely sure if filtering combinations is proper way to solve your problem (nevertheless, it's not very clear what actually you'd like to achieve, so who knows).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM