2

It seems that this is some known problem but I cannot find it. I have m balls and n bins. I would like to generate all possible combinations of putting the balls into the bins including full and empty assignments, i.e., a bin maybe empty or may contain all balls (see the example below).

For example, for m=n=2, I found:

1    x    bin 1 contains ball 1, bin 2 contains nothing.
x    1    bin 1 contains nothing, bin 2 contains ball 1.
2    x    bin 1 contains ball 2, bin 2 contains nothing.
x    2    bin 1 contains nothing, bin 2 contains ball 2.
1,2  x    bin 1 contains balls 1 and 2, bin 2 contains nothing.
x    1,2  bin 1 contains nothing, bin 2 contains balls 1 and 2.
1    2    bin 1 contains ball 1, bin 2 contains ball 2.
2    1    bin 1 contains ball 2, bin 2 contains ball 1.
x    x    bin 1 contains nothing, bin 2 contains nothing.

How can I generate these combinations in Python (for example)? You can provide a pseudo-code or any programming language you wish.

I start by generating all combinations like

x
1
2
1,2

then I was thinking to assign these combinations to the bins but I could not finish it.

Adriaan
  • 17,741
  • 7
  • 42
  • 75
zdm
  • 495
  • 3
  • 15
  • 1
    The answer depends on whether the balls are considered to be distinguishable or indistinguishable. In other words, the balls are indistinguishable if any two balls are interchangeable. Your example implies that the balls are numbered, i.e. distinguishable. – Cameron Bieganek Oct 15 '19 at 04:01
  • The usual approach to counting the number of ways of putting `m` indistinguishable balls into `n` (distinguishable) bins is called [stars and bars](https://en.wikipedia.org/wiki/Stars_and_bars_(combinatorics)). – Cameron Bieganek Oct 15 '19 at 04:32

3 Answers3

4

Here's a solution in Matlab, fully vectorized. It should be pretty fast. The approach is:

  1. Create a matrix that indicates which bin each ball goes to (including the possibility of no bin);
  2. Collect the balls that are in each bin.

The first part is just the Cartesian product of an n+1-element vector with itself m times. It can be done as a numerical base conversion with dec2base, provided that n is less than 36. If not, it this approach could be used.

The second part is done efficiently with accumarray.

m = 2; % data: number of balls
n = 3; % data: number of bins
t = (n+1)^m; % compute number of cases
[~, pos] = ismember(dec2base(0:t-1, n+1), ['1':'9' 'A':'Z']); % each column in this
% matrix refers to a ball, and tells which bin the ball goes to, or zero if no bin
[ii, jj, vv] = find(pos); % ii: combination; jj: ball: vv: bin
result = accumarray([ii vv], jj, [t n], @(x){sort(x).'}); % collect balls of each bin

Example

For m=2, n=3:

enter image description here

(Sorry about using an image).

Community
  • 1
  • 1
Luis Mendo
  • 110,752
  • 13
  • 76
  • 147
3

for m balls to be put into n bins, there are n^m combinations. But as you accept putting no balls to m-1 balls, the answer to your question is summation of m^0 + mC1 * n^1 + mC2 * n^2 + .... mCm * n^m

eg. for 3 balls into 3 bins, the total number is 1 + 3*3 + 3*9 + 1*27 = 64

Python Solution.

import itertools
#let bins be 'ABC', but u can have your own assignment
bins = 'ABC'
balls = '123'
tmp = []
for no_ball_used in range(len(balls)+1):
    bin_occupied = [''.join(list(i)) for i in itertools.product(bins,repeat=no_ball_used)]
    ball_used = [''.join(list(i)) for i in itertools.combinations(balls,no_ball_used)]
    solution = [i for i in itertools.product(ball_used,bin_occupied)]
    tmp.append(solution)

tmp is the complete list, where first part is ball used, and second part is bin used.

print(tmp)
[[('', '')], [('1', 'A'), ('1', 'B'), ('1', 'C'), ('2', 'A'), ('2', 'B'), ('2', 'C'), ('3', 'A'), ('3', 'B'), ('3', 'C')], [('12', 'AA'), ('12', 'AB'), ('12', 'AC'), ('12', 'BA'), ('12', 'BB'), ('12', 'BC'), ('12', 'CA'), ('12', 'CB'), ('12', 'CC'), ('13', 'AA'), ('13', 'AB'), ('13', 'AC'), ('13', 'BA'), ('13', 'BB'), ('13', 'BC'), ('13', 'CA'), ('13', 'CB'), ('13', 'CC'), ('23', 'AA'), ('23', 'AB'), ('23', 'AC'), ('23', 'BA'), ('23', 'BB'), ('23', 'BC'), ('23', 'CA'), ('23', 'CB'), ('23', 'CC')], [('123', 'AAA'), ('123', 'AAB'), ('123', 'AAC'), ('123', 'ABA'), ('123', 'ABB'), ('123', 'ABC'), ('123', 'ACA'), ('123', 'ACB'), ('123', 'ACC'), ('123', 'BAA'), ('123', 'BAB'), ('123', 'BAC'), ('123', 'BBA'), ('123', 'BBB'), ('123', 'BBC'), ('123', 'BCA'), ('123', 'BCB'), ('123', 'BCC'), ('123', 'CAA'), ('123', 'CAB'), ('123', 'CAC'), ('123', 'CBA'), ('123', 'CBB'), ('123', 'CBC'), ('123', 'CCA'), ('123', 'CCB'), ('123', 'CCC')]]

tmp[0] = combination of 0 balls

tmp[1] = combination of 1 balls

tmp[2] = combination of 2 balls

tmp[3] = combination of 3 balls

You can unpack tmp with

[item for sublist in tmp for item in sublist]
chrisckwong821
  • 1,133
  • 12
  • 24
1

Ok, here's a limited solution for two bins, but arbitrary number of (distinguishable) balls.

It starts by finding the possible combinations of balls that can be put in bin 1 (combsBin1). From there, all these combinations are iterated, and a subset of balls is generated by excluding those already put into bin 1. Then for each combination in combsBin1, all combinations for filling bin 2 are found. Finally, the combination of these combinations (whoa, I'm sorry for the poor wording) is stored.

Here comes some Octave (MATLAB compatible) code. It's definitely not beautiful, and it may help to remove some semicolons to inspect intermediate results.

balls = [NaN 1 2];
bins = [1 2];

finalCombs = {};

% Fill bin 1
combsBin1 = {};
for iBall = 1:numel(balls)-1
  if (iBall == 1)
    temp = nchoosek(balls, iBall);
  else
    temp = nchoosek(balls(2:end), iBall);
  end
  combsBin1 = [combsBin1; mat2cell(temp, ones(size(temp, 1), 1))];
end

% Output
printf('Possible combinations for bin 1 only:\n\n');
printf('Bin 1\n');
printf('-----\n');
for iC = 1:size(combsBin1, 1)
  printf('%s\n', num2str(combsBin1{iC, 1}));
end

% Fill bin 2 by iterating all combinations found for bin 1
% and excluding all balls which are already in bin 1.
for iComb = 1:numel(combsBin1)
  b = [NaN setdiff(balls(2:end), combsBin1{iComb})];
  if (isnan(b))
    finalCombs = [finalCombs; [combsBin1{iComb}, {NaN}]];
  end
  for iBall = 1:numel(b)-1
    if (iBall == 1)
      temp = nchoosek(b, iBall);
    else
      temp = nchoosek(b(2:end), iBall);
    end
    c = mat2cell(temp, ones(size(temp, 1), 1));
    for iC = 1:numel(c)      
      finalCombs = [finalCombs; [combsBin1{iComb}, c(iC)]];
    end
  end
end

% Output
printf('\n\nPossible combinations for bin 1 and bin 2:\n\n');
printf('Bin 1          Bin 2\n');
printf('-----          -----\n');
for iC = 1:size(finalCombs, 1)
  str = num2str(finalCombs{iC, 1});
  printf('%s', str);
  printf('%s', char(32 * ones(1, 15 - length(str))));
  printf('%s\n', num2str(finalCombs{iC, 2}));
end

I incorporate a "NaN ball" as no ball is put into a specific bin. All possible combinations for a set of balls are found by using nchoosek for increasing number of balls. To store varying number of balls per combination, I need to use cell arrays, which makes the handling even more complicated, and possibly the code even harder to understand.

For two bins and two balls, we get the following output:

Possible combinations for bin 1 only:

Bin 1
-----
NaN
1
2
1  2


Possible combinations for bin 1 and bin 2:

Bin 1          Bin 2
-----          -----
NaN            NaN
NaN            1
NaN            2
NaN            1  2
1              NaN
1              2
2              NaN
2              1
1  2           NaN

If we use three balls, we get:

Possible combinations for bin 1 only:

Bin 1
-----
NaN
1
2
3
1  2
1  3
2  3
1  2  3


Possible combinations for bin 1 and bin 2:

Bin 1          Bin 2
-----          -----
NaN            NaN
NaN            1
NaN            2
NaN            3
NaN            1  2
NaN            1  3
NaN            2  3
NaN            1  2  3
1              NaN
1              2
1              3
1              2  3
2              NaN
2              1
2              3
2              1  3
3              NaN
3              1
3              2
3              1  2
1  2           NaN
1  2           3
1  3           NaN
1  3           2
2  3           NaN
2  3           1
1  2  3        NaN

So, now, to generalize this code to also support arbitrary number of bins, you must put the second part (excluding already chosen balls from before, etc.) into some iterative, maybe recursive process. That might be a lot of work, but the presented approach should be suitable nevertheless.

Hope that helps!

HansHirse
  • 18,010
  • 10
  • 38
  • 67