Let's say I have \$5\$ apples, thus \$n = 5\$. I have three bags (\$k = 3\$), each having the capacities of \$4\$, \$4\$ and \$2\$:
$$c = { \{4, 4, 2 \}}$$
I'd like to calculate the number of ways the \$5\$ apples can be distributed into those bags; each bag can be empty. The results for this example would be following output:
$$\{4,1,0\}\\ \{4,0,1\}\\ \{1,4,0\}\\ \{0,4,1\}\\ \{3,2,0\}\\ \{3,0,2\}\\ \{2,3,0\}\\ \{0,3,2\}\\ \{3,1,1\}\\ \{1,3,1\}\\ \{2,2,1\}\\ \{2,1,2\}\\ \{1,2,2\}\\$$
Thus there are \$13\$ possible distributions for \$n = 5\$, \$k = 3\$ and \$c = \{4, 4, 2\}\$.
I have written a simple program which can calculate this, the basic algorithm is like following:
- Generate all integer partitions for \$n\$. I'm optimizing this by limiting each partition to each number in \$c\$; as such, the partition \$\{5, 0, 0\}\$ is impossible. For the example above, this would generate following partitions:
$$\{4,1,0\}\\ \{3,2,0\}\\ \{3,1,1\}\\ \{2,2,1\}\\$$
- Permute each partition. Each partition will have \$(\frac{k!}{i!})\$ possible permutations, whereas \$i\$ describes the number of identical integers. The set \$\{3, 1, 1\}\$ would have precisely \$3\$ permutations, as \$(\frac{3!}{2!} = 3)\$:
$$\\\{3,1,1\}\\ \{1,3,1\}\\ \{1,1,3\}\\$$
- After each permutation is calculated, validate them based on \$c\$. The permutation \$\{1, 1, 3\}\$ would be invalid, as it wouldn't fit into \$c\$.
- Finally, if the permutation is valid, increase the counter.
I'm storing the partitions in a BlockingCollection
to avoid running out of memory in case the numbers get too large (permutations will always be slower than partitions): \$n = 1000\$ could potentially yield \$2.4 \cdot 10^{31}\$ partitions!
My code works well for small numbers. Another example would yield \$48\$ possible distributions:
$$\\n = 10\\ k = 3\\ c = \{10,8,5\}$$
The problem of my approach is that it isn't efficient for large numbers at all. Following input could take days, if not weeks to compute:
$$\\n = 30\\ k = 20\\ c = \{20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1\}$$
I'm looking for ways to optimize my code, so that calculations for input like that would be feasible.
internal class Program
{
static int n;
static int k;
static int[] c;
static BlockingCollection<int[]> partitions;
static double counter;
static void Main(string[] args)
{
n = 10;
k = 3;
c = new[] {10, 8, 5}.OrderByDescending(x => x).ToArray();
partitions = new BlockingCollection<int[]>(8);
Task partitionTask = Task.Factory.StartNew(() =>
{
Partition(n, c[0], new List<int>());
});
partitionTask.ContinueWith(delegate { partitions.CompleteAdding(); });
foreach (int[] x in partitions.GetConsumingEnumerable())
{
Permute(x);
}
Console.WriteLine($"There are {counter:n0} possible distributions.");
Console.ReadLine();
}
// Generate all partitions of c
static void Partition(int x, int limit, List<int> p)
{
if (x > 0)
{
for (int i = Math.Min(x, limit); i > 0; i--)
{
if (p.Count == c.Length)
continue;
Partition(x - i, Math.Min(i, p.Count > c.Length - 2 ? i : c[p.Count + 1]), p.Concat(new[] { i }).ToList());
}
}
else
{
while (p.Count < k)
p.Add(0);
partitions.Add(p.ToArray());
}
}
// Generate all permutations for a given set and validate them
static void Permute(int[] set)
{
Action<int> permute = null;
permute = start =>
{
if (start == set.Length)
{
// Validate this permutation, if valid, increase the counter
if (!set.Where((t, i) => t > c[i]).Any())
{
counter++;
}
}
else
{
List<int> swaps = new List<int>();
for (int i = start; i < set.Length; i++)
{
if (swaps.Contains(set[i])) continue;
swaps.Add(set[i]);
Swap(set, start, i);
permute(start + 1);
Swap(set, start, i);
}
}
};
permute(0);
}
static void Swap(int[] set, int index1, int index2)
{
int temp = set[index1];
set[index1] = set[index2];
set[index2] = temp;
}
}