How to get random number list by give size, and expectd sum, fully support
i hava a code sum-int.ts sum-float.ts internal/sum-num.ts
whai i want to do
- rN = radom number ( float or int ) between min ~ max
- size = [r1, r2, r3, ...rN].length
- sum = r1 + r2 + r3 + ...rN
- all rN should >= min, and <= max
- support (unique / no-unique) values
but now there has problem
- can't make it work when max <= 0 with float (so i disable input it)
- can't make it work when max <= 1 with int (so i disable input it)
- the code has logic bug, so i come ask here how to make it by js
update
thx @SeverinPappadeux for int version, and idea for float
- coreFnRandSumInt int version
- coreFnRandSumFloat float version
{ size: 2, sum: 5, min: 0, max: 5, n: 5, maxv: 5 }
true 0 5 [ 2, 3 ] 0 [ 2, 3 ]
{ bool: true,
ret_a: [ 2, 3 ],
a_sum: 5,
ret_b: [ 2, 3 ],
b_sum: 5 }
[ 2, 3 ] 5
----------
{ size: 6, sum: 13, min: -8, max: 15, n: 61, maxv: 23 }
false 0 61 [ 9, 8, 7, 3, 6, 28 ] -8 [ 9, 8, 7, 3, 6, 28 ]
false 1 61 [ 11, 9, 7, 4, 5, 25 ] -8 [ 11, 9, 7, 4, 5, 25 ]
true 2 13 [ 1, -1, 0, -2, 2, 13 ] -8 [ 9, 7, 8, 6, 10, 21 ]
{ bool: true,
ret_a: [ 9, 7, 8, 6, 10, 21 ],
a_sum: 61,
ret_b: [ 1, -1, 0, -2, 2, 13 ],
b_sum: 13 }
[ 1, -1, 0, -2, 2, 13 ] 13
----------
{ size: 6, sum: -13, min: -8, max: 15, n: 35, maxv: 23 }
true 0 -13 [ 0, -6, -1, -4, -7, 5 ] -8 [ 8, 2, 7, 4, 1, 13 ]
{ bool: true,
ret_a: [ 8, 2, 7, 4, 1, 13 ],
a_sum: 35,
ret_b: [ 0, -6, -1, -4, -7, 5 ],
b_sum: -13 }
[ 0, -6, -1, -4, -7, 5 ] -13
{ size: 6, sum: 0, min: -8, max: 15, n: 48, maxv: 23 }
true 0 0 [ -1, 0, -3, -2, -4, 10 ] -8 [ 7, 8, 5, 6, 4, 18 ]
{ bool: true,
ret_a: [ 7, 8, 5, 6, 4, 18 ],
a_sum: 48,
ret_b: [ -1, 0, -3, -2, -4, 10 ],
b_sum: 0 }
[ -1, 0, -3, -2, -4, 10 ] 0
/**
* not support unique, but will try make unique if can
* thx @SeverinPappadeux for int version
*
* @see https://stackoverflow.com/questions/53279807/how-to-get-random-number-list-with-fixed-sum-and-size
*/
export function coreFnRandSumInt(argv: ISumNumParameterWuthCache)
{
let {
random,
size,
sum,
min,
max,
} = argv;
let sum_1_to_size = sum_1_to_n(size);
sum = isUnset(sum) ? sum_1_to_size : sum;
expect(sum).integer();
min = isUnset(min) ? (sum > 0 ? 0 : sum) : min;
max = isUnset(max) ? Math.abs(sum) : max;
expect(min).integer();
expect(max).integer();
let n_sum = Math.abs(sum - size * min);
let maxv = max - min;
/*
console.log({
sum_1_to_size,
size,
sum,
min,
max,
n_sum,
maxv,
});
*/
if (sum > 0)
{
expect(sum).gt(min)
}
/**
* pre-check
*/
//expect(maxv, `(max - min) should > sum_1_to_size`).gte(sum_1_to_size);
/**
* probabilities
*/
let prob = get_prob(size, maxv);
expect(prob).is.array.lengthOf(size);
/**
* make rmultinom use with random.next
*/
let rmultinomFn = libRmath.Multinomial(fakeLibRmathRng(random.next)).rmultinom;
/**
* low value for speed up, but more chance fail
*/
let n_len = argv.limit || 5 || n_sum;
/**
* rebase number
*/
let n_diff: number = min;
const rmultinomCreateFn = (n_len: number) => {
return (rmultinomFn(n_len, n_sum, prob) as number[][])
.reduce((a, value) =>
{
let i = value.length;
let b_sum = 0;
let bool = false;
let unique_len = 0;
while(i--)
{
let v = value[i];
let n = v + n_diff;
if (value.indexOf(v) === i)
{
unique_len++;
}
if (n >= min && n <= max)
{
bool = true;
value[i] = n;
b_sum += n
}
else
{
bool = false;
break;
}
}
if (bool && b_sum === sum)
{
let item = {
value,
unique_len,
b_sum,
bool,
};
a.push(item)
}
return a
}, [] as {
value: number[],
unique_len: number,
b_sum: number,
bool: boolean,
}[])
.sort((a, b) => b.unique_len - a.unique_len)
;
};
/**
* pre-make fail-back value
*/
const cache_max = 10;
let cache: number[][] = [];
{
let len = 200;
let arr = array_unique(rmultinomCreateFn(len));
if (arr.length)
{
let i = Math.min(cache_max, arr.length);
while(i--)
{
cache.push(arr[i].value)
}
cache = array_unique(cache.map(v => v.sort()))
}
arr = undefined;
// console.log(cache);
}
/**
* try reset memory
*/
argv = undefined;
return () =>
{
let arr = rmultinomCreateFn(n_len);
let ret_b: number[];
let bool_toplevel: boolean;
let c_len = cache.length;
if (arr.length)
{
ret_b = arr[0].value;
bool_toplevel = arr[0].bool;
if (bool_toplevel && c_len < cache_max)
{
cache.push(ret_b);
}
}
else if (c_len)
{
let i = UtilDistributions.randIndex(random, c_len);
ret_b = cache[i];
bool_toplevel = true;
}
if (!bool_toplevel || !ret_b)
{
throw new Error(`can't generator value by current input argv, or try set limit for high number`)
}
return ret_b;
}
}