aboutsummaryrefslogtreecommitdiff
path: root/src/util.ts
diff options
context:
space:
mode:
authorTerry Truong <terry06890@gmail.com>2022-03-28 12:23:26 +1100
committerTerry Truong <terry06890@gmail.com>2022-03-28 12:23:26 +1100
commit10ccee584417d51afc583484b692a8d7086a0d5f (patch)
treeaef1e0a286a19c927fc6d16c3efb154b8c5058f0 /src/util.ts
parente39f5ada10723dc1f5c29f32543051f90df03041 (diff)
Split lib.ts into layout.ts and util.ts
Diffstat (limited to 'src/util.ts')
-rw-r--r--src/util.ts88
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;
+}