diff options
| author | Terry Truong <terry06890@gmail.com> | 2022-03-28 12:23:26 +1100 |
|---|---|---|
| committer | Terry Truong <terry06890@gmail.com> | 2022-03-28 12:23:26 +1100 |
| commit | 10ccee584417d51afc583484b692a8d7086a0d5f (patch) | |
| tree | aef1e0a286a19c927fc6d16c3efb154b8c5058f0 /src/util.ts | |
| parent | e39f5ada10723dc1f5c29f32543051f90df03041 (diff) | |
Split lib.ts into layout.ts and util.ts
Diffstat (limited to 'src/util.ts')
| -rw-r--r-- | src/util.ts | 88 |
1 files changed, 88 insertions, 0 deletions
diff --git a/src/util.ts b/src/util.ts new file mode 100644 index 0000000..79c2b5c --- /dev/null +++ b/src/util.ts @@ -0,0 +1,88 @@ +/* + * Contains commonly-used utility functions. + */ + +// Returns [0 ... len] +export function range(len: number){ + return [...Array(len).keys()]; +} +// Returns sum of array values +export function arraySum(array: number[]){ + return array.reduce((x,y) => x+y); +} +// Returns array copy with vals clipped to within [min,max], redistributing to compensate (returns null on failure) +export function limitVals(arr: number[], min: number, max: number){ + let vals = [...arr]; + let clipped = new Array(vals.length).fill(false); + let owedChg = 0; // Stores total change made after clipping values + while (true){ + // Clip values + for (let i = 0; i < vals.length; i++){ + if (clipped[i]){ + continue; + } + if (vals[i] < min){ + owedChg += vals[i] - min; + vals[i] = min; + clipped[i] = true; + } else if (vals[i] > max){ + owedChg += vals[i] - max; + vals[i] = max; + clipped[i] = true; + } + } + if (Math.abs(owedChg) < Number.EPSILON){ + return vals; + } + // Compensate for changes made + let indicesToUpdate = (owedChg > 0) ? + range(vals.length).filter(idx => vals[idx] < max) : + range(vals.length).filter(idx => vals[idx] > min); + if (indicesToUpdate.length == 0){ + return null; + } + for (let i of indicesToUpdate){ + vals[i] += owedChg / indicesToUpdate.length; + } + owedChg = 0; + } +} +// Usable to iterate through possible int arrays with ascending values in the range 0 to maxLen-1, starting with [0] + // eg: With maxLen 3, updates [0] to [0,1], then to [0,2], then [0,1,2], then null +export function updateAscSeq(seq: number[], maxLen: number){ + // Try increasing last element, then preceding elements, then extending the array + let i = seq.length - 1; + while (true){ + if (i > 0 && seq[i] < (maxLen - 1) - (seq.length - 1 - i)){ + seq[i]++; + return true; + } else if (i > 0){ + i--; + } else { + if (seq.length < maxLen){ + seq.push(0); + seq.splice(0, seq.length, ...range(seq.length)); + return true; + } else { + return false; + } + } + } +} +// Given a non-empty array of non-negative weights, returns an array index chosen with weighted pseudorandomness +// Returns null if array contains all zeros +export function randWeightedChoice(weights: number[]): number | null { + let thresholds = Array(weights.length); + let sum = 0; + for (let i = 0; i < weights.length; i++){ + sum += weights[i]; + thresholds[i] = sum; + } + let rand = Math.random(); + for (let i = 0; i < weights.length; i++){ + if (rand <= thresholds[i] / sum){ + return i; + } + } + return null; +} |
