effi-find
Version:
Creates a function to choose an Item from a list in O(1)
193 lines (192 loc) • 6.82 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.chooserFactory = void 0;
const fluent_iterable_1 = require("@codibre/fluent-iterable");
function getGCD(value1, value2) {
if (value1 === undefined)
return value2;
while (value2 !== 0) {
const temp = value2;
value2 = value1 % value2;
value1 = temp;
}
if (value1 === 0) {
throw new RangeError('There is no GCD');
}
return value1;
}
const empty = Symbol('empty');
const INFINITY_BOUNDS = new Map([
[-1, Symbol('NEGATIVE_INFINITY')],
[1, Symbol('POSITIVE_INFINITY')],
]);
function prepareValue(value, multiplier) {
if (value === undefined)
return undefined;
return Math.round(value * multiplier);
}
class NormalizedList {
constructor(list, gcd, multiplier, init, end, halfDelimited, lowerBound, upperBound) {
this.list = list;
this.gcd = gcd;
this.multiplier = multiplier;
this.init = init;
this.end = end;
this.halfDelimited = halfDelimited;
this.lowerBound = lowerBound;
this.upperBound = upperBound;
this.multiplierSymbol = Symbol('multiplier');
this.current = 0;
this.pivot = empty;
this.multiple = 0;
this.iterator = this.list[Symbol.iterator]();
}
[Symbol.iterator]() {
return this;
}
next() {
if (this.item === undefined)
this.item = this.iterator.next();
if (this.item.done)
return { done: true, value: undefined };
const item = this.item.value;
if (!item)
return { done: false, value: item };
const { value } = item;
const init = value[this.init];
const end = item.value[this.end];
if (end === undefined || init === undefined) {
const halfDelimited = value[this.halfDelimited];
delete this.item.value[this.init];
delete this.item.value[this.end];
delete this.item.value[this.halfDelimited];
this.item = this.iterator.next();
return {
done: false,
value: {
value,
[this.multiplierSymbol]: halfDelimited
? INFINITY_BOUNDS.get(halfDelimited)
: empty,
},
};
}
if (this.pivot === empty || this.pivot !== value) {
this.pivot = value;
this.current = init;
this.multiple = Math.floor((init - this.lowerBound) / this.gcd);
}
if (end < this.current) {
delete this.item.value[this.init];
delete this.item.value[this.end];
this.item = this.iterator.next();
}
const result = {
done: false,
value: {
value,
[this.multiplierSymbol]: this.multiple,
},
};
this.current += this.gcd;
this.multiple++;
return result;
}
}
function getItem(base, initSymbol, endSymbol, init, multiplier, end) {
const item = base;
item[initSymbol] = prepareValue(item[init], multiplier);
item[endSymbol] = prepareValue(item[end], multiplier);
return item;
}
function partitionRange(original, list, init, end, precision = 2) {
const initSymbol = Symbol(init.toString());
const endSymbol = Symbol(end.toString());
const halfDelimited = Symbol('halfDelimited');
const multiplier = 10 ** precision;
let upperBound;
let gcd;
let lowerBound;
let lowestBound;
let highestBound;
for (const item of original) {
const current = getItem(item, initSymbol, endSymbol, init, multiplier, end);
if (!upperBound)
upperBound = current[initSymbol];
lowerBound = upperBound;
upperBound = current[initSymbol];
if (upperBound !== undefined && upperBound > lowerBound) {
gcd = getGCD(gcd, upperBound - lowerBound);
}
lowerBound = upperBound ?? Number.NEGATIVE_INFINITY;
upperBound = current[endSymbol] ?? Number.POSITIVE_INFINITY;
if (lowerBound === Number.NEGATIVE_INFINITY &&
upperBound === Number.POSITIVE_INFINITY) {
continue;
}
else if (lowerBound === Number.NEGATIVE_INFINITY) {
current[halfDelimited] = -1;
}
else if (upperBound === Number.POSITIVE_INFINITY) {
current[halfDelimited] = 1;
}
else {
if (lowestBound === undefined || lowestBound > lowerBound) {
lowestBound = lowerBound;
}
if (highestBound === undefined || highestBound < upperBound) {
highestBound = upperBound;
}
if (upperBound > lowerBound) {
gcd = getGCD(gcd, upperBound - lowerBound);
}
}
}
if (gcd === undefined) {
throw new RangeError('Could not find GCD');
}
return new NormalizedList(list, gcd, multiplier, initSymbol, endSymbol, halfDelimited, lowestBound ?? Number.MIN_SAFE_INTEGER, highestBound ?? Number.MAX_SAFE_INTEGER);
}
function chooserFactory(list, ...keys) {
let normalized = (0, fluent_iterable_1.fluent)(list).map((value) => ({
value,
}));
const getters = [];
const prepared = keys.map((key) => {
if (Array.isArray(key)) {
const iterator = partitionRange(list, normalized, key[0], key[1], key[2]);
const result = iterator.multiplierSymbol;
const { gcd, multiplier, lowerBound, upperBound } = iterator;
normalized = iterator;
getters.push((x) => {
if (x === undefined)
return empty;
const inScale = x * multiplier;
if (inScale < lowerBound)
return INFINITY_BOUNDS.get(-1);
if (inScale > upperBound)
return INFINITY_BOUNDS.get(1);
return Math.floor((inScale - lowerBound) / gcd);
});
return result;
}
getters.push(fluent_iterable_1.identity);
return (x) => x.value[key];
});
const tree = (0, fluent_iterable_1.fluent)(normalized).toObjectChainReduce(() => [], (acc, { value }) => {
acc.push(value);
return acc;
}, ...prepared);
return ((...args) => {
let result = tree;
for (let i = 0; i < args.length; i++) {
const item = args[i];
const getter = getters[i] ?? fluent_iterable_1.identity;
result = result[getter(item)];
if (result === undefined)
break;
}
return result ?? [];
});
}
exports.chooserFactory = chooserFactory;