UNPKG

@yookue/ts-multi-map

Version:

Multiple key/value map & range map for typescript

585 lines (583 loc) 16.1 kB
var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/util/RangeMap.ts var RangeMap_exports = {}; __export(RangeMap_exports, { RangeMap: () => RangeMap }); module.exports = __toCommonJS(RangeMap_exports); var import_object_hash = __toESM(require("object-hash")); var RangeMap = class { /** * Construct a range map instance * * @param entries the map entries * @param validation whether to compare the ranges to all the ranges previously, to determine if there are any conflicts * * @example * ```ts * const map = new RangeMap([ * [[1, 30], 'green'], * [[30, 60], 'blue'], * [[60, 90], 'orange'], * [[90, 100, true, true], 'red'] * ]); * ``` */ constructor(entries, validation = true) { this.keyMap = /* @__PURE__ */ new Map(); this.valueMap = /* @__PURE__ */ new Map(); entries == null ? void 0 : entries.forEach((entry) => { const [k, v] = entry; this.set(k, v, validation); }); } /** * Construct a range map instance * * @param entries the map entries * @param validation whether to compare the ranges to all the ranges previously, to determine if there are any conflicts * * @returns a range map instance * * @example * ```ts * const map = RangeMap.of([ * [[1, 30], 'green'], * [[30, 60], 'blue'], * [[60, 90], 'orange'], * [[90, 100, true, true], 'red'] * ]); * ``` */ static of(entries, validation = true) { return new RangeMap(entries, validation); } /** * Returns the value of the given key * * @param key the key to retrieve * @param defaults the default value if not found * * @returns the value of the given key * * @example * ```ts * const map = RangeMap.of([ * [[1, 50, true, true], 'white'], * [[51, 100, false, true], 'black'] * ]); * map.get([1, 50, true, true]); // 'white' * map.get([51, 100, false, true]); // 'black' * ``` */ get(key, defaults) { const hash = (0, import_object_hash.default)(this.toInternalKey(key)); return this.valueMap.get(hash) ?? defaults; } /** * Returns the value that associated to the number, by determining which bound contains the given number * * @param digit the number to retrieve * @param defaults the default value if not found * * @returns the value that associated to the number, by determining which bound contains the given number * * @example * ```ts * const map = RangeMap.of([ * [[1, 50, true, true], 'white'], * [[51, 100, false, true], 'black'] * ]); * map.getByDigit(25); // 'white' * map.getByDigit(50); // 'white' * map.getByDigit(51); // undefined * map.getByDigit(52); // 'black' * map.getByDigit(200); // undefined * ``` */ getByDigit(digit, defaults) { if (this.isEmpty()) { return defaults; } for (const [k, v] of this.keyMap.entries()) { if ((v.startInclusive ? digit >= v.start : digit > v.start) && (v.endInclusive ? digit <= v.end : digit < v.end)) { return this.valueMap.get(k); } } return defaults; } /** * Sets the value of the given key * * @param key the key to set * @param value the value to set * @param validation whether to compare the range to all the ranges previously, to determine if there are any conflicts * * @example * ```ts * map.set([60, 80, true, true], 'volcano'); * ``` */ set(key, value, validation = true) { const alias = this.toInternalKey(key); if (alias.start > alias.end || alias.start === alias.end && !alias.startInclusive && !alias.endInclusive) { throw RangeError(`Range start ${alias.start} must be less than or equal to it's end ${alias.end}`); } if (validation) { this.checkRangeConflict(alias); } const hash = (0, import_object_hash.default)(alias); this.keyMap.set(hash, alias); this.valueMap.set(hash, value); } /** * Clears the map */ clear() { this.keyMap.clear(); this.valueMap.clear(); } /** * Returns the keys of the map * * @returns the keys of the map */ keys() { return [...this.keyMap.values()]; } /** * Returns the value array of the map * * @returns the value array of the map */ values() { return [...this.valueMap.values()]; } /** * Returns the key/value entries of the map * * @returns the key/value entries of the map */ entries() { if (this.isEmpty()) { return []; } if (this.keyMap.size !== this.valueMap.size) { throw EvalError(`Internal maps size mismatch!`); } const result = []; for (const [k, v] of this.valueMap) { result.push([this.keyMap.get(k), v]); } return result; } /** * Deletes the entry with the given key * * @param key the key to delete * * @returns whether the entry has been deleted * * @example * ```ts * map.deleteByKey([1, 50]); * ``` */ deleteByKey(key) { if (this.isEmpty()) { return false; } const hash = (0, import_object_hash.default)(this.toInternalKey(key)); return this.keyMap.delete(hash) && this.valueMap.delete(hash); } /** * Deletes all the entries with the given keys * * @param keys the keys to delete * * @returns whether any of the entries has been deleted * * @example * ```ts * map.deleteByKeys([[1, 30], [30, 50]]); * ``` */ deleteByKeys(keys) { if (!keys.length || this.isEmpty()) { return false; } let result = false; for (const key of keys) { if (this.deleteByKey(key)) { result = true; } } return result; } /** * Deletes the entry/entries with the given value * * @param value the value to delete * * @returns whether the entry/entries has been deleted * * @example * ```ts * map.deleteByValue('red'); * ``` */ deleteByValue(value) { if (this.isEmpty()) { return false; } let result = false; for (const [k, v] of this.valueMap.entries()) { if (v === value && this.keyMap.delete(k) && this.valueMap.delete(k)) { result = true; } } return result; } /** * Deletes all the entries with the given values * * @param values the values to delete * * @returns whether any of the entries has been deleted * * @example * ```ts * map.deleteByValues(['green', 'blue']); * ``` */ deleteByValues(values) { if (!values.length || this.isEmpty()) { return false; } let result = false; for (const value of values) { if (this.deleteByValue(value)) { result = true; } } return result; } /** * Processes each entry in the map * * @param callback a callback function that processes each entry * @param thisArg any instance to retrieve 'this' reference in the callback function * * @example * ```ts * map.forEach((value, key) => { * console.log(value); * }); * ``` */ forEach(callback, thisArg) { this.entries().forEach((entry) => { const [k, v] = entry; callback(v, k); }, thisArg); } /** * Processes each entry in the map with index capability * * @param callback a callback function that processes each entry * @param thisArg any instance to retrieve 'this' reference in the callback function * * @example * ```ts * map.forEachIndexing((value, key, index) => { * console.log(index); * }); * ``` */ forEachIndexing(callback, thisArg) { let index = 0; this.entries().forEach((entry) => { const [k, v] = entry; callback(v, k, index++); }, thisArg); } /** * Processes each entry in the map with breakable capability * * @param callback a callback function that processes each entry. Returning false indicates to break the map iteration * @param thisArg any instance to retrieve 'this' reference in the callback function * * @example * ```ts * map.forEachBreakable((value, key) => { * return true; * }); * ``` */ forEachBreakable(callback, thisArg) { this.entries().forEach((entry) => { const [k, v] = entry; if (!callback(v, k)) { return; } }, thisArg); } /** * Returns whether the map contains the given key * * @param key the key to check * * @returns whether the map contains the given key * * @example * ```ts * map.hasKey([1, 30]); * ``` */ hasKey(key) { return this.isNotEmpty() && this.keyMap.has((0, import_object_hash.default)(this.toInternalKey(key))); } /** * Returns whether the map contains the given key/value pair * * @param key the key to check * @param value the value to check * * @returns whether the map contains the given key/value pair * * @example * ```ts * const map = RangeMap.of([ * [[1, 50], 'white'] * ]); * map.hasKeyValue([1, 50], 'white'); // true * map.hasKeyValue([1, 50], 'black'); // false * ``` */ hasKeyValue(key, value) { return this.isNotEmpty() && this.get(key) === value; } /** * Returns whether the map contains any of the given keys * * @param keys the keys to check * * @returns whether the map contains any of the given keys * * @example * ```ts * const map = RangeMap.of([ * [[1, 50, true, true], 'white'], * [[51, 100, false, true], 'black'] * ]); * map.hasAnyKeys([[1, 50, true, true], [20, 60]]); // true * ``` */ hasAnyKeys(keys) { return this.isNotEmpty() && keys.length > 0 && keys.some((item) => this.hasKey(item)); } /** * Returns whether the map contains all the given keys * * @param keys the keys to check * * @returns whether the map contains all the given keys * * @example * ```ts * map.hasAllKeys([[1, 50], [51, 100]]); * ``` */ hasAllKeys(keys) { return this.isNotEmpty() && keys.length > 0 && keys.every((item) => this.hasKey(item)); } /** * Returns whether any entries of the map that contains the given values * * @param value the value to check * * @returns whether any entries of the map that contains the given values * * @example * ```ts * const map = RangeMap.of([ * [[1, 50, true, true], 'white'], * [[51, 100, false, true], 'black'] * ]); * map.hasValue('white'); // true * map.hasValue('blue'); // false * ``` */ hasValue(value) { return this.isNotEmpty() && this.values().includes(value); } /** * Returns whether the map contains any of the given values * * @param values the values to check * * @returns whether the map contains any of the given values * * @example * ```ts * const map = RangeMap.of([ * [[1, 50, true, true], 'white'], * [[51, 100, false, true], 'black'] * ]); * map.hasAnyValues(['red', 'black']); // true * map.hasAnyValues(['top', 'right']); // false * ``` */ hasAnyValues(values) { return this.isNotEmpty() && values.length > 0 && values.some((item) => this.hasValue(item)); } /** * Returns whether the map contains all the given values, matching exactly * * @param values the values to check * * @returns whether the map contains all the given values, matching exactly * * @example * ```ts * map.hasAllValues(['red', 'green', 'blue']); * ``` */ hasAllValues(values) { return this.isNotEmpty() && values.length > 0 && values.every((item) => this.hasValue(item)); } /** * Returns whether the map is empty * * @returns whether the map is empty */ isEmpty() { return !this.valueMap.size; } /** * Returns whether the map is not empty * * @returns whether the map is not empty */ isNotEmpty() { return this.valueMap.size > 0; } /** * Response for returning the list of key/value to iterate * * @example * ```ts * for (const [key, value] of map) { * console.log(value); * } * ``` */ // @ts-ignore [Symbol.iterator]() { return this.entries()[Symbol.iterator](); } /** * Returns the size of map * * @returns the size of map */ get size() { return this.valueMap.size; } /** * Returns the string representation of the map identifier ('RangeMap') * * @returns the string representation of the map identifier */ get [Symbol.toStringTag]() { return "RangeMap"; } /** * Returns the string representation of the map elements * * @returns the string representation of the map elements * * @example * ```ts * const map = RangeMap.of([ * [[1, 50, true, true], 'white'], * [[51, 100, false, true], 'black'] * ]); * console.log(map.toString()); // '[start:1,end:50,startInclusive:true,endInclusive:true]:white;[start:51,end:100,startInclusive:false,endInclusive:true]:black' * ``` */ toString() { return [...this].map((entry) => { const [k, v] = entry; const ks = Object.keys(k).map((item) => `${item}:${k[item]}`).join(); return `[${ks}]:${v}`; }).join(";"); } /** * Converts the given key to internal one, for storage * * @param key the key to convert * * @returns the converted key */ toInternalKey(key) { return !Array.isArray(key) ? key : { start: key.length > 0 ? key[0] : 0, end: key.length > 1 ? key[1] : 0, startInclusive: key.length > 2 ? key[2] : true, endInclusive: key.length > 3 ? key[3] : false }; } /** * Checks the given key is conflicting with the ranges previously * * @param key the key to inspect */ checkRangeConflict(key) { if (this.isEmpty()) { return; } const keyExpress = `${key.startInclusive ? "[" : "("}${key.start},${key.end}${key.endInclusive ? "]" : ")"}`; for (const k of this.keyMap.values()) { const kExpress = `${k.startInclusive ? "[" : "("}${k.start},${k.end}${k.endInclusive ? "]" : ")"}`; if (key.start <= k.start && key.end >= k.end) { throw RangeError(`Range ${keyExpress} must not override the range ${kExpress}`); } const badStart = (key.startInclusive ? key.start >= k.start : key.start > k.start) && (key.startInclusive && k.endInclusive ? key.start <= k.end : key.start < k.end); const badEnd = (key.endInclusive && k.startInclusive ? key.end >= k.start : key.end > k.start) && key.end <= k.end; if (badStart || badEnd) { throw RangeError(`Range ${keyExpress} must not intersect the range ${kExpress}`); } } } }; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { RangeMap });