-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.ts
73 lines (58 loc) · 1.68 KB
/
index.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
import { RNG } from '../../constants';
import { when } from '../../error/when';
import { isArray } from '../../filters/isArray';
import { WithRNGOption } from '../../types/WithRNGOption';
import {
ERROR_VALUES_IS_EMPTY,
ERROR_VALUES_HAS_NON_ARRAY_ITEM,
ERROR_VALUES_HAS_NON_FINITE_WEIGHT,
ERROR_NO_VALID_WEIGHTS,
} from './constants';
export type WeightedOptions = {} & WithRNGOption;
export type WeightedEntry<T = unknown> = [T, number];
/**
* Returns weighted random value from an array of values and weights.
*
* *Note:* current implementation is mostly based on same utility from
* Chance library (https://chancejs.com/).
*
* See https://github.com/chancejs/chancejs/blob/859e555e29725df471d8110ad73c303e8a7f03b3/chance.js#L720
*/
export const weighted = <T = unknown>(
values: WeightedEntry<T>[],
{ rng = RNG }: WeightedOptions = {}
): T => {
when(!values.length).drop(ERROR_VALUES_IS_EMPTY);
let sum = 0;
for (let i = 0; i < values.length; ++i) {
const value = values[i];
const weight = value[1];
when(!isArray(value)).drop(ERROR_VALUES_HAS_NON_ARRAY_ITEM);
when(!Number.isFinite(weight)).drop(
ERROR_VALUES_HAS_NON_FINITE_WEIGHT
);
if (weight > 0) {
sum += weight;
}
}
when(sum === 0).drop(ERROR_NO_VALID_WEIGHTS);
let selected = rng() * sum;
let total = 0;
let index = -1;
let lastGoodIndex = -1;
for (let i = 0; i < values.length; i++) {
const weight = values[i][1];
total += weight;
if (weight > 0) {
if (selected <= total) {
index = i;
break;
}
lastGoodIndex = i;
}
if (i === values.length - 1) {
index = lastGoodIndex;
}
}
return values[index][0];
};