Lee
Lee

Reputation: 31090

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.

Upvotes: 2

Views: 562

Answers (3)

farhawa
farhawa

Reputation: 10417

from itertools import permutations

a = range(2)


# get all the possible combinations

indexes = ','.join([str(i) for i in range(len(a))])+","
comb = []
perms = [''.join(p) for p in permutations(indexes)]
for x in perms:
    candidate = [[int(i) for i in  list(s)] if len(s) !=0 else [] for s in x.split(',') ]
    if candidate not in comb and [row[::-1] for row in candidate] not in comb:
        comb.append(candidate)

for item in comb:
    print item
>>
 [[0], [1], []]
 [[0], [], [1]]
 [[0, 1], [], []]
 [[], [0, 1], []]
 [[], [0], [1]]
 [[], [1], [0]]
 [[], [], [0, 1]]
 [[1], [0], []]
 [[1], [], [0]]

Upvotes: 1

hyades
hyades

Reputation: 3170

How about writing a recursive function to do this?

Basic idea is that

  1. subset(bins, l) is equal to [l] if bins == 1, since you have just one bin to place values
  2. subset(bins, l) = l[i] + subset(bins-1, lp) where lp = l without l[i]

    +

    [l] + subset(bins-1, []) -> take all into one bin
    +

    [] + subset(bins, l) -> take none into bin

Thus subset(3, [0, 1]) = [ [0] + subset(2, [1]) , [1] + subset(2, [0]) , [0, 1] , subset(2, []) , [] + subset(2, []) ]

The code goes like

def subset(bins, l):
    if bins == 1:
        if l == []:
            return [[]]
        return [[l]]
    all_possible = []
    for i in range(len(l)): # choosing one element 
        a = l[:]
        x = l[i]
        a.pop(i)
        if len(a) > 0:
            y = subset(bins-1, a) # recurse for that list minus chosen one
            for j in y:
                all_possible.append([[x]] + j)
    y = subset(bins-1, l) # dont take out any element
    for j in y:
        all_possible.append([[]] + j)
    y = [[]] * (bins-1) # take out all elements
    all_possible.append([l] + y)
    return all_possible

Output -

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

Upvotes: 0

DSM
DSM

Reputation: 353419

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]]

Upvotes: 4

Related Questions