This is strongly related to Splitting a set of integers over a set of bins, but a much simpler case.
If we have $N$ indistinguishable balls and $k$ arbitrarily large distinguishable bins, we know the number of ways to distribute the balls over the bins (allowing for empty bins) is given by a binomial coeffcient
binCounts[balls_, bins_] :=
Binomial[balls + bins - 1, balls];
and if we want the actual splits, those are given by taking all permutations of integer partitions of $N$ of length $k$
binSplits[balls_, bins_] :=
Apply[
Join,
Permutations[PadRight[#, bins]] & /@
IntegerPartitions[balls, bins]
];
Just to show this indeed works
Table[
Length[binSplits[n, k]] == binCounts[n, k],
{n, 1, 20},
{k, 1, 10}
] // Flatten // Apply[And]
True
This breaks down, though, when we have sized bins, i.e. each of our $k$ bins has some max number of allowed balls $c_i$. From this math.SE question I'm not expecting there to be a nice closed-form solution for the number of partitions. That doesn't mean we can't write an algorithm to generate them.
The naive approach would be to just filter our prior list of solutions
binSplitsSized[balls_, binSizes_] :=
Block[{bins = Length[binSizes], splits},
splits = Apply[
Join,
Permutations[PadRight[#, bins]] & /@
IntegerPartitions[balls, bins]
];
Select[splits, Min[binSizes - #] >= 0 &]
]
binSplitsSized[10, ConstantArray[3, 5]]
{{3, 3, 3, 1, 0}, {3, 3, 3, 0, 1}, {3, 3, 1, 3, 0}, {3, 3, 1, 0, 3}, {3, 3, 0,
3, 1}, {3, 3, 0, 1, 3}, {3, 1, 3, 3, 0}, {3, 1, 3, 0, 3}, {3, 1, 0, 3, 3}, {3,
0, 3, 3, 1}, {3, 0, 3, 1, 3}, {3, 0, 1, 3, 3}, {1, 3, 3, 3, 0}, {1, 3, 3, 0,
3}, {1, 3, 0, 3, 3}, {1, 0, 3, 3, 3}, {0, 3, 3, 3, 1}, {0, 3, 3, 1, 3}, {0, 3,
1, 3, 3}, {0, 1, 3, 3, 3}, {3, 3, 2, 2, 0}, {3, 3, 2, 0, 2}, {3, 3, 0, 2,
2}, {3, 2, 3, 2, 0}, {3, 2, 3, 0, 2}, {3, 2, 2, 3, 0}, {3, 2, 2, 0, 3}, {3, 2,
0, 3, 2}, {3, 2, 0, 2, 3}, {3, 0, 3, 2, 2}, {3, 0, 2, 3, 2}, {3, 0, 2, 2,
3}, {2, 3, 3, 2, 0}, {2, 3, 3, 0, 2}, {2, 3, 2, 3, 0}, {2, 3, 2, 0, 3}, {2, 3,
0, 3, 2}, {2, 3, 0, 2, 3}, {2, 2, 3, 3, 0}, {2, 2, 3, 0, 3}, {2, 2, 0, 3,
3}, {2, 0, 3, 3, 2}, {2, 0, 3, 2, 3}, {2, 0, 2, 3, 3}, {0, 3, 3, 2, 2}, {0, 3,
2, 3, 2}, {0, 3, 2, 2, 3}, {0, 2, 3, 3, 2}, {0, 2, 3, 2, 3}, {0, 2, 2, 3,
3}, {3, 3, 2, 1, 1}, {3, 3, 1, 2, 1}, {3, 3, 1, 1, 2}, {3, 2, 3, 1, 1}, {3, 2,
1, 3, 1}, {3, 2, 1, 1, 3}, {3, 1, 3, 2, 1}, {3, 1, 3, 1, 2}, {3, 1, 2, 3,
1}, {3, 1, 2, 1, 3}, {3, 1, 1, 3, 2}, {3, 1, 1, 2, 3}, {2, 3, 3, 1, 1}, {2, 3,
1, 3, 1}, {2, 3, 1, 1, 3}, {2, 1, 3, 3, 1}, {2, 1, 3, 1, 3}, {2, 1, 1, 3,
3}, {1, 3, 3, 2, 1}, {1, 3, 3, 1, 2}, {1, 3, 2, 3, 1}, {1, 3, 2, 1, 3}, {1, 3,
1, 3, 2}, {1, 3, 1, 2, 3}, {1, 2, 3, 3, 1}, {1, 2, 3, 1, 3}, {1, 2, 1, 3,
3}, {1, 1, 3, 3, 2}, {1, 1, 3, 2, 3}, {1, 1, 2, 3, 3}, {3, 2, 2, 2, 1}, {3, 2,
2, 1, 2}, {3, 2, 1, 2, 2}, {3, 1, 2, 2, 2}, {2, 3, 2, 2, 1}, {2, 3, 2, 1,
2}, {2, 3, 1, 2, 2}, {2, 2, 3, 2, 1}, {2, 2, 3, 1, 2}, {2, 2, 2, 3, 1}, {2, 2,
2, 1, 3}, {2, 2, 1, 3, 2}, {2, 2, 1, 2, 3}, {2, 1, 3, 2, 2}, {2, 1, 2, 3,
2}, {2, 1, 2, 2, 3}, {1, 3, 2, 2, 2}, {1, 2, 3, 2, 2}, {1, 2, 2, 3, 2}, {1, 2,
2, 2, 3}, {2, 2, 2, 2, 2}}
but if the number of bins or balls get even moderately large, this quickly becomes unworkable, since we're generating the entire Binomial[balls + bins - 1, balls]
solution set.
So how can we do better? The easiest initial approach is to just filter our IntegerPartitions
off the bat to exclude any unworkable solutions
binSplitsSized2[balls_, binSizes_] :=
Block[{bins = Length[binSizes], max = Max[binSizes], splits},
splits = Apply[
Join,
Permutations[PadRight[#, bins]] & /@
Select[IntegerPartitions[balls, bins], Max[#] <= max &]
];
Select[splits, Min[binSizes - #] >= 0 &]
]
and this can give a significant boost
binSplitsSized[10, ConstantArray[3, 5]] // Length // RepeatedTiming
{0.0018, 101}
binSplitsSized2[10, ConstantArray[3, 5]] // Length // RepeatedTiming
{0.00022, 101}
but can also be very easily stymied
binSplitsSized[10, ConstantArray[10, 1]~Join~ConstantArray[1, 4]] //
Length // RepeatedTiming
{0.0019, 16}
binSplitsSized2[10, ConstantArray[10, 1]~Join~ConstantArray[1, 4]] //
Length // RepeatedTiming
{0.0018, 16}
So what good approaches are there do better?