Give up on trying to do it in one line (and program defensively, there are quite a few edge cases)
EDIT
Added SplitValue2()
(an improvement over SplitValue()
) and Shuffle()
static List<int> SplitValue(int value, int nParts, int maxPart)
{
if (maxPart < value / nParts) throw new Exception("Not possible");
var rng = new Random();
var lst = new List<int>();
var total = 0;
// Initial random allocation
for (var i = 0; i < nParts; i++)
{
var part = rng.Next(Math.Min(maxPart + 1, value - total)); // upper bound is exclusive
lst.Add(part);
total += part;
// Need more room
if (total == value && i + 1 < nParts)
for (var j = i; j >= 0; j--)
{
if (lst[i] > 0)
{
lst[i] -= 1;
total--;
}
}
}
// Top-up
for (var i = 0; i < nParts && total < value; i++)
{
var topup = Math.Min(maxPart - lst[i], value - total);
lst[i] += topup;
total += topup;
}
if (total != 100) throw new Exception("Failed");
return lst;
}
static List<int> SplitValue2(int valueToSplit, int nParts, int maxPart)
{
var result = new int[nParts];
var prng = new Random();
if (maxPart < valueToSplit / nParts) throw new Exception("Not possible");
var remaining = valueToSplit;
while (remaining > 0)
{
for (var i = 0; i < nParts && remaining > 0; i++)
{
var next = prng.Next(0, Math.Min(maxPart - result[i], remaining) + 1);
result[i] += next;
remaining -= next;
}
}
return Shuffle(result.ToList());
}
static List<int> Shuffle(List<int> list)
{
if (list == null) throw new Exception("nothing to do");
var cpy = new List<int>(list);
var prng = new Random();
var ret = new List<int>();
var len = cpy.Count;
if (len == 0) return ret;
var lenRem = len;
while (lenRem > 1)
{
var select = prng.Next(lenRem);
ret.Add(cpy[select]);
cpy.RemoveAt(select);
lenRem--;
}
ret.Add(cpy[0]);
return ret;
}
Console.WriteLine("Split 1");
//Console.WriteLine(string.Join(',', SplitValue(100,5,10)));
Console.WriteLine(string.Join(',', SplitValue(100,5,20)));
Console.WriteLine(string.Join(',', SplitValue(100,5,30)));
Console.WriteLine(string.Join(',', SplitValue(100,5,70)));
Console.WriteLine(string.Join(',', SplitValue(100,5,70)));
Console.WriteLine(string.Join(',', SplitValue(100,5,150)));
Console.WriteLine("\nSplit 2");
//Console.WriteLine(string.Join(',', SplitValue2(100,5,10)));
Console.WriteLine(string.Join(',', SplitValue2(100,5,20)));
Console.WriteLine(string.Join(',', SplitValue2(100,5,30)));
Console.WriteLine(string.Join(',', SplitValue2(100,5,70)));
Console.WriteLine(string.Join(',', SplitValue2(100,5,70)));
Console.WriteLine(string.Join(',', SplitValue2(100,5,150)));
I don't claim that this is bug-free, you will need to test
(and curious to see what other ideas are offered)
Sample output
Split 1
20,20,20,20,20
30,30,15,15,10
69,27,2,2,0
44,24,22,1,9
85,9,6,0,0
Split 2
20,20,20,20,20
21,30,11,25,13
3,4,64,4,25
8,10,56,13,13
3,0,1,86,10