I'm trying to do as the title suggests. Take in a value like 100 with a part number like 5. Then split 100 into 5 parts that add up to 100. Each part being random. So a result would be like 20, 25, 5, 40, 10. It would return a list/array. This is the code I'm currently using thanks to a post here from 10+ years ago.
List<int> a = new List<int>();
a = Enumerable.Repeat(0, numOfStats - 1) // Seq with (n-1) elements...
.Select(x => Random.Range(1, points)) // ...mapped to random values
.Concat(new[] { 0, points })
.OrderBy(x => x)
.ToArray()
.ToList();
return a.Skip(1).Select((x, i) => x - a[i]).ToList();
numStats is the division number and points is the total value that will be split.
The only problem is that I need to make sure each part is no more than a certain number. So each part would be max 30 for example. Anyone know how I can edit this to make sure there is a clamp on the parts?
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