Search code examples
pythonnumpycombinationssympypython-itertools

How to bin all subsets of a python list into n bins


I have a list:

a = range(2)

and I am trying to get the list's contents binned into n(=3) bins, in all possible ways, giving (order not important):

[[[],[0],[1]],
[[],[1],[0]],
[[],[0,1],[]],
[[],[],[0,1]],
[[0],[1],[]],
[[0],[],[1]],
[[1],[0],[]],
[[1],[],[0]],
[[0,1],[],[]]]

So far, I have been using the sympy.utilities.iterables library, firstly to get all possible subsets and filtering the outputs from the variations method to get the required result:

def binner(a,n=3):
    import numpy as np
    import sympy.utilities.iterables as itt
    import itertools as it
    b = list(itt.subsets(a))    #get all subsets
    b.append(())                #append empty tuple to allow two empty bins
    c=list(itt.variations(b,n)) 
    d=[]
    for x in c:
        if np.sum(np.bincount(list(it.chain.from_iterable(x))))==len(a):
            d.append(x)     # add only combinations with no repeats and with all included
    return list(set(d))             # remove duplicates

I have a feeling that there is a much better way of doing this. Can anyone help?

Note, I am not tied to the sympy library and am open to any python/numpy based alternatives.


Solution

  • Assuming I understand your aim (not sure what you might want to happen in cases of duplicate elements, namely whether you want to consider them distinct or not), you could use itertools.product:

    import itertools
    
    def everywhere(seq, width):
        for locs in itertools.product(range(width), repeat=len(seq)):
            output = [[] for _ in range(width)]
            for elem, loc in zip(seq, locs):
                output[loc].append(elem)
            yield output
    

    which gives

    >>> for x in everywhere(list(range(2)), 3):
    ...     print(x)
    ...     
    [[0, 1], [], []]
    [[0], [1], []]
    [[0], [], [1]]
    [[1], [0], []]
    [[], [0, 1], []]
    [[], [0], [1]]
    [[1], [], [0]]
    [[], [1], [0]]
    [[], [], [0, 1]]