doubly-linked-list-typed
Version:
Doubly Linked List
953 lines (869 loc) • 32.9 kB
text/typescript
/**
* data-structure-typed
*
* @author Pablo Zeng
* @copyright Copyright (c) 2022 Pablo Zeng <zrwusa@gmail.com>
* @license MIT License
*/
import type {
EntryCallback,
HashMapLinkedNode,
HashMapOptions,
HashMapStoreItem,
LinkedHashMapOptions
} from '../../types';
import { IterableEntryBase } from '../base';
import { isWeakKey, rangeCheck } from '../../utils';
/**
* 1. Key-Value Pair Storage: HashMap stores key-value pairs. Each key map to a value.
* 2. Fast Lookup: It's used when you need to quickly find, insert, or delete entries based on a key.
* 3. Unique Keys: Keys are unique.
* If you try to insert another entry with the same key, the new one will replace the old entry.
* 4. Unordered Collection: HashMap does not guarantee the order of entries, and the order may change over time.
* @example
* // should maintain insertion order
* const linkedHashMap = new LinkedHashMap<number, string>();
* linkedHashMap.set(1, 'A');
* linkedHashMap.set(2, 'B');
* linkedHashMap.set(3, 'C');
*
* const result = Array.from(linkedHashMap);
* console.log(result); // [
* // [1, 'A'],
* // [2, 'B'],
* // [3, 'C']
* // ]
* @example
* // fast lookup of values by key
* const hashMap = new HashMap<number, string>();
* hashMap.set(1, 'A');
* hashMap.set(2, 'B');
* hashMap.set(3, 'C');
*
* console.log(hashMap.get(1)); // 'A'
* console.log(hashMap.get(2)); // 'B'
* console.log(hashMap.get(3)); // 'C'
* console.log(hashMap.get(99)); // undefined
* @example
* // remove duplicates when adding multiple entries
* const hashMap = new HashMap<number, string>();
* hashMap.set(1, 'A');
* hashMap.set(2, 'B');
* hashMap.set(1, 'C'); // Update value for key 1
*
* console.log(hashMap.size); // 2
* console.log(hashMap.get(1)); // 'C'
* console.log(hashMap.get(2)); // 'B'
* @example
* // count occurrences of keys
* const data = [1, 2, 1, 3, 2, 1];
*
* const countMap = new HashMap<number, number>();
* for (const key of data) {
* countMap.set(key, (countMap.get(key) || 0) + 1);
* }
*
* console.log(countMap.get(1)); // 3
* console.log(countMap.get(2)); // 2
* console.log(countMap.get(3)); // 1
*/
export class HashMap<K = any, V = any, R = [K, V]> extends IterableEntryBase<K, V> {
/**
* The constructor function initializes a HashMap object with an optional initial collection and
* options.
* @param entryOrRawElements - The `entryOrRawElements` parameter is an iterable collection of elements of a type
* `T`. It is an optional parameter and its default value is an empty array `[]`.
* @param [options] - The `options` parameter is an optional object that can contain two properties:
*/
constructor(entryOrRawElements: Iterable<R | [K, V]> = [], options?: HashMapOptions<K, V, R>) {
super();
if (options) {
const { hashFn, toEntryFn } = options;
if (hashFn) this._hashFn = hashFn;
if (toEntryFn) this._toEntryFn = toEntryFn;
}
if (entryOrRawElements) {
this.setMany(entryOrRawElements);
}
}
protected _store: { [key: string]: HashMapStoreItem<K, V> } = {};
/**
* The function returns the store object, which is a dictionary of HashMapStoreItem objects.
* @returns The store property is being returned. It is a dictionary-like object with string keys and
* values of type HashMapStoreItem<K, V>.
*/
get store(): { [p: string]: HashMapStoreItem<K, V> } {
return this._store;
}
protected _objMap: Map<object, V> = new Map();
/**
* The function returns the object map.
* @returns The `objMap` property is being returned, which is a `Map` object with keys of type
* `object` and values of type `V`.
*/
get objMap(): Map<object, V> {
return this._objMap;
}
protected _toEntryFn?: (rawElement: R) => [K, V];
/**
* The function returns the value of the _toEntryFn property.
* @returns The function being returned is `this._toEntryFn`.
*/
get toEntryFn() {
return this._toEntryFn;
}
protected _size = 0;
/**
* The function returns the size of an object.
* @returns The size of the object, which is a number.
*/
get size(): number {
return this._size;
}
protected _hashFn: (key: K) => string = (key: K) => String(key);
/**
* The hasFn function is a function that takes in an item and returns a boolean
* indicating whether the item is contained within the hash table.
*
* @return The hash function
*/
get hashFn() {
return this._hashFn;
}
/**
* Time Complexity: O(1)
* Space Complexity: O(1)
*
* The function checks if a given element is an array with exactly two elements.
* @param {any} rawElement - The `rawElement` parameter is of type `any`, which means it can be any
* data type.
* @returns a boolean value.
*/
isEntry(rawElement: any): rawElement is [K, V] {
return Array.isArray(rawElement) && rawElement.length === 2;
}
/**
* Time Complexity: O(1)
* Space Complexity: O(1)
*
* The function checks if the size of an object is equal to zero and returns a boolean value.
* @returns A boolean value indicating whether the size of the object is 0 or not.
*/
isEmpty(): boolean {
return this._size === 0;
}
/**
* Time Complexity: O(1)
* Space Complexity: O(1)
*
* The clear() function resets the state of an object by clearing its internal store, object map, and
* size.
*/
clear() {
this._store = {};
this._objMap.clear();
this._size = 0;
}
/**
* Time Complexity: O(1)
* Space Complexity: O(1)
*
* The `set` function adds a key-value pair to a map-like data structure, incrementing the size if
* the key is not already present.
* @param {K} key - The key parameter is the key used to identify the value in the data structure. It
* can be of any type, but if it is an object, it will be stored in a Map, otherwise it will be
* stored in a regular JavaScript object.
* @param {V} value - The value parameter represents the value that you want to associate with the
* key in the data structure.
*/
set(key: K, value: V): boolean {
if (this._isObjKey(key)) {
if (!this.objMap.has(key)) {
this._size++;
}
this.objMap.set(key, value);
} else {
const strKey = this._getNoObjKey(key);
if (this.store[strKey] === undefined) {
this._size++;
}
this._store[strKey] = { key, value };
}
return true;
}
/**
* Time Complexity: O(k)
* Space Complexity: O(k)
*
* The function `setMany` takes an iterable collection of objects, maps each object to a key-value
* pair using a mapping function, and sets each key-value pair in the current object.
* @param entryOrRawElements - The `entryOrRawElements` parameter is an iterable collection of elements of a type
* `T`.
* @returns The `setMany` function is returning an array of booleans.
*/
setMany(entryOrRawElements: Iterable<R | [K, V]>): boolean[] {
const results: boolean[] = [];
for (const rawEle of entryOrRawElements) {
let key: K | undefined, value: V | undefined;
if (this.isEntry(rawEle)) {
key = rawEle[0];
value = rawEle[1];
} else if (this._toEntryFn) {
const item = this._toEntryFn(rawEle);
key = item[0];
value = item[1];
}
if (key !== undefined && value !== undefined) results.push(this.set(key, value));
}
return results;
}
/**
* Time Complexity: O(1)
* Space Complexity: O(1)
*
* The `get` function retrieves a value from a map based on a given key, either from an object map or
* a string map.
* @param {K} key - The `key` parameter is the key used to retrieve a value from the map. It can be
* of any type, but it should be compatible with the key type used when the map was created.
* @returns The method `get(key: K)` returns a value of type `V` if the key exists in the `_objMap`
* or `_store`, otherwise it returns `undefined`.
*/
override get(key: K): V | undefined {
if (this._isObjKey(key)) {
return this.objMap.get(key);
} else {
const strKey = this._getNoObjKey(key);
return this._store[strKey]?.value;
}
}
/**
* Time Complexity: O(1)
* Space Complexity: O(1)
*
* The `has` function checks if a given key exists in the `_objMap` or `_store` based on whether it
* is an object key or not.
* @param {K} key - The parameter "key" is of type K, which means it can be any type.
* @returns The `has` method is returning a boolean value.
*/
override has(key: K): boolean {
if (this._isObjKey(key)) {
return this.objMap.has(key);
} else {
const strKey = this._getNoObjKey(key);
return strKey in this.store;
}
}
/**
* Time Complexity: O(1)
* Space Complexity: O(1)
*
* The `delete` function removes an element from a map-like data structure based on the provided key.
* @param {K} key - The `key` parameter is the key of the element that you want to delete from the
* data structure.
* @returns The `delete` method returns a boolean value. It returns `true` if the key was
* successfully deleted from the map, and `false` if the key was not found in the map.
*/
delete(key: K): boolean {
if (this._isObjKey(key)) {
if (this.objMap.has(key)) {
this._size--;
}
return this.objMap.delete(key);
} else {
const strKey = this._getNoObjKey(key);
if (strKey in this.store) {
delete this.store[strKey];
this._size--;
return true;
}
return false;
}
}
/**
* Time Complexity: O(n)
* Space Complexity: O(n)
*
* The clone function creates a new HashMap with the same key-value pairs as
* this one. The clone function is useful for creating a copy of an existing
* HashMap, and then modifying that copy without affecting the original.
*
* @return A new hashmap with the same values as this one
*/
clone(): HashMap<K, V, R> {
return new HashMap<K, V, R>(this, { hashFn: this._hashFn, toEntryFn: this._toEntryFn });
}
/**
* Time Complexity: O(n)
* Space Complexity: O(n)
*
* The `map` function in TypeScript creates a new HashMap by applying a callback function to each
* key-value pair in the original HashMap.
* @param callbackfn - The callback function that will be called for each key-value pair in the
* HashMap. It takes four parameters:
* @param {any} [thisArg] - The `thisArg` parameter is an optional argument that specifies the value
* to be used as `this` when executing the `callbackfn` function. If `thisArg` is provided, it will
* be passed as the `this` value to the `callbackfn` function. If `thisArg
* @returns The `map` method is returning a new `HashMap` object with the transformed values based on
* the provided callback function.
*/
map<VM>(callbackfn: EntryCallback<K, V, VM>, thisArg?: any): HashMap<K, VM> {
const resultMap = new HashMap<K, VM>();
let index = 0;
for (const [key, value] of this) {
resultMap.set(key, callbackfn.call(thisArg, key, value, index++, this));
}
return resultMap;
}
/**
* Time Complexity: O(n)
* Space Complexity: O(n)
*
* The `filter` function creates a new HashMap containing key-value pairs from the original HashMap
* that satisfy a given predicate function.
* @param predicate - The predicate parameter is a function that takes four arguments: value, key,
* index, and map. It is used to determine whether an element should be included in the filtered map
* or not. The function should return a boolean value - true if the element should be included, and
* false otherwise.
* @param {any} [thisArg] - The `thisArg` parameter is an optional argument that specifies the value
* to be used as `this` when executing the `predicate` function. If `thisArg` is provided, it will be
* passed as the `this` value to the `predicate` function. If `thisArg` is
* @returns The `filter` method is returning a new `HashMap` object that contains the key-value pairs
* from the original `HashMap` that pass the provided `predicate` function.
*/
filter(predicate: EntryCallback<K, V, boolean>, thisArg?: any): HashMap<K, V> {
const filteredMap = new HashMap<K, V>();
let index = 0;
for (const [key, value] of this) {
if (predicate.call(thisArg, key, value, index++, this)) {
filteredMap.set(key, value);
}
}
return filteredMap;
}
/**
* The function returns an iterator that yields key-value pairs from both an object store and an
* object map.
*/
protected *_getIterator(): IterableIterator<[K, V]> {
for (const node of Object.values(this.store)) {
yield [node.key, node.value] as [K, V];
}
for (const node of this.objMap) {
yield node as [K, V];
}
}
/**
* The function checks if a given key is an object or a function.
* @param {any} key - The parameter "key" can be of any type.
* @returns a boolean value.
*/
protected _isObjKey(key: any): key is object | ((...args: any[]) => any) {
const keyType = typeof key;
return (keyType === 'object' || keyType === 'function') && key !== null;
}
/**
* The function `_getNoObjKey` takes a key and returns a string representation of the key, handling
* different types of keys.
* @param {K} key - The `key` parameter is of type `K`, which represents the type of the key being
* passed to the `_getNoObjKey` function.
* @returns a string value.
*/
protected _getNoObjKey(key: K): string {
const keyType = typeof key;
let strKey: string;
if (keyType !== 'string' && keyType !== 'number' && keyType !== 'symbol') {
strKey = this._hashFn(key);
} else {
if (keyType === 'number') {
// TODO numeric key should has its own hash
strKey = <string>key;
} else {
strKey = <string>key;
}
}
return strKey;
}
}
/**
* 1. Maintaining the Order of Element Insertion: Unlike HashMap, LinkedHashMap maintains the order in which entries are inserted. Therefore, when you traverse it, entries will be returned in the order they were inserted into the map.
* 2. Based on Hash Table and Linked List: It combines the structures of a hash table and a linked list, using the hash table to ensure fast access, while maintaining the order of entries through the linked list.
* 3. Time Complexity: Similar to HashMap, LinkedHashMap offers constant-time performance for get and put operations in most cases.
*/
export class LinkedHashMap<K = any, V = any, R = [K, V]> extends IterableEntryBase<K, V> {
protected readonly _sentinel: HashMapLinkedNode<K, V | undefined>;
/**
* The constructor initializes a LinkedHashMap object with an optional raw collection and options.
* @param entryOrRawElements - The `entryOrRawElements` parameter is an iterable collection of elements. It is
* used to initialize the HashMapLinked instance with key-value pairs. Each element in the
* `entryOrRawElements` is converted to a key-value pair using the `toEntryFn` function (if provided) and
* then added to the HashMap
* @param [options] - The `options` parameter is an optional object that can contain the following
* properties:
*/
constructor(entryOrRawElements: Iterable<R | [K, V]> = [], options?: LinkedHashMapOptions<K, V, R>) {
super();
this._sentinel = <HashMapLinkedNode<K, V>>{};
this._sentinel.prev = this._sentinel.next = this._head = this._tail = this._sentinel;
if (options) {
const { hashFn, objHashFn, toEntryFn } = options;
if (hashFn) this._hashFn = hashFn;
if (objHashFn) this._objHashFn = objHashFn;
if (toEntryFn) {
this._toEntryFn = toEntryFn;
}
}
if (entryOrRawElements) {
this.setMany(entryOrRawElements);
}
}
protected _hashFn: (key: K) => string = (key: K) => String(key);
/**
* The function returns the hash function used for generating a hash value for a given key.
* @returns The hash function that takes a key of type K and returns a string.
*/
get hashFn(): (key: K) => string {
return this._hashFn;
}
protected _objHashFn: (key: K) => object = (key: K) => <object>key;
/**
* The function returns the object hash function.
* @returns The function `objHashFn` is being returned.
*/
get objHashFn(): (key: K) => object {
return this._objHashFn;
}
protected _noObjMap: Record<string, HashMapLinkedNode<K, V | undefined>> = {};
/**
* The function returns a record of HashMapLinkedNode objects with string keys.
* @returns The method is returning a Record object, which is a TypeScript type that represents an
* object with string keys and values that are HashMapLinkedNode objects with keys of type K and
* values of type V or undefined.
*/
get noObjMap(): Record<string, HashMapLinkedNode<K, V | undefined>> {
return this._noObjMap;
}
protected _objMap = new WeakMap<object, HashMapLinkedNode<K, V | undefined>>();
/**
* The function returns the WeakMap object used to map objects to HashMapLinkedNode instances.
* @returns The `objMap` property is being returned.
*/
get objMap(): WeakMap<object, HashMapLinkedNode<K, V | undefined>> {
return this._objMap;
}
protected _head: HashMapLinkedNode<K, V | undefined>;
/**
* The function returns the head node of a HashMapLinkedNode.
* @returns The method `getHead()` is returning a `HashMapLinkedNode` object with key type `K` and
* a value type `V | undefined`.
*/
get head(): HashMapLinkedNode<K, V | undefined> {
return this._head;
}
protected _tail: HashMapLinkedNode<K, V | undefined>;
/**
* The function returns the tail node of a HashMapLinkedNode.
* @returns The `_tail` property of type `HashMapLinkedNode<K, V | undefined>` is being returned.
*/
get tail(): HashMapLinkedNode<K, V | undefined> {
return this._tail;
}
protected _toEntryFn?: (rawElement: R) => [K, V] = (rawElement: R) => {
if (this.isEntry(rawElement)) {
// TODO, For performance optimization, it may be necessary to only inspect the first element traversed.
return rawElement;
} else {
throw new Error(
"If the provided entryOrRawElements does not adhere to the [key, value] type format, the toEntryFn in the constructor's options parameter needs to specified."
);
}
};
/**
* The function returns the value of the _toEntryFn property.
* @returns The function being returned is `this._toEntryFn`.
*/
get toEntryFn() {
return this._toEntryFn;
}
protected _size = 0;
/**
* The function returns the size of an object.
* @returns The size of the object.
*/
get size() {
return this._size;
}
/**
* Time Complexity: O(1)
* Space Complexity: O(1)
*
* The function returns the key-value pair at the front of a data structure.
* @returns The front element of the data structure, represented as a tuple with a key (K) and a
* value (V).
*/
get first() {
if (this._size === 0) return;
return <[K, V]>[this.head.key, this.head.value];
}
/**
* Time Complexity: O(1)
* Space Complexity: O(1)
*
* The function returns the key-value pair at the end of a data structure.
* @returns The method is returning an array containing the key-value pair of the tail element in the
* data structure.
*/
get last() {
if (this._size === 0) return;
return <[K, V]>[this.tail.key, this.tail.value];
}
/**
* The `begin()` function in TypeScript iterates over a linked list and yields key-value pairs.
*/
*begin() {
let node = this.head;
while (node !== this._sentinel) {
yield [node.key, node.value];
node = node.next;
}
}
/**
* The function `reverseBegin()` iterates over a linked list in reverse order, yielding each node's
* key and value.
*/
*reverseBegin() {
let node = this.tail;
while (node !== this._sentinel) {
yield [node.key, node.value];
node = node.prev;
}
}
/**
* Time Complexity: O(1)
* Space Complexity: O(1)
*
* The `set` function adds a new key-value pair to a data structure, either using an object key or a
* string key.
* @param {K} key - The `key` parameter is the key to be set in the data structure. It can be of any
* type, but typically it is a string or symbol.
* @param {V} [value] - The `value` parameter is an optional parameter of type `V`. It represents the
* value associated with the key being set in the data structure.
* @returns the size of the data structure after the key-value pair has been set.
*/
set(key: K, value?: V): boolean {
let node;
const isNewKey = !this.has(key); // Check if the key is new
if (isWeakKey(key)) {
const hash = this._objHashFn(key);
node = this.objMap.get(hash);
if (!node && isNewKey) {
// Create a new node
node = { key: <K>hash, value, prev: this.tail, next: this._sentinel };
this.objMap.set(hash, node);
} else if (node) {
// Update the value of an existing node
node.value = value;
}
} else {
const hash = this._hashFn(key);
node = this.noObjMap[hash];
if (!node && isNewKey) {
this.noObjMap[hash] = node = { key, value, prev: this.tail, next: this._sentinel };
} else if (node) {
// Update the value of an existing node
node.value = value;
}
}
if (node && isNewKey) {
// Update the head and tail of the linked list
if (this._size === 0) {
this._head = node;
this._sentinel.next = node;
} else {
this.tail.next = node;
node.prev = this.tail; // Make sure that the prev of the new node points to the current tail node
}
this._tail = node;
this._sentinel.prev = node;
this._size++;
}
return true;
}
/**
* Time Complexity: O(k)
* Space Complexity: O(k)
*
* The function `setMany` takes an iterable collection, converts each element into a key-value pair
* using a provided function, and sets each key-value pair in the current object, returning an array
* of booleans indicating the success of each set operation.
* @param entryOrRawElements - The entryOrRawElements parameter is an iterable collection of elements of type
* R.
* @returns The `setMany` function returns an array of booleans.
*/
setMany(entryOrRawElements: Iterable<R | [K, V]>): boolean[] {
const results: boolean[] = [];
for (const rawEle of entryOrRawElements) {
let key: K | undefined, value: V | undefined;
if (this.isEntry(rawEle)) {
key = rawEle[0];
value = rawEle[1];
} else if (this._toEntryFn) {
const item = this._toEntryFn(rawEle);
key = item[0];
value = item[1];
}
if (key !== undefined && value !== undefined) results.push(this.set(key, value));
}
return results;
}
/**
* Time Complexity: O(1)
* Space Complexity: O(1)
*
* The function checks if a given key exists in a map, using different logic depending on whether the
* key is a weak key or not.
* @param {K} key - The `key` parameter is the key that is being checked for existence in the map.
* @returns The method `has` is returning a boolean value.
*/
override has(key: K): boolean {
if (isWeakKey(key)) {
const hash = this._objHashFn(key);
return this.objMap.has(hash);
} else {
const hash = this._hashFn(key);
return hash in this.noObjMap;
}
}
/**
* Time Complexity: O(1)
* Space Complexity: O(1)
*
* The function `get` retrieves the value associated with a given key from a map, either by using the
* key directly or by using an index stored in the key object.
* @param {K} key - The `key` parameter is the key used to retrieve a value from the map. It can be
* of any type, but typically it is a string or symbol.
* @returns The value associated with the given key is being returned. If the key is an object key,
* the value is retrieved from the `_nodes` array using the index stored in the `OBJ_KEY_INDEX`
* property of the key. If the key is a string key, the value is retrieved from the `_noObjMap` object
* using the key itself. If the key is not found, `undefined` is
*/
override get(key: K): V | undefined {
if (isWeakKey(key)) {
const hash = this._objHashFn(key);
const node = this.objMap.get(hash);
return node ? node.value : undefined;
} else {
const hash = this._hashFn(key);
const node = this.noObjMap[hash];
return node ? node.value : undefined;
}
}
/**
* Time Complexity: O(n)
* Space Complexity: O(1)
*
* The function `at` retrieves the key-value pair at a specified index in a linked list.
* @param {number} index - The index parameter is a number that represents the position of the
* element we want to retrieve from the data structure.
* @returns The method `at(index: number)` is returning an array containing the key-value pair at
* the specified index in the data structure. The key-value pair is represented as a tuple `[K, V]`,
* where `K` is the key and `V` is the value.
*/
at(index: number): V | undefined {
rangeCheck(index, 0, this._size - 1);
let node = this.head;
while (index--) {
node = node.next;
}
return node.value;
}
/**
* Time Complexity: O(1)
* Space Complexity: O(1)
*
* The `delete` function removes a key-value pair from a map-like data structure.
* @param {K} key - The `key` parameter is the key that you want to delete from the data structure.
* It can be of any type, but typically it is a string or an object.
* @returns a boolean value. It returns `true` if the deletion was successful, and `false` if the key
* was not found.
*/
delete(key: K): boolean {
let node;
if (isWeakKey(key)) {
const hash = this._objHashFn(key);
// Get nodes from WeakMap
node = this.objMap.get(hash);
if (!node) {
return false; // If the node does not exist, return false
}
// Remove nodes from WeakMap
this.objMap.delete(hash);
} else {
const hash = this._hashFn(key);
// Get nodes from noObjMap
node = this.noObjMap[hash];
if (!node) {
return false; // If the node does not exist, return false
}
// Remove nodes from orgMap
delete this.noObjMap[hash];
}
// Remove node from doubly linked list
this._deleteNode(node);
return true;
}
/**
* Time Complexity: O(n)
* Space Complexity: O(1)
*
* The `deleteAt` function deletes a node at a specified index in a linked list.
* @param {number} index - The index parameter represents the position at which the node should be
* deleted in the linked list.
* @returns The size of the list after deleting the element at the specified index.
*/
deleteAt(index: number): boolean {
rangeCheck(index, 0, this._size - 1);
let node = this.head;
while (index--) {
node = node.next;
}
return this._deleteNode(node);
}
/**
* Time Complexity: O(1)
* Space Complexity: O(1)
*
* The function checks if a data structure is empty by comparing its size to zero.
* @returns The method is returning a boolean value indicating whether the size of the object is 0 or
* not.
*/
isEmpty(): boolean {
return this._size === 0;
}
/**
* The function checks if a given element is an array with exactly two elements.
* @param {any} rawElement - The `rawElement` parameter is of type `any`, which means it can be any
* data type.
* @returns a boolean value.
*/
isEntry(rawElement: any): rawElement is [K, V] {
return Array.isArray(rawElement) && rawElement.length === 2;
}
/**
* Time Complexity: O(1)
* Space Complexity: O(1)
*
* The `clear` function clears all the entries in a data structure and resets its properties.
*/
clear(): void {
this._noObjMap = {};
this._size = 0;
this._head = this._tail = this._sentinel.prev = this._sentinel.next = this._sentinel;
}
/**
* Time Complexity: O(n)
* Space Complexity: O(n)
*
* The `clone` function creates a new instance of a `LinkedHashMap` with the same key-value pairs as
* the original.
* @returns The `clone()` method is returning a new instance of `LinkedHashMap<K, V>` that is a clone
* of the original `LinkedHashMap` object.
*/
clone(): LinkedHashMap<K, V> {
const cloned = new LinkedHashMap<K, V>([], { hashFn: this._hashFn, objHashFn: this._objHashFn });
for (const entry of this) {
const [key, value] = entry;
cloned.set(key, value);
}
return cloned;
}
/**
* Time Complexity: O(n)
* Space Complexity: O(n)
*
* The `filter` function creates a new `LinkedHashMap` containing key-value pairs from the original
* map that satisfy a given predicate function.
* @param predicate - The `predicate` parameter is a callback function that takes four arguments:
* `value`, `key`, `index`, and `this`. It should return a boolean value indicating whether the
* current element should be included in the filtered map or not.
* @param {any} [thisArg] - The `thisArg` parameter is an optional argument that allows you to
* specify the value of `this` within the `predicate` function. It is used when you want to bind a
* specific object as the context for the `predicate` function. If `thisArg` is not provided, `this
* @returns a new `LinkedHashMap` object that contains the key-value pairs from the original
* `LinkedHashMap` object that satisfy the given predicate function.
*/
filter(predicate: EntryCallback<K, V, boolean>, thisArg?: any): LinkedHashMap<K, V> {
const filteredMap = new LinkedHashMap<K, V>();
let index = 0;
for (const [key, value] of this) {
if (predicate.call(thisArg, key, value, index, this)) {
filteredMap.set(key, value);
}
index++;
}
return filteredMap;
}
/**
* Time Complexity: O(n)
* Space Complexity: O(n)
*
* The `map` function in TypeScript creates a new `LinkedHashMap` by applying a callback function to
* each key-value pair in the original map.
* @param callback - The callback parameter is a function that will be called for each key-value pair
* in the map. It takes four arguments: the value of the current key-value pair, the key of the
* current key-value pair, the index of the current key-value pair, and the map itself. The callback
* function should
* @param {any} [thisArg] - The `thisArg` parameter is an optional argument that allows you to
* specify the value of `this` within the callback function. If provided, the callback function will
* be called with `thisArg` as its `this` value. If not provided, `this` will refer to the current
* map
* @returns a new `LinkedHashMap` object with the values mapped according to the provided callback
* function.
*/
map<MK, MV>(callback: EntryCallback<K, V, [MK, MV]>, thisArg?: any): LinkedHashMap<MK, MV> {
const mappedMap = new LinkedHashMap<MK, MV>();
let index = 0;
for (const [key, value] of this) {
const [newKey, newValue] = callback.call(thisArg, key, value, index, this);
mappedMap.set(newKey, newValue);
index++;
}
return mappedMap;
}
/**
* Time Complexity: O(n)
* Space Complexity: O(1)
* where n is the number of entries in the LinkedHashMap.
*
* The above function is an iterator that yields key-value pairs from a linked list.
*/
protected *_getIterator() {
let node = this.head;
while (node !== this._sentinel) {
yield [node.key, node.value] as [K, V];
node = node.next;
}
}
/**
* Time Complexity: O(1)
* Space Complexity: O(1)
*
* The `_deleteNode` function removes a node from a doubly linked list and updates the head and tail
* pointers if necessary.
* @param node - The `node` parameter is an instance of the `HashMapLinkedNode` class, which
* represents a node in a linked list. It contains a key-value pair and references to the previous
* and next nodes in the list.
*/
protected _deleteNode(node: HashMapLinkedNode<K, V | undefined>): boolean {
const { prev, next } = node;
prev.next = next;
next.prev = prev;
if (node === this.head) {
this._head = next;
}
if (node === this.tail) {
this._tail = prev;
}
this._size -= 1;
return true;
}
}