"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.LinkedHashMap = exports.HashMap = void 0;
const base_1 = require("../base");
const utils_1 = require("../../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
 */
class HashMap extends base_1.IterableEntryBase {
    /**
     * 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 = [], options) {
        super();
        this._store = {};
        this._objMap = new Map();
        this._size = 0;
        this._hashFn = (key) => String(key);
        if (options) {
            const { hashFn, toEntryFn } = options;
            if (hashFn)
                this._hashFn = hashFn;
            if (toEntryFn)
                this._toEntryFn = toEntryFn;
        }
        if (entryOrRawElements) {
            this.setMany(entryOrRawElements);
        }
    }
    /**
     * 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() {
        return this._store;
    }
    /**
     * 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() {
        return this._objMap;
    }
    /**
     * The function returns the value of the _toEntryFn property.
     * @returns The function being returned is `this._toEntryFn`.
     */
    get toEntryFn() {
        return this._toEntryFn;
    }
    /**
     * The function returns the size of an object.
     * @returns The size of the object, which is a number.
     */
    get size() {
        return this._size;
    }
    /**
     * 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) {
        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() {
        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, value) {
        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) {
        const results = [];
        for (const rawEle of entryOrRawElements) {
            let key, value;
            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`.
     */
    get(key) {
        var _a;
        if (this._isObjKey(key)) {
            return this.objMap.get(key);
        }
        else {
            const strKey = this._getNoObjKey(key);
            return (_a = this._store[strKey]) === null || _a === void 0 ? void 0 : _a.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.
     */
    has(key) {
        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) {
        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() {
        return new HashMap(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(callbackfn, thisArg) {
        const resultMap = new HashMap();
        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, thisArg) {
        const filteredMap = new HashMap();
        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.
     */
    *_getIterator() {
        for (const node of Object.values(this.store)) {
            yield [node.key, node.value];
        }
        for (const node of this.objMap) {
            yield node;
        }
    }
    /**
     * 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.
     */
    _isObjKey(key) {
        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.
     */
    _getNoObjKey(key) {
        const keyType = typeof key;
        let strKey;
        if (keyType !== 'string' && keyType !== 'number' && keyType !== 'symbol') {
            strKey = this._hashFn(key);
        }
        else {
            if (keyType === 'number') {
                // TODO numeric key should has its own hash
                strKey = key;
            }
            else {
                strKey = key;
            }
        }
        return strKey;
    }
}
exports.HashMap = HashMap;
/**
 * 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.
 */
class LinkedHashMap extends base_1.IterableEntryBase {
    /**
     * 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 = [], options) {
        super();
        this._hashFn = (key) => String(key);
        this._objHashFn = (key) => key;
        this._noObjMap = {};
        this._objMap = new WeakMap();
        this._toEntryFn = (rawElement) => {
            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.");
            }
        };
        this._size = 0;
        this._sentinel = {};
        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);
        }
    }
    /**
     * 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() {
        return this._hashFn;
    }
    /**
     * The function returns the object hash function.
     * @returns The function `objHashFn` is being returned.
     */
    get objHashFn() {
        return this._objHashFn;
    }
    /**
     * 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() {
        return this._noObjMap;
    }
    /**
     * The function returns the WeakMap object used to map objects to HashMapLinkedNode instances.
     * @returns The `objMap` property is being returned.
     */
    get objMap() {
        return this._objMap;
    }
    /**
     * 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() {
        return this._head;
    }
    /**
     * The function returns the tail node of a HashMapLinkedNode.
     * @returns The `_tail` property of type `HashMapLinkedNode<K, V | undefined>` is being returned.
     */
    get tail() {
        return this._tail;
    }
    /**
     * The function returns the value of the _toEntryFn property.
     * @returns The function being returned is `this._toEntryFn`.
     */
    get toEntryFn() {
        return this._toEntryFn;
    }
    /**
     * 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 [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 [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, value) {
        let node;
        const isNewKey = !this.has(key); // Check if the key is new
        if ((0, utils_1.isWeakKey)(key)) {
            const hash = this._objHashFn(key);
            node = this.objMap.get(hash);
            if (!node && isNewKey) {
                // Create a new node
                node = { key: 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) {
        const results = [];
        for (const rawEle of entryOrRawElements) {
            let key, value;
            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.
     */
    has(key) {
        if ((0, utils_1.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
     */
    get(key) {
        if ((0, utils_1.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) {
        (0, utils_1.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) {
        let node;
        if ((0, utils_1.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) {
        (0, utils_1.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() {
        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) {
        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() {
        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() {
        const cloned = new LinkedHashMap([], { 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, thisArg) {
        const filteredMap = new LinkedHashMap();
        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(callback, thisArg) {
        const mappedMap = new LinkedHashMap();
        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.
     */
    *_getIterator() {
        let node = this.head;
        while (node !== this._sentinel) {
            yield [node.key, node.value];
            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.
     */
    _deleteNode(node) {
        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;
    }
}
exports.LinkedHashMap = LinkedHashMap;
//# sourceMappingURL=hash-map.js.map