UNPKG

@oazmi/kitchensink

Version:

a collection of personal utility functions

976 lines (975 loc) 66.3 kB
/** contains a set of common collections. * * @module */ import "./_dnt.polyfills.js"; import type { MaybePromiseLike, PrefixProps } from "./typedefs.js"; /** a very simple python-like `List`s class, that allows for in-between insertions, deletions, and replacements, to keep the list compact. * * TODO: add examples */ export declare class List<T> extends Array<T> { /** ensure that built-in class methods create a primitive `Array`, instead of an instance of this `List` class. * * > [!note] * > it is extremely important that we set the `[Symbol.species]` static property to `Array`, * > otherwise any Array method that creates another Array (such as `map` and `splice`) will create an instance of `List` instead of an `Array`. * > this will eventually become a huge hindrance in future computationally heavy subclasses of this class that utilize the splice often. * * related reading material: * - about the `Symbol.species` static property: [mdn link](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/species). * - about possible deprecation of this feature: [tc39 proposal link](https://github.com/tc39/proposal-rm-builtin-subclassing). * - about why use `Symbol.species` instead of `symbol_species` from "alias.ts": see the comment inside the body of {@link Deque[Symbol.iterator]}. */ static [Symbol.species]: ArrayConstructor; constructor(items?: Iterable<T>); /** inserts an item at the specified index, shifting all items ahead of it one position to the front. * * negative indices are also supported for indicating the position of the newly added item _after_ the array's length has incremented. * * @example * ```ts * import { assertEquals } from "jsr:@std/assert" * * const arr = new List([0, 1, 2, 3, 4]) * arr.insert(-1, 5) // similar to pushing * assertEquals([...arr], [0, 1, 2, 3, 4, 5]) * arr.insert(-2, 4.5) * assertEquals([...arr], [0, 1, 2, 3, 4, 4.5, 5]) * arr.insert(1, 0.5) * assertEquals([...arr], [0, 0.5, 1, 2, 3, 4, 4.5, 5]) * ``` */ insert(index: number, item: T): void; /** deletes an item at the specified index, shifting all items ahead of it one position to the back. * * negative indices are also supported for indicating the deletion index from the end of the array. * * @example * ```ts * import { assertEquals } from "jsr:@std/assert" * * const arr = new List([0, 0.5, 1, 2, 3, 4, 4.5, 5]) * arr.delete(-1) // similar to popping * assertEquals([...arr], [0, 0.5, 1, 2, 3, 4, 4.5]) * arr.delete(-2) * assertEquals([...arr], [0, 0.5, 1, 2, 3, 4.5]) * arr.delete(1) * assertEquals([...arr], [0, 1, 2, 3, 4.5]) * ``` */ delete(index: number): T | undefined; /** swap the position of two items by their index. * * if any of the two indices is out of bound, then appropriate number of _empty_ elements will be created to fill the gap; * similar to how index-based assignment works (i.e. `my_list[off_bound_index] = "something"` will increase `my_list`'s length). * * @example * ```ts * import { assertEquals } from "jsr:@std/assert" * * const arr = new List<string>(["0", "4", "2", "3", "1", "5", "6"]) * arr.swap(1, 4) * assertEquals(arr.slice(), ["0", "1", "2", "3", "4", "5", "6"]) * * // swapping elements with an out of bound index will create additional intermediate `empty` elements. * // moreover, the existing element that is swapped will have `undefined` put in its place instead of `empty`. * assertEquals(arr.length, 7) * arr.swap(5, 9) * assertEquals(arr.length, 10) * assertEquals(arr.slice(), ["0", "1", "2", "3", "4", undefined, "6", , , "5"]) // notice the empty entries. * ``` */ swap(index1: number, index2: number): void; /** get an item at the specified `index`. * * this is equivalent to using index-based getter: `my_list[index]`. */ get(index: number): T | undefined; /** sets the value at the specified index. * * prefer using this method instead of index-based assignment, because subclasses may additionally cary out more operations with this method. * for attaining compatibility between `List` and its subclasses, it would be in your best interest to use the `set` method. * - **not recommended**: `my_list[index] = "hello"` * - **preferred**: `my_list.set(index, "hello")` */ set(index: number, value: T): T; static from<T, U = T>(arrayLike: ArrayLike<T>, mapfn?: (v: T, k: number) => U, thisArg?: any): List<U>; static of<T>(...items: T[]): List<T>; } /** a specialized list that keeps track of the number of duplicates of each item in the list, similar to a reference counter. * * this class automatically updates the reference counter on any mutations to the list at `O(log(n))`, where `n` is the number of unique items. * * > [!note] * > note that you **must** use the {@link set} method for index-based assignment, otherwise the class will not be able track the changes made. * - **don't do**: `my_list[index] = "hello"` * - **do**: `my_list.set(index, "hello")` * * @example * ```ts * import { assertEquals } from "jsr:@std/assert" * * const * logs: string[] = [], * get_logs = (): string[] => { * // the `logs` are cleared once this function is called * return logs.splice(0, logs.length) * } * * class TrackedList<T> extends RcList<T> { * constructor(items?: T[]) { * super(items) * } * * protected override onAdded(item: T): void { * logs.push(`new item introduced: ${item}`) * } * * protected override onDeleted(item: T): void { * logs.push(`item completely removed: ${item}`) * } * } * * const list = new TrackedList<number>() * list.push(1, 2, 2, 3) * assertEquals(get_logs(), ["new item introduced: 1", "new item introduced: 2", "new item introduced: 3"]) * * list.pop() // removes the `3` * assertEquals(get_logs(), ["item completely removed: 3"]) * * list.splice(0, 1) // removes the `1` * assertEquals(get_logs(), ["item completely removed: 1"]) * * list.unshift(4, 4, 5) * assertEquals(get_logs(), ["new item introduced: 4", "new item introduced: 5"]) * * list.shift() // removes the first `4`, but another copy still exists, so it shouldn't log anything * assertEquals(get_logs(), []) * * list.shift() // removes the second `4`, and now, all copies of `4` have been removed * assertEquals(get_logs(), ["item completely removed: 4"]) * * list.set(1, 6) // replaces the first `2` with `6` * assertEquals(get_logs(), ["new item introduced: 6"]) * * list.set(2, 7) // replaces the other `2` with `7` * assertEquals(get_logs(), ["new item introduced: 7", "item completely removed: 2"]) * * assertEquals([...list], [5, 6, 7]) * * list.set(99, 9999) // set `list[99] = 9999`, and extends the length of the list to `100` * assertEquals(get_logs(), ["new item introduced: 9999"]) * * // the reference counter of `undefined` is now `96`, because the length of the list was extended by `97` elements, * // and the final element (index `99`) was assigned the value of `9999`. * // we can get the reference count of a certain value using the `getRc` method. * assertEquals(list.getRc(undefined as any), 96) * assertEquals(list.getRc(5), 1) * assertEquals(list.getRc(6), 1) * assertEquals(list.getRc(7), 1) * * // note that `onAdded` is not called for `undefined` elements that are introduced as a consequence of the list extending after assignment. * // but `onAdded` will be called when the user _actually_ inserts an `undefined` element via direct mutation methods. * ``` */ export declare class RcList<T> extends List<T> { /** the reference counting `Map`, that bookkeeps the multiplicity of each item in the list. */ protected readonly rc: Map<T, number>; /** get the reference count (multiplicity) of a specific item in the list. * * note that the reference count for a non-existing item is `undefined` instead of `0`. */ readonly getRc: (key: T) => number | undefined; /** set the reference count of a specific item in the list. */ protected readonly setRc: (key: T, value: number) => Map<T, number>; /** delete the reference counting of a specific item in the list. a `true` is returned if the item did exist in {@link rc}, prior to deletion. */ protected readonly delRc: (key: T) => boolean; constructor(items?: Iterable<T>); /** this overridable method gets called when a new unique item is determined to be added to the list. * * this method is called _before_ the item is actually added to the array, but it is executed right _after_ its reference counter has incremented to `1`. * * > [!note] * > avoid accessing or mutating the array itself in this method's body (consider it an undefined behavior). * * @param item the item that is being added. */ protected onAdded(item: T): void; /** this overridable method gets called when a unique item (reference count of 1) is determined to be removed from the list. * * this method is called _before_ the item is actually removed from the array, but it is executed right _after_ its reference counter has been deleted. * * > [!note] * > avoid accessing or mutating the array itself in this method's body (consider it an undefined behavior). * * @param item the item that is being removed. */ protected onDeleted(item: T): void; /** increments the reference count of each item in the provided array of items. * * @param items the items whose counts are to be incremented. */ protected incRcs(...items: T[]): void; /** decrements the reference count of each item in the provided array of items. * * @param items the items whose counts are to be decremented. */ protected decRcs(...items: T[]): void; push(...items: T[]): number; pop(): T | undefined; shift(): T | undefined; unshift(...items: T[]): number; splice(start: number, deleteCount?: number, ...items: T[]): T[]; swap(index1: number, index2: number): void; /** sets the value at the specified index, updating the counter accordingly. * * always use this method instead of index-based assignment, because the latter is not interceptable (except when using proxies): * - **don't do**: `my_list[index] = "hello"` * - **do**: `my_list.set(index, "hello")` */ set(index: number, value: T): T; static from<T, U = T>(arrayLike: ArrayLike<T>, mapfn?: (v: T, k: number) => U, thisArg?: any): RcList<U>; static of: <T>(...items: T[]) => RcList<T>; } /** a resizable double-ended circular queue, similar to python's `collection.deque`. * * @example * ```ts * import { assertEquals } from "jsr:@std/assert" * * const deque = new Deque<number>(5) * * // pushing to the front * deque.pushFront(1, 2) * assertEquals(deque.getFront(), 2) * * // pushing to the rear * deque.pushBack(0, -1) * assertEquals(deque.getBack(), -1) * assertEquals(deque.getFront(), 2) * * // iterating over the queue, starting from the rear-most element to the front most * assertEquals([...deque], [-1, 0, 1, 2]) * * // popping the front and rear * assertEquals(deque.popFront(), 2) * assertEquals(deque.popBack(), -1) * assertEquals([...deque], [0, 1]) * * // pushing more items into the deque than its capacity (which is `5` elements) removes elements from the other end * deque.pushFront(2, 3, 4, 5, 6) * assertEquals([...deque], [2, 3, 4, 5, 6]) // the two rear-most elements have been removed * deque.pushBack(1) * assertEquals([...deque], [1, 2, 3, 4, 5]) // the front-most element has been removed * * // rotating the deque when its capacity is full * deque.rotate(2) // rotate forward/to-the-right by 2 steps * assertEquals([...deque], [4, 5, 1, 2, 3]) * deque.rotate(-1) // rotate backwards/to-the-left by 1 step * assertEquals([...deque], [5, 1, 2, 3, 4]) * deque.rotate(11) // rotating forward by 11 steps is equivalent to 1 forward step * assertEquals([...deque], [4, 5, 1, 2, 3]) * * // rotating the deque when it is partially filled * deque.popBack() * deque.popBack() * assertEquals([...deque], [1, 2, 3]) * deque.rotate(1) // rotate forward by 1 step * assertEquals([...deque], [3, 1, 2]) * deque.rotate(-2) // rotate backwards by 2 steps * assertEquals([...deque], [2, 3, 1]) * deque.rotate(-5) // rotate backwards by 5 steps, which is equivalent to 2 backward steps * assertEquals([...deque], [1, 2, 3]) * * // reversing the ordering of a partially filled deque * deque.reverse() * assertEquals([...deque], [3, 2, 1]) * * // reversing the ordering of a completely filled deque * deque.pushBack(4, 5) * assertEquals([...deque], [5, 4, 3, 2, 1]) * deque.reverse() * assertEquals([...deque], [1, 2, 3, 4, 5]) * * // acquiring elements through indexing using the `at` method * assertEquals(deque.at(0), 1) * assertEquals(deque.at(-1), 5) // negative indices are supported * assertEquals(deque.at(-2), 4) * assertEquals(deque.at(2), 3) * assertEquals(deque.at(11), 2) // overflowing indices are also supported * assertEquals(deque.at(-9), 2) // negative overflowing indices are supported as well * * // making the deque only partially filled * deque.popFront() * deque.popFront() * assertEquals([...deque], [1, 2, 3]) * * // indexing using the `at` method will return `undefined` if the deque is partially filled, and the given index slot is empty. * // this is because the index provided to the `at` method circulates (i.e. modulo) around the `length` of the deque, * // as opposed to its current element `count` amount. * assertEquals(deque.at(1), 2) * assertEquals(deque.at(-1), undefined) * assertEquals(deque.at(-2), undefined) * assertEquals(deque.at(-3), 3) * assertEquals(deque.at(4), undefined) * assertEquals(deque.at(5), 1) * assertEquals(deque.at(6), 2) * assertEquals(deque.at(11), 2) * assertEquals(deque.at(-8), 3) * * // to acquire items based on the index that circulates around the current element `count` amount (instead of `length`), use the `seek` method. * assertEquals(deque.seek(1), 2) * assertEquals(deque.seek(-1), 3) * assertEquals(deque.seek(-2), 2) * assertEquals(deque.seek(-3), 1) * assertEquals(deque.seek(4), 2) * assertEquals(deque.seek(5), 3) * assertEquals(deque.seek(6), 1) * assertEquals(deque.seek(11), 3) * assertEquals(deque.seek(-8), 2) * * // to replace an existing item with a new one, using the `seek` index, use the `replace` method * assertEquals([...deque], [1, 2, 3]) * deque.replace(0, 9) * deque.replace(10, 8) * deque.replace(-1, 7) * assertEquals([...deque], [9, 8, 7]) * * // to insert in-between elements, use the `insert` method * deque.insert(-1, 6, 5, 4, 3) * assertEquals([...deque], [9, 8, 7, 6, 5]) // the excess elements `4` and `3` cannot be inserted, since the length capacity of 5 has been reached. * deque.insert(-4, 77, 66) * assertEquals([...deque], [9, 8, 77, 66, 7]) * deque.insert(1, 88) * assertEquals([...deque], [9, 88, 8, 77, 66]) * * // to resize the deque, use the `resize` method * assertEquals(deque.length, 5) * deque.resize(8) * assertEquals(deque.length, 8) * deque.insert(0, 99, 98, 97, 96) * assertEquals([...deque], [99, 98, 97, 96, 9, 88, 8, 77]) * deque.resize(3) // if you resize to a shorter length, then you'll lose the excess elements from the front. * assertEquals([...deque], [99, 98, 97]) * deque.resize(5) * assertEquals([...deque], [99, 98, 97]) * deque.pushFront(96, 95, 94) * assertEquals([...deque], [98, 97, 96, 95, 94]) * ``` */ export declare class Deque<T> { readonly length: number; private items; private front; private back; count: number; /** a double-ended circular queue, similar to python's `collection.deque`. * * @param length specify the maximum length of the queue. * pushing more items than the length will remove the items from the opposite side, so as to maintain the size. */ constructor(length: number); /** iterate over the items in this deque, starting from the rear-most item, and ending at the front-most item. */ [Symbol.iterator](): Iterator<T>; /** inserts one or more items to the rear of the deque. * * if the deque is full, it will remove the front item before adding a new item. */ pushBack(...items: T[]): void; /** inserts one or more items to the front of the deque. * * if the deque is full, it will remove the rear item before adding a new item. */ pushFront(...items: T[]): void; /** get the item at the back of the deque without removing/popping it. */ getBack(): T | undefined; /** get the item at the front of the deque without removing/popping it. */ getFront(): T | undefined; /** removes/pops the item at the back of the deque and returns it. */ popBack(): T | undefined; /** removes/pops the item at the front of the deque and returns it. */ popFront(): T | undefined; /** rotates the deque `steps` number of positions to the right. * * if `steps` is negative, then it will rotate in the left direction. * when the deque is not empty, rotating with `step = 1` is equivalent to `this.pushBack(this.popFront())` */ rotate(steps: number): void; /** reverses the order of the items in the deque. */ reverse(): void; /** normalize the internal `items` array so that it beings with the first element of the deque. * * this method effectively makes it so that `this.back` becomes `this.length - 1`, and `this.front` becomes `this.count`. * this is useful for when you'd like to carry out a slightly complex re-indexing or mutation task on `this.items`, * but don't want to compute the indexes at every iteration of the subtasks involved. */ private normalize; /** provide an index relative to `this.back + 1`, and get the appropriate resolved index `i` that can be used to retrieve `this.items[i]`. * * example: * - given that a `deque` has a `length` of `5` and a `count` of `3` (i.e. carrying three elements), then: * - `deque.items[deque.resolveIndex(0)] === "rear-most element of the deque"` * - `deque.items[deque.resolveIndex(-1)] === "fifth element ahead of the rear of the deque"` * - `deque.items[deque.resolveIndex(5)] === "fifth element ahead of the rear of the deque"` * - `deque.items[deque.resolveIndex(6)] === "rear-most element of the deque"` */ private resolveIndex; /** provide an index relative to `this.back + 1`, and get the resolved seek-index `i` that is always within the current {@link count} amount of elements. * the returned resolved index `i` can be used to retrieve the element at that index by using `this.items[i]`. * * example: * - given that a `deque` has a `length` of `5` and a `count` of `3` (i.e. carrying three elements), then: * - `deque.items[deque.resolveSeekIndex(0)] === "rear-most element of the deque"` * - `deque.items[deque.resolveSeekIndex(-1)] === "third element ahead of the rear of the deque"` * - `deque.items[deque.resolveSeekIndex(2)] === "third element ahead of the rear of the deque"` * - `deque.items[deque.resolveSeekIndex(3)] === "rear-most element of the deque"` */ private resolveSeekIndex; /** returns the item at the specified index, relative to the rear of the deque. * * if the capacity (element {@link count}) of this deque is not full, * then you may receive `undefined` when you provide an index where an empty element exists. * in other words, this method is not aware of the number of elements currently stored in the deque. * * to obtain an element that is _always_ within the current partial capacity limit, use the {@link seek} method instead. * * @param index The index of the item to retrieve, relative to the rear-most element. * @returns The item at the specified index, or `undefined` if the index is out of range with respect to the current {@link count} number of items. */ at(index: number): T | undefined; /** returns the item at the specified index, relative to the rear of the deque, * ensuring that the index circulates back if it goes off the current item {@link count} amount. * * if the capacity (element {@link count}) of this deque is not full, * then you may receive `undefined` when you provide an index where an empty element exists. * in other words, this method is not aware of the number of elements currently stored in the deque. * * to obtain an element that is _always_ within the current partial capacity limit, use the {@link seek} method instead. * * @param seek_index The index of the item to retrieve, relative to the rear-most element. * @returns The item at the specified index (within the element {@link count} amount of this deque), or `undefined` if there are absolutely no items in the deque. */ seek(seek_index: number): T | undefined; /** replaces the item at the specified index with a new item, always ensuring the index is bound to the current element {@link count} amount * (as opposed the the full deque {@link length}), so that unoccupied element slots are **not** replaced. * i.e. only existing items can be replaced. */ replace(seek_index: number, item: T): void; /** inserts additional items at the specified seek-index, shifting all items ahead of it to the front. * if the deque is full, it removes the front item before adding the new additional items. * * ~~TODO: current implementation is incomplete, because it involves too many index computations, and I'm too lazy for that. * plus, president biden is going to drop the "ball" in times square today on new year's eve. * obviously I wouldn't want to miss this historic moment. /s~~ * in place of a lackluster "ball drop", we got a much more exciting thunder show from the Almighty Himself! */ insert(seek_index: number, ...insert_items: T[]): void; resize(new_length: number): void; } /** invert a `Map<F, Set<R>>` to `Map<R, Set<F>>`. */ export declare const invertMap: <F, R>(forward_map: Map<F, Set<R>>) => Map<R, Set<F>>; export type InvertibleMapBase<K, V> = Map<K, Set<V>> & Omit<PrefixProps<Map<V, Set<K>>, "r">, "rclear" | "rset"> & { rset: (key: V, value: Iterable<K>) => InvertibleMapBase<K, V>; }; /** an invertible map maintains a bidirectional one-to-many mapping between `keys` (of kind `K`) and collection of values (of kind `Set<V>`). <br> * the reverse mapping is also a one-to-many between `keys` (of kind `V`) and collection of values (of kind `Set<K>`). <br> * the dual map model of this class allows for quick lookups and mutations both directions. <br> * this data structure highly resembles a directed graph's edges. <br> * * @typeParam K the type of keys in the forward map * @typeParam V the type of values in the reverse map * * @example * ```ts * import { assertEquals } from "jsr:@std/assert" * * const bimap = new InvertibleMap<number, string>() * * // add values to the forward map * bimap.add(1, "one", "first") * bimap.add(2, "two", "second") * * // add values to the reverse map * bimap.radd("second", 3, 4, 5) * * // perform lookups in both directions * assertEquals(bimap.get(1), new Set(["one", "first"])) * assertEquals(bimap.rget("second"), new Set([2, 3, 4, 5])) * * // remove entries while maintaining invertibility * bimap.delete(6) // `false` because the key never existed * bimap.delete(2) // `true` * assertEquals(bimap.rget("second"), new Set([3, 4, 5])) * bimap.rremove("second", 4, 5, 6, 7) * assertEquals(bimap.rget("second"), new Set([3])) * * // iterate over the forward map * const bimap_entries: [key: number, values: string[]][] = [] * for (const [k, v] of bimap) { bimap_entries.push([k, [...v]]) } * assertEquals(bimap_entries, [ * [1, ["one", "first"]], * [3, ["second"]], * [4, []], * [5, []], * ]) * * // clear the entire bidirectional map * bimap.clear() * assertEquals([...bimap.entries()], []) * ``` */ export declare class InvertibleMap<K, V> implements InvertibleMapBase<K, V> { /** forward mapping. not intended for direct access, since manually mutating it will ruin the invertibility with the reverse map if you're not careful. */ fmap: Map<K, Set<V>>; /** reverse mapping. not intended for direct access, since manually mutating it will ruin the invertibility with the forward map if you're not careful. */ rmap: Map<V, Set<K>>; /** size of the forward map */ size: number; /** size of the reverse map */ rsize: number; /** at a specific `key` in the forward map, add the list of `items`, * and then also assign `key` to the list of items in the reverse map to maintain invertibility. */ add: (key: K, ...items: V[]) => void; /** at a specific `key` in the reverse map, add the list of `items`, * and then also assign `key` to the list of items in the forward map to maintain invertibility. */ radd: (key: V, ...items: K[]) => void; /** clear out both forward and reverse maps completely of all their entries */ clear: () => void; /** delete a `key` in the forward map, and also remove its mentions from the reverse map's entries. <br> * if `keep_key` is optionally set to `true`, we will only clear out the set of items held by the forward map at the key, * and keep the key itself intact (along with the original (now mutated and clear) `Set<V>` which the key refers to) */ delete: (key: K, keep_key?: boolean) => boolean; /** delete a `key` in the reverse map, and also remove its mentions from the forward map's entries. <br> * if `keep_key` is optionally set to `true`, we will only clear out the set of items held by the reverse map at the key, * and keep the key itself intact (along with the original (now mutated and clear) `Set<V>` which the key refers to) */ rdelete: (key: V, keep_key?: boolean) => boolean; /** at a specific `key` in the forward map, remove/delete the list of `items`, * and then also remove `key` from the list of items in the reverse map to maintain invertibility. */ remove: (key: K, ...items: V[]) => void; /** at a specific `key` in the reverse map, remove/delete the list of `items`, * and then also remove `key` from the list of items in the forward map to maintain invertibility. */ rremove: (key: V, ...items: K[]) => void; forEach: (callbackfn: (value: Set<V>, key: K, map: Map<K, Set<V>>) => void, thisArg?: any) => void; rforEach: (callbackfn: (value: Set<K>, key: V, map: Map<V, Set<K>>) => void, thisArg?: any) => void; get: (key: K) => Set<V> | undefined; rget: (key: V) => Set<K> | undefined; has: (key: K) => boolean; rhas: (key: V) => boolean; set: (key: K, value: Iterable<V>) => this; rset: (key: V, value: Iterable<K>) => this; entries: Map<K, Set<V>>["entries"]; rentries: Map<V, Set<K>>["entries"]; keys: Map<K, V>["keys"]; rkeys: Map<V, K>["keys"]; values: Map<K, Set<V>>["values"]; rvalues: Map<V, Set<K>>["values"]; [Symbol.iterator]: Map<K, Set<V>>["entries"]; [Symbol.toStringTag]: string; /** create an empty invertible map. <br> * optionally provide an initial `forward_map` to populate the forward mapping, and then automatically deriving the reverse mapping from it. <br> * or provide an initial `reverse_map` to populate the reverse mapping, and then automatically deriving the froward mapping from it. <br> * if both `forward_map` and `reverse_map` are provided, then it will be up to YOU to make sure that they are actual inverses of each other. <br> * @param forward_map initiallize by populating with an optional initial forward map (the reverse map will be automatically computed if `reverse_map === undefined`) * @param reverse_map initiallize by populating with an optional initial reverse map (the forward map will be automatically computed if `forward_map === undefined`) */ constructor(forward_map?: Map<K, Set<V>> | undefined, reverse_map?: Map<V, Set<K>> | undefined); } /** a directed acyclic graph edges mapping optimized for looking up number of connections going **into** and **out of** a node */ export type GraphEdges<ID, FROM extends ID = ID, TO extends ID = ID> = Map<FROM, Set<TO>>; export declare class TopologicalScheduler<ID, FROM extends ID = ID, TO extends ID = ID> { /** the edges depict the directed edge from an id (`key: FROM`) to a set of ids (`value: Set<TO>`) */ readonly edges: GraphEdges<ID, FROM, TO>; /** after a source id is fired, this stack will get filled up with dependent node ids in topological order. <br> * the top item in the stack will correspond to the first node id that must be processed (least dependency), * while the bottom one will be the last to be resolved (most dependencies). <br> * use the {@link pop} method to pop out the top from this stack, or use the {@link seek} method just to view the top without popping. */ readonly stack: ID[]; /** declare ids that need to be fired simultaneously. * once the ids are fired, the function will topologically traverse the {@link edges} (via DFS), * and eventually push the order of resoluion into the {@link stack}. <br> * make sure that the source ids are NOT dependent on one another, because that will break the topological ordering of the output stack. */ fire: (...source_ids: FROM[]) => void; /** while processing topologically ordered {@link stack}, you may block certain ids so that they and * their (pure) dependents down the line are removed from the {@link stack}. <br> * if no id is provided (no arguments), then we will assume that you wish to block the most recently popped id. */ block: (...block_ids: FROM[] | never[]) => void; /** clear the topologically ordered {@link stack}, perhaps to restart the traversal. */ clear: () => void; /** pop the top element from the topologically ordered {@link stack}. */ pop: () => ID | undefined; /** view the top element from the topologically ordered {@link stack} without popping it. */ seek: () => ID | undefined; /** iterate over the topologically ordered {@link stack} of ids */ [Symbol.iterator]: () => IterableIterator<ID>; constructor(edges: GraphEdges<ID, FROM, TO>); } export type InvertibleGraphEdges<ID extends PropertyKey, FROM extends ID = ID, TO extends ID = ID> = InvertibleMap<FROM, TO>; export declare class TopologicalAsyncScheduler<ID extends PropertyKey, FROM extends ID = ID, TO extends ID = ID> { pending: Set<TO>; clear: () => void; fire: (...source_ids: FROM[]) => void; resolve: (...ids: ID[]) => TO[]; constructor(invertible_edges: InvertibleGraphEdges<ID, FROM, TO>); } /** definition of an object that provides map-like methods */ export interface SimpleMap<K, V> { get(key: K): V | undefined; set(key: K, value: V): this; has(key: K): boolean; delete(key: K): boolean; } /** a map like object, similar to a {@link WeakMap}, that weakly stores keys of Objects and Functions, * but can also (strongly) store primitive objects as keys, similar to {@link Map}. hence the name, `HybridWeakMap` <br> */ export declare class HybridWeakMap<K, V> implements SimpleMap<K, V> { wmap: WeakMap<K & WeakKey, V>; smap: Map<K & PropertyKey, V>; private pick; get(key: K): V | undefined; set(key: K, value: V): this; has(key: K): boolean; delete(key: K): boolean; } /** a tree object (constructed by class returned by {@link treeClass_Factory}) with no initialized value will have this symbol set as its default value */ export declare const TREE_VALUE_UNSET: unique symbol; export declare const treeClass_Factory: (base_map_class: new <KT, VT>(...args: any[]) => SimpleMap<KT, VT>) => { new <K, V>(value?: V | typeof TREE_VALUE_UNSET): { value: V | typeof TREE_VALUE_UNSET; getDeep(reverse_keys: K[], create_intermediate?: true): { value: any; getDeep(reverse_keys: K[], create_intermediate?: true): any; getDeep(reverse_keys: K[], create_intermediate?: boolean): any | undefined; setDeep<T>(reverse_keys: K[], value: T, create_intermediate?: true): any; setDeep<T>(reverse_keys: K[], value: T, create_intermediate?: boolean): any | undefined; /** check if a deep child exists with the provided array of reversed keys. <br> * this is implemented to be slightly quicker than {@link getDeep} */ hasDeep(reverse_keys: K[]): boolean; delDeep(reverse_keys: K[]): boolean; get(key: K): any | undefined; set(key: K, value: any): any; has(key: K): boolean; delete(key: K): boolean; }; getDeep(reverse_keys: K[], create_intermediate?: boolean): { value: any; getDeep(reverse_keys: K[], create_intermediate?: true): any; getDeep(reverse_keys: K[], create_intermediate?: boolean): any | undefined; setDeep<T>(reverse_keys: K[], value: T, create_intermediate?: true): any; setDeep<T>(reverse_keys: K[], value: T, create_intermediate?: boolean): any | undefined; /** check if a deep child exists with the provided array of reversed keys. <br> * this is implemented to be slightly quicker than {@link getDeep} */ hasDeep(reverse_keys: K[]): boolean; delDeep(reverse_keys: K[]): boolean; get(key: K): any | undefined; set(key: K, value: any): any; has(key: K): boolean; delete(key: K): boolean; } | undefined; setDeep<T>(reverse_keys: K[], value: T, create_intermediate?: true): { value: any; getDeep(reverse_keys: K[], create_intermediate?: true): any; getDeep(reverse_keys: K[], create_intermediate?: boolean): any | undefined; setDeep<T>(reverse_keys: K[], value: T, create_intermediate?: true): any; setDeep<T>(reverse_keys: K[], value: T, create_intermediate?: boolean): any | undefined; /** check if a deep child exists with the provided array of reversed keys. <br> * this is implemented to be slightly quicker than {@link getDeep} */ hasDeep(reverse_keys: K[]): boolean; delDeep(reverse_keys: K[]): boolean; get(key: K): any | undefined; set(key: K, value: any): any; has(key: K): boolean; delete(key: K): boolean; }; setDeep<T>(reverse_keys: K[], value: T, create_intermediate?: boolean): { value: any; getDeep(reverse_keys: K[], create_intermediate?: true): any; getDeep(reverse_keys: K[], create_intermediate?: boolean): any | undefined; setDeep<T>(reverse_keys: K[], value: T, create_intermediate?: true): any; setDeep<T>(reverse_keys: K[], value: T, create_intermediate?: boolean): any | undefined; /** check if a deep child exists with the provided array of reversed keys. <br> * this is implemented to be slightly quicker than {@link getDeep} */ hasDeep(reverse_keys: K[]): boolean; delDeep(reverse_keys: K[]): boolean; get(key: K): any | undefined; set(key: K, value: any): any; has(key: K): boolean; delete(key: K): boolean; } | undefined; /** check if a deep child exists with the provided array of reversed keys. <br> * this is implemented to be slightly quicker than {@link getDeep} */ hasDeep(reverse_keys: K[]): boolean; delDeep(reverse_keys: K[]): boolean; get(key: K): { value: any; getDeep(reverse_keys: K[], create_intermediate?: true): any; getDeep(reverse_keys: K[], create_intermediate?: boolean): any | undefined; setDeep<T>(reverse_keys: K[], value: T, create_intermediate?: true): any; setDeep<T>(reverse_keys: K[], value: T, create_intermediate?: boolean): any | undefined; /** check if a deep child exists with the provided array of reversed keys. <br> * this is implemented to be slightly quicker than {@link getDeep} */ hasDeep(reverse_keys: K[]): boolean; delDeep(reverse_keys: K[]): boolean; get(key: K): any | undefined; set(key: K, value: any): any; has(key: K): boolean; delete(key: K): boolean; } | undefined; set(key: K, value: { value: any; getDeep(reverse_keys: K[], create_intermediate?: true): any; getDeep(reverse_keys: K[], create_intermediate?: boolean): any | undefined; setDeep<T>(reverse_keys: K[], value: T, create_intermediate?: true): any; setDeep<T>(reverse_keys: K[], value: T, create_intermediate?: boolean): any | undefined; /** check if a deep child exists with the provided array of reversed keys. <br> * this is implemented to be slightly quicker than {@link getDeep} */ hasDeep(reverse_keys: K[]): boolean; delDeep(reverse_keys: K[]): boolean; get(key: K): any | undefined; set(key: K, value: any): any; has(key: K): boolean; delete(key: K): boolean; }): any; has(key: K): boolean; delete(key: K): boolean; }; }; export declare const WeakTree: { new <K, V>(value?: V | typeof TREE_VALUE_UNSET): { value: V | typeof TREE_VALUE_UNSET; getDeep(reverse_keys: K[], create_intermediate?: true): { value: any; getDeep(reverse_keys: K[], create_intermediate?: true): any; getDeep(reverse_keys: K[], create_intermediate?: boolean): any | undefined; setDeep<T>(reverse_keys: K[], value: T, create_intermediate?: true): any; setDeep<T>(reverse_keys: K[], value: T, create_intermediate?: boolean): any | undefined; /** check if a deep child exists with the provided array of reversed keys. <br> * this is implemented to be slightly quicker than {@link getDeep} */ hasDeep(reverse_keys: K[]): boolean; delDeep(reverse_keys: K[]): boolean; get(key: K): any | undefined; set(key: K, value: any): any; has(key: K): boolean; delete(key: K): boolean; }; getDeep(reverse_keys: K[], create_intermediate?: boolean): { value: any; getDeep(reverse_keys: K[], create_intermediate?: true): any; getDeep(reverse_keys: K[], create_intermediate?: boolean): any | undefined; setDeep<T>(reverse_keys: K[], value: T, create_intermediate?: true): any; setDeep<T>(reverse_keys: K[], value: T, create_intermediate?: boolean): any | undefined; /** check if a deep child exists with the provided array of reversed keys. <br> * this is implemented to be slightly quicker than {@link getDeep} */ hasDeep(reverse_keys: K[]): boolean; delDeep(reverse_keys: K[]): boolean; get(key: K): any | undefined; set(key: K, value: any): any; has(key: K): boolean; delete(key: K): boolean; } | undefined; setDeep<T>(reverse_keys: K[], value: T, create_intermediate?: true): { value: any; getDeep(reverse_keys: K[], create_intermediate?: true): any; getDeep(reverse_keys: K[], create_intermediate?: boolean): any | undefined; setDeep<T>(reverse_keys: K[], value: T, create_intermediate?: true): any; setDeep<T>(reverse_keys: K[], value: T, create_intermediate?: boolean): any | undefined; /** check if a deep child exists with the provided array of reversed keys. <br> * this is implemented to be slightly quicker than {@link getDeep} */ hasDeep(reverse_keys: K[]): boolean; delDeep(reverse_keys: K[]): boolean; get(key: K): any | undefined; set(key: K, value: any): any; has(key: K): boolean; delete(key: K): boolean; }; setDeep<T>(reverse_keys: K[], value: T, create_intermediate?: boolean): { value: any; getDeep(reverse_keys: K[], create_intermediate?: true): any; getDeep(reverse_keys: K[], create_intermediate?: boolean): any | undefined; setDeep<T>(reverse_keys: K[], value: T, create_intermediate?: true): any; setDeep<T>(reverse_keys: K[], value: T, create_intermediate?: boolean): any | undefined; /** check if a deep child exists with the provided array of reversed keys. <br> * this is implemented to be slightly quicker than {@link getDeep} */ hasDeep(reverse_keys: K[]): boolean; delDeep(reverse_keys: K[]): boolean; get(key: K): any | undefined; set(key: K, value: any): any; has(key: K): boolean; delete(key: K): boolean; } | undefined; /** check if a deep child exists with the provided array of reversed keys. <br> * this is implemented to be slightly quicker than {@link getDeep} */ hasDeep(reverse_keys: K[]): boolean; delDeep(reverse_keys: K[]): boolean; get(key: K): { value: any; getDeep(reverse_keys: K[], create_intermediate?: true): any; getDeep(reverse_keys: K[], create_intermediate?: boolean): any | undefined; setDeep<T>(reverse_keys: K[], value: T, create_intermediate?: true): any; setDeep<T>(reverse_keys: K[], value: T, create_intermediate?: boolean): any | undefined; /** check if a deep child exists with the provided array of reversed keys. <br> * this is implemented to be slightly quicker than {@link getDeep} */ hasDeep(reverse_keys: K[]): boolean; delDeep(reverse_keys: K[]): boolean; get(key: K): any | undefined; set(key: K, value: any): any; has(key: K): boolean; delete(key: K): boolean; } | undefined; set(key: K, value: { value: any; getDeep(reverse_keys: K[], create_intermediate?: true): any; getDeep(reverse_keys: K[], create_intermediate?: boolean): any | undefined; setDeep<T>(reverse_keys: K[], value: T, create_intermediate?: true): any; setDeep<T>(reverse_keys: K[], value: T, create_intermediate?: boolean): any | undefined; /** check if a deep child exists with the provided array of reversed keys. <br> * this is implemented to be slightly quicker than {@link getDeep} */ hasDeep(reverse_keys: K[]): boolean; delDeep(reverse_keys: K[]): boolean; get(key: K): any | undefined; set(key: K, value: any): any; has(key: K): boolean; delete(key: K): boolean; }): any; has(key: K): boolean; delete(key: K): boolean; }; }; export declare const StrongTree: { new <K, V>(value?: V | typeof TREE_VALUE_UNSET): { value: V | typeof TREE_VALUE_UNSET; getDeep(reverse_keys: K[], create_intermediate?: true): { value: any; getDeep(reverse_keys: K[], create_intermediate?: true): any; getDeep(reverse_keys: K[], create_intermediate?: boolean): any | undefined; setDeep<T>(reverse_keys: K[], value: T, create_intermediate?: true): any; setDeep<T>(reverse_keys: K[], value: T, create_intermediate?: boolean): any | undefined; /** check if a deep child exists with the provided array of reversed keys. <br> * this is implemented to be slightly quicker than {@link getDeep} */ hasDeep(reverse_keys: K[]): boolean; delDeep(reverse_keys: K[]): boolean; get(key: K): any | undefined; set(key: K, value: any): any; has(key: K): boolean; delete(key: K): boolean; }; getDeep(reverse_keys: K[], create_intermediate?: boolean): { value: any; getDeep(reverse_keys: K[], create_intermediate?: true): any; getDeep(reverse_keys: K[], create_intermediate?: boolean): any | undefined; setDeep<T>(reverse_keys: K[], value: T, create_intermediate?: true): any; setDeep<T>(reverse_keys: K[], value: T, create_intermediate?: boolean): any | undefined; /** check if a deep child exists with the provided array of reversed keys. <br> * this is implemented to be slightly quicker than {@link getDeep} */ hasDeep(reverse_keys: K[]): boolean; delDeep(reverse_keys: K[]): boolean; get(key: K): any | undefined; set(key: K, value: any): any; has(key: K): boolean; delete(key: K): boolean; } | undefined; setDeep<T>(reverse_keys: K[], value: T, create_intermediate?: true): { value: any; getDeep(reverse_keys: K[], create_intermediate?: true): any; getDeep(reverse_keys: K[], create_intermediate?: boolean): any | undefined; setDeep<T>(reverse_keys: K[], value: T, create_intermediate?: true): any; setDeep<T>(reverse_keys: K[], value: T, create_intermediate?: boolean): any | undefined; /** check if a deep child exists with the provided array of reversed keys. <br> * this is implemented to be slightly quicker than {@link getDeep} */ hasDeep(reverse_keys: K[]): boolean; delDeep(reverse_keys: K[]): boolean; get(key: K): any | undefined; set(key: K, value: any): any; has(key: K): boolean; delete(key: K): boolean; }; setDeep<T>(reverse_keys: K[], value: T, create_intermediate?: boolean): { value: any; getDeep(reverse_keys: K[], create_intermediate?: true): any; getDeep(reverse_keys: K[], create_intermediate?: boolean): any | undefined; setDeep<T>(reverse_keys: K[], value: T, create_intermediate?: true): any; setDeep<T>(reverse_keys: K[], value: T, create_intermediate?: boolean): any | undefined; /** check if a deep child exists with the provided array of reversed keys. <br> * this is implemented to be slightly quicker than {@link getDeep} */ hasDeep(reverse_keys: K[]): boolean; delDeep(reverse_keys: K[]): boolean; get(key: K): any | undefined; set(key: K, value: any): any; has(key: K): boolean; delete(key: K): boolean; } | undefined; /** check if a deep child exists with the provided array of reversed keys. <br> * this is implemented to be slightly quicker than {@link getDeep} */ hasDeep(reverse_keys: K[]): boolean; delDeep(reverse_keys: K[]): boolean; get(key: K): { value: any; getDeep(reverse_keys: K[], create_intermediate?: true): any; getDeep(reverse_keys: K[], create_intermediate?: boolean): any | undefined; setDeep<T>(reverse_keys: K[], value: T, create_intermediate?: true): any; setDeep<T>(reverse_keys: K[], value: T, create_intermediate?: boolean): any | undefined; /** check if a deep child exists with the provided array of reversed keys. <br> * this is implemented to be slightly quicker than {@link getDeep} */ hasDeep(reverse_keys: K[]): boolean; delDeep(reverse_keys: K[]): boolean; get(key: K): any | undefined; set(key: K, value: any): any; has(key: K): boolean; delete(key: K): boolean; } | undefined; set(key: K, value: { value: any; getDeep(reverse_keys: K[], create_intermediate?: true): any; getDeep(reverse_keys: K[], create_intermediate?: boolean): any | undefined; setDeep<T>(reverse_keys: K[], value: T, create_intermediate?: true): any; setDeep<T>(reverse_keys: K[], value: T, create_intermediate?: boolean): any | undefined; /** check if a deep child exists with the provided array of reversed keys. <br> * this is implemented to be slightly quicker than {@link getDeep} */ hasDeep(reverse_keys: K[]): boolean; delDeep(reverse_keys: K[]): boolean; get(key: K): any | undefined; set(key: K, value: any): any; has(key: K): boolean; delete(key: K): boolean; }): any; has(key: K): boolean; delete(key: K): boolean; }; }; export declare const HybridTree: { new <K, V>(value?: V | typeof TREE_VALUE_UNSET): { value: V | typeof TREE_VALUE_UNSET; getDeep(reverse_keys: K[], create_intermediate?: true): { value: any; getDeep(reverse_keys: K[], create_intermediate?: true): any; getDeep(reverse_keys: K[], create_intermediate?: boolean): any | undefined;