Task: I need to distribute a sum of packages into containers with different capacity.
Each capacity has restrictions: minimum and maximum package count.
Package count should be equal or less than sum of selected container capacities.
Capacities are stored in database. Packages sum is prompted from user and could be decimal.
Result should be a collection of container capacities for every container. There is no limit to results count.
I have a recursive solution written in C# but it crashes with StackOverflowException for large package sums.
// returns all combinations of capacity max and min values which package sum could include
static IEnumerable<List<int>> GetCombinations(int[] set, int sum, List<int> values)
{
for (var i = 0; i < set.Length; i++)
{
var left = sum - set[i];
var vals = new List<int>(values);
vals.Add(set[i]);
if (left == 0)
{
yield return vals;
}
else
{
int[] possible = set.Where(n => n <= sum).ToArray();
if (possible.Length > 0)
{
foreach (var s in GetCombinations(possible, left, vals))
{
yield return s;
}
}
}
}
}
Calling code where packageCapacities contains properties Count (which is MaxCount for real) and MinCount:
var allCapacityValues = packageCapacities
.SelectMany(x => Enumerable.Range((int)x.MinCount, (int)x.Count - (int)x.MinCount + 1))
.OrderByDescending(x => x)
.ToArray();
// gets first combination, sort numbers in it and distinct it
var combination = GetCombinations(allCapacityValues, (int)Math.Ceiling(contentData.FactCount), new List<int>())
.Select(x => x.OrderByDescending(o => o))
.Distinct(new EnumerableComparer<int>())
.FirstOrDefault();
where there are two capacities and sum of 13 which is distributed into 3 container sizes.
Reproducible code:
using System;
using System.Collections.Generic;
using System.Linq;
namespace Data.Services
{
public class ContainerGenerationService1
{
public void GenerateContainersWorks()
{
int capacity1Min = 4;
int capacity1Max = 5;
int capacity2Min = 2;
int capacity2Max = 2;
int[] set = Enumerable.Range(capacity1Min, capacity1Max - capacity1Min + 1)
.Concat(Enumerable.Range(capacity2Min, capacity2Max - capacity2Min + 1))
.ToArray();
int sum = 13;
var combination = GetCombinations(set, sum, new List<int>())
.Select(x => x.OrderByDescending(o => o))
.Distinct(new EnumerableComparer<int>())
.FirstOrDefault();
}
public void GenerateContainersFails()
{
int capacity1Min = 3;
int capacity1Max = 9;
int[] set = Enumerable.Range(capacity1Min, capacity1Max - capacity1Min + 1).ToArray();
int sum = 999999;
var combination = GetCombinations(set, sum, new List<int>())
.Select(x => x.OrderByDescending(o => o))
.Distinct(new EnumerableComparer<int>())
.FirstOrDefault();
}
static IEnumerable<List<int>> GetCombinations(int[] set, int sum, List<int> values)
{
for (var i = 0; i < set.Length; i++)
{
var left = sum - set[i];
var vals = new List<int>(values);
vals.Add(set[i]);
if (left == 0)
{
yield return vals;
}
else
{
int[] possible = set.Where(n => n <= sum).ToArray();
if (possible.Length > 0)
{
foreach (var s in GetCombinations(possible, left, vals))
{
yield return s;
}
}
}
}
}
class EnumerableComparer<T> : IEqualityComparer<IEnumerable<T>> where T : IComparable<T>
{
public bool Equals(IEnumerable<T> first, IEnumerable<T> second)
{
if (first == second)
return true;
if ((first == null) || (second == null))
return false;
return new HashSet<T>(first).SetEquals(second);
}
public int GetHashCode(IEnumerable<T> enumerable)
{
return enumerable.OrderBy(x => x)
.Aggregate(1, (current, val) => current + val.GetHashCode());
}
}
}
}
Calling code:
var svc = new ContainerGenerationService1();
svc.GenerateContainersWorks(); // works
svc.GenerateContainersFails(); // fails with StackOverflowException