From 7f5eb6cb70fc612dfea189652d8514472e055a9f Mon Sep 17 00:00:00 2001 From: Revone Date: Sat, 2 Dec 2023 22:15:18 +0800 Subject: [PATCH] refactor: Extract all methods such as 'some', 'every', 'entries', 'keys', 'values', 'forEach', 'reduce' into the base classes 'IterableElementBase' and 'IterablePairBase'. --- CHANGELOG.md | 2 +- src/data-structures/base/index.ts | 1 + src/data-structures/base/iterable-base.ts | 329 ++++++++++++++++++ .../binary-tree/binary-tree.ts | 224 +++++------- src/data-structures/graph/abstract-graph.ts | 83 +++-- src/data-structures/hash/hash-map.ts | 261 ++++---------- src/data-structures/heap/heap.ts | 99 ++++-- src/data-structures/index.ts | 1 + .../linked-list/doubly-linked-list.ts | 125 +++---- .../linked-list/singly-linked-list.ts | 127 +++---- src/data-structures/queue/deque.ts | 116 ++---- src/data-structures/queue/queue.ts | 155 ++++----- src/data-structures/stack/stack.ts | 85 +++-- src/data-structures/trie/trie.ts | 115 +++--- src/types/data-structures/base/base.ts | 6 + src/types/data-structures/base/index.ts | 1 + src/types/data-structures/index.ts | 1 + .../binary-tree/avl-tree.test.ts | 20 +- .../binary-tree/binary-tree.test.ts | 20 +- .../data-structures/binary-tree/bst.test.ts | 20 +- .../binary-tree/rb-tree.test.ts | 16 +- .../binary-tree/tree-multimap.test.ts | 20 +- .../graph/directed-graph.test.ts | 8 +- .../data-structures/hash/hash-map.test.ts | 4 +- .../linked-list/doubly-linked-list.test.ts | 28 ++ .../linked-list/singly-linked-list.test.ts | 25 ++ test/unit/data-structures/queue/deque.test.ts | 25 ++ 27 files changed, 1072 insertions(+), 845 deletions(-) create mode 100644 src/data-structures/base/index.ts create mode 100644 src/data-structures/base/iterable-base.ts create mode 100644 src/types/data-structures/base/base.ts create mode 100644 src/types/data-structures/base/index.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 28cb6ad..d2905cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ All notable changes to this project will be documented in this file. - [Semantic Versioning](https://semver.org/spec/v2.0.0.html) - [`auto-changelog`](https://github.com/CookPete/auto-changelog) -## [v1.48.0](https://github.com/zrwusa/data-structure-typed/compare/v1.35.0...main) (upcoming) +## [v1.48.1](https://github.com/zrwusa/data-structure-typed/compare/v1.35.0...main) (upcoming) ### Changes diff --git a/src/data-structures/base/index.ts b/src/data-structures/base/index.ts new file mode 100644 index 0000000..4183571 --- /dev/null +++ b/src/data-structures/base/index.ts @@ -0,0 +1 @@ +export * from './iterable-base'; \ No newline at end of file diff --git a/src/data-structures/base/iterable-base.ts b/src/data-structures/base/iterable-base.ts new file mode 100644 index 0000000..a5c3990 --- /dev/null +++ b/src/data-structures/base/iterable-base.ts @@ -0,0 +1,329 @@ +import { ElementCallback, PairCallback, ReduceElementCallback, ReducePairCallback } from "../../types"; + +export abstract class IterablePairBase { + + /** + * Time Complexity: O(n) + * Space Complexity: O(1) + */ + + /** + * Time Complexity: O(n) + * Space Complexity: O(1) + * + * The function is an implementation of the Symbol.iterator method that returns an iterable iterator. + * @param {any[]} args - The `args` parameter in the code snippet represents a rest parameter. It + * allows the function to accept any number of arguments as an array. In this case, the `args` + * parameter is used to pass any additional arguments to the `_getIterator` method. + */ + * [Symbol.iterator](...args: any[]): IterableIterator<[K, V]> { + yield* this._getIterator(...args); + } + + /** + * Time Complexity: O(n) + * Space Complexity: O(n) + */ + /** + * Time Complexity: O(n) + * Space Complexity: O(n) + * + * The function returns an iterator that yields key-value pairs from the object, where the value can + * be undefined. + */ + * entries(): IterableIterator<[K, V | undefined]> { + for (const item of this) { + yield item; + } + } + + /** + * Time Complexity: O(n) + * Space Complexity: O(n) + */ + /** + * Time Complexity: O(n) + * Space Complexity: O(n) + * + * The function returns an iterator that yields the keys of a data structure. + */ + * keys(): IterableIterator { + for (const item of this) { + yield item[0]; + } + } + + /** + * Time Complexity: O(n) + * Space Complexity: O(n) + */ + /** + * Time Complexity: O(n) + * Space Complexity: O(n) + * + * The function returns an iterator that yields the values of a collection. + */ + * values(): IterableIterator { + for (const item of this) { + yield item[1]; + } + } + + /** + * Time Complexity: O(n) + * Space Complexity: O(1) + */ + /** + * Time Complexity: O(n) + * Space Complexity: O(1) + * + * The `every` function checks if every element in a collection satisfies a given condition. + * @param predicate - The `predicate` parameter is a callback function that takes three arguments: + * `value`, `key`, and `index`. It should return a boolean value indicating whether the condition is + * met for the current element in the iteration. + * @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 first argument to the `predicate` function. If `thisArg` is not provided + * @returns The `every` method is returning a boolean value. It returns `true` if every element in + * the collection satisfies the provided predicate function, and `false` otherwise. + */ + every(predicate: PairCallback, thisArg?: any): boolean { + let index = 0; + for (const item of this) { + if (!predicate.call(thisArg, item[1], item[0], index++, this)) { + return false; + } + } + return true; + } + + /** + * Time Complexity: O(n) + * Space Complexity: O(1) + */ + /** + * Time Complexity: O(n) + * Space Complexity: O(1) + * + * The "some" function iterates over a collection and returns true if at least one element satisfies + * a given predicate. + * @param predicate - The `predicate` parameter is a callback function that takes three arguments: + * `value`, `key`, and `index`. It should return a boolean value indicating whether the condition is + * met for the current element in the iteration. + * @param {any} [thisArg] - The `thisArg` parameter is an optional argument that specifies the value + * to be used as the `this` value when executing the `predicate` function. If `thisArg` is provided, + * it will be passed as the first argument to the `predicate` function. If `thisArg` is + * @returns a boolean value. It returns true if the predicate function returns true for any pair in + * the collection, and false otherwise. + */ + some(predicate: PairCallback, thisArg?: any): boolean { + let index = 0; + for (const item of this) { + if (predicate.call(thisArg, item[1], item[0], index++, this)) { + return true; + } + } + return false; + } + + /** + * Time Complexity: O(n) + * Space Complexity: O(1) + */ + /** + * Time Complexity: O(n) + * Space Complexity: O(1) + * + * The `forEach` function iterates over each key-value pair in a collection and executes a callback + * function for each pair. + * @param callbackfn - The callback function that will be called for each element in the collection. + * It takes four parameters: the value of the current element, the key of the current element, the + * index of the current element, and the collection itself. + * @param {any} [thisArg] - The `thisArg` parameter is an optional argument that allows you to + * specify the value of `this` within the callback function. If `thisArg` is provided, it will be + * used as the `this` value when calling the callback function. If `thisArg` is not provided, ` + */ + forEach(callbackfn: PairCallback, thisArg?: any): void { + let index = 0; + for (const item of this) { + const [key, value] = item; + callbackfn.call(thisArg, value, key, index++, this) + } + } + + /** + * Time Complexity: O(n) + * Space Complexity: O(1) + */ + /** + * Time Complexity: O(n) + * Space Complexity: O(1) + * + * The `reduce` function iterates over key-value pairs and applies a callback function to each pair, + * accumulating a single value. + * @param callbackfn - The callback function that will be called for each element in the collection. + * It takes four arguments: the current accumulator value, the current value of the element, the key + * of the element, and the index of the element in the collection. It should return the updated + * accumulator value. + * @param {U} initialValue - The `initialValue` parameter is the initial value of the accumulator. It + * is the value that will be used as the first argument to the `callbackfn` function when reducing + * the elements of the collection. + * @returns The `reduce` method is returning the final value of the accumulator after iterating over + * all the elements in the collection. + */ + reduce(callbackfn: ReducePairCallback, initialValue: U): U { + let accumulator = initialValue; + let index = 0; + for (const item of this) { + const [key, value] = item; + accumulator = callbackfn(accumulator, value, key, index++, this) + } + return accumulator; + } + + protected abstract _getIterator(...args: any[]): IterableIterator<[K, V]>; +} + +export abstract class IterableElementBase { + + /** + * Time Complexity: O(n) + * Space Complexity: O(1) + */ + /** + * Time Complexity: O(n) + * Space Complexity: O(1) + * + * The function is an implementation of the Symbol.iterator method that returns an IterableIterator. + * @param {any[]} args - The `args` parameter in the code snippet represents a rest parameter. It + * allows the function to accept any number of arguments as an array. In this case, the `args` + * parameter is used to pass any number of arguments to the `_getIterator` method. + */ + * [Symbol.iterator](...args: any[]): IterableIterator { + yield* this._getIterator(...args); + } + + /** + * Time Complexity: O(n) + * Space Complexity: O(n) + */ + /** + * Time Complexity: O(n) + * Space Complexity: O(n) + * + * The function returns an iterator that yields all the values in the object. + */ + * values(): IterableIterator { + for (const item of this) { + yield item; + } + } + + /** + * Time Complexity: O(n) + * Space Complexity: O(1) + */ + /** + * Time Complexity: O(n) + * Space Complexity: O(1) + * + * The `every` function checks if every element in the array satisfies a given predicate. + * @param predicate - The `predicate` parameter is a callback function that takes three arguments: + * the current element being processed, its index, and the array it belongs to. It should return a + * boolean value indicating whether the element satisfies a certain condition or not. + * @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 `every` method is returning a boolean value. It returns `true` if every element in + * the array satisfies the provided predicate function, and `false` otherwise. + */ + every(predicate: ElementCallback, thisArg?: any): boolean { + let index = 0; + for (const item of this) { + if (!predicate.call(thisArg, item as V, index++, this)) { + return false; + } + } + return true; + } + + /** + * Time Complexity: O(n) + * Space Complexity: O(1) + */ + /** + * Time Complexity: O(n) + * Space Complexity: O(1) + * + * The "some" function checks if at least one element in a collection satisfies a given predicate. + * @param predicate - The `predicate` parameter is a callback function that takes three arguments: + * `value`, `index`, and `array`. It should return a boolean value indicating whether the current + * element satisfies the condition. + * @param {any} [thisArg] - The `thisArg` parameter is an optional argument that specifies the value + * to be used as the `this` value when executing the `predicate` function. If `thisArg` is provided, + * it will be passed as the `this` value to the `predicate` function. If `thisArg + * @returns a boolean value. It returns true if the predicate function returns true for any element + * in the collection, and false otherwise. + */ + some(predicate: ElementCallback, thisArg?: any): boolean { + let index = 0; + for (const item of this) { + if (predicate.call(thisArg, item as V, index++, this)) { + return true; + } + } + return false; + } + + /** + * Time Complexity: O(n) + * Space Complexity: O(1) + */ + /** + * Time Complexity: O(n) + * Space Complexity: O(1) + * + * The `forEach` function iterates over each element in an array-like object and calls a callback + * function for each element. + * @param callbackfn - The callbackfn parameter is a function that will be called for each element in + * the array. It takes three arguments: the current element being processed, the index of the current + * element, and the array that forEach was called upon. + * @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 + */ + forEach(callbackfn: ElementCallback, thisArg?: any): void { + let index = 0; + for (const item of this) { + callbackfn.call(thisArg, item as V, index++, this) + } + } + + /** + * Time Complexity: O(n) + * Space Complexity: O(1) + */ + /** + * Time Complexity: O(n) + * Space Complexity: O(1) + * + * The `reduce` function iterates over the elements of an array-like object and applies a callback + * function to reduce them into a single value. + * @param callbackfn - The callbackfn parameter is a function that will be called for each element in + * the array. It takes four arguments: + * @param {U} initialValue - The initialValue parameter is the initial value of the accumulator. It + * is the value that the accumulator starts with before the reduction operation begins. + * @returns The `reduce` method is returning the final value of the accumulator after iterating over + * all the elements in the array and applying the callback function to each element. + */ + reduce(callbackfn: ReduceElementCallback, initialValue: U): U { + let accumulator = initialValue; + let index = 0; + for (const item of this) { + accumulator = callbackfn(accumulator, item as V, index++, this) + } + return accumulator; + } + + protected abstract _getIterator(...args: any[]): IterableIterator; +} diff --git a/src/data-structures/binary-tree/binary-tree.ts b/src/data-structures/binary-tree/binary-tree.ts index 7a4d41d..7ee9f45 100644 --- a/src/data-structures/binary-tree/binary-tree.ts +++ b/src/data-structures/binary-tree/binary-tree.ts @@ -13,7 +13,7 @@ import type { BTNKey, BTNodeEntry, BTNodeExemplar, - BTNodeKeyOrNode + BTNodeKeyOrNode, } from '../../types'; import { BinaryTreeNested, @@ -22,11 +22,13 @@ import { DFSOrderPattern, FamilyPosition, IterationType, - NodeDisplayLayout + NodeDisplayLayout, + PairCallback } from '../../types'; import { IBinaryTree } from '../../interfaces'; import { trampoline } from '../../utils'; import { Queue } from '../queue'; +import { IterablePairBase } from "../base"; /** * Represents a node in a binary tree. @@ -103,7 +105,7 @@ export class BinaryTreeNode = BinaryTree * 9. Complete Trees: All levels are fully filled except possibly the last, filled from left to right. */ -export class BinaryTree = BinaryTreeNode>, TREE extends BinaryTree = BinaryTree>> +export class BinaryTree = BinaryTreeNode>, TREE extends BinaryTree = BinaryTree>> extends IterablePairBase implements IBinaryTree { iterationType = IterationType.ITERATIVE @@ -118,7 +120,7 @@ export class BinaryTree = BinaryTreeNode * required. */ constructor(elements?: Iterable>, options?: Partial) { - + super(); if (options) { const { iterationType } = options; if (iterationType) { @@ -1724,47 +1726,6 @@ export class BinaryTree = BinaryTreeNode return ans; } - /** - * Time complexity: O(n) - * Space complexity: O(n) - */ - - /** - * Time complexity: O(n) - * Space complexity: O(n) - * - * The function "keys" returns an array of keys from a given object. - * @returns an array of BTNKey objects. - */ - keys(): BTNKey[] { - const keys: BTNKey[] = []; - for (const entry of this) { - keys.push(entry[0]); - } - return keys; - } - - /** - * Time complexity: O(n) - * Space complexity: O(n) - */ - - /** - * Time complexity: O(n) - * Space complexity: O(n) - * - * The function "values" returns an array of values from a map-like object. - * @returns The `values()` method is returning an array of values (`V`) from the entries in the - * object. - */ - values(): (V | undefined)[] { - const values: (V | undefined)[] = []; - for (const entry of this) { - values.push(entry[1]); - } - return values; - } - /** * Time complexity: O(n) * Space complexity: O(n) @@ -1785,34 +1746,30 @@ export class BinaryTree = BinaryTreeNode } /** - * Time complexity: O(n) - * Space complexity: O(1) + * Time Complexity: O(n) + * Space Complexity: O(n) */ /** - * The `forEach` function iterates over each entry in a tree and calls a callback function with the - * entry and the tree as arguments. - * @param callback - The callback parameter is a function that will be called for each entry in the - * tree. It takes two parameters: entry and tree. + * Time Complexity: O(n) + * Space Complexity: O(n) + * + * The `filter` function creates a new tree by iterating over the elements of the current tree and + * adding only the elements that satisfy the given predicate function. + * @param predicate - The `predicate` parameter is a function that takes three arguments: `value`, + * `key`, and `index`. It should return a boolean value indicating whether the pair should be + * included in the filtered tree or not. + * @param {any} [thisArg] - The `thisArg` parameter is an optional argument that specifies the value + * to be used as the `this` value when executing the `predicate` function. If `thisArg` is provided, + * it will be passed as the first argument to the `predicate` function. If `thisArg` is + * @returns The `filter` method is returning a new tree object that contains the key-value pairs that + * pass the given predicate function. */ - forEach(callback: (entry: [BTNKey, V | undefined], tree: this) => void): void { - for (const entry of this) { - callback(entry, this); - } - } - - /** - * The `filter` function creates a new tree by iterating over the entries of the current tree and - * adding the entries that satisfy the given predicate. - * @param predicate - The `predicate` parameter is a function that takes two arguments: `entry` and - * `tree`. - * @returns The `filter` method is returning a new tree object that contains only the entries that - * satisfy the given predicate function. - */ - filter(predicate: (entry: [BTNKey, V | undefined], tree: this) => boolean) { + filter(predicate: PairCallback, thisArg?: any) { const newTree = this.createTree(); + let index = 0; for (const [key, value] of this) { - if (predicate([key, value], this)) { + if (predicate.call(thisArg, value, key, index++, this)) { newTree.add([key, value]); } } @@ -1820,87 +1777,43 @@ export class BinaryTree = BinaryTreeNode } /** - * The `map` function creates a new tree by applying a callback function to each entry in the current - * tree. - * @param callback - The callback parameter is a function that takes two arguments: entry and tree. + * Time Complexity: O(n) + * Space Complexity: O(n) + */ + + /** + * Time Complexity: O(n) + * Space Complexity: O(n) + * + * The `map` function creates a new tree by applying a callback function to each key-value pair in + * the original tree. + * @param callback - The callback parameter is a function that will be called for each key-value pair + * in the tree. It takes four arguments: the value of the current pair, the key of the current pair, + * the index of the current pair, and a reference to the tree itself. The callback function should + * return a new + * @param {any} [thisArg] - The `thisArg` parameter is an optional argument that allows you to + * specify the value of `this` within the callback function. If you pass a value for `thisArg`, it + * will be used as the `this` value when the callback function is called. If you don't pass a value * @returns The `map` method is returning a new tree object. */ - map(callback: (entry: [BTNKey, V | undefined], tree: this) => V) { + map(callback: PairCallback, thisArg?: any) { const newTree = this.createTree(); + let index = 0; for (const [key, value] of this) { - newTree.add([key, callback([key, value], this)]); + newTree.add([key, callback.call(thisArg, value, key, index++, this)]); } return newTree; } - // TODO Type error, need to return a TREE that is a value type only for callback function. - // map(callback: (entry: [BTNKey, V | undefined], tree: this) => NV) { - // const newTree = this.createTree(); - // for (const [key, value] of this) { - // newTree.add(key, callback([key, value], this)); - // } - // return newTree; - // } - - /** - * The `reduce` function iterates over the entries of a tree and applies a callback function to each - * entry, accumulating a single value. - * @param callback - The callback parameter is a function that takes three arguments: accumulator, - * entry, and tree. It is called for each entry in the tree and is used to accumulate a single value - * based on the logic defined in the callback function. - * @param {T} initialValue - The initialValue parameter is the initial value of the accumulator. It - * is the value that will be passed as the first argument to the callback function when reducing the - * elements of the tree. - * @returns The `reduce` method is returning the final value of the accumulator after iterating over - * all the entries in the tree and applying the callback function to each entry. - */ - reduce(callback: (accumulator: T, entry: [BTNKey, V | undefined], tree: this) => T, initialValue: T): T { - let accumulator = initialValue; - for (const [key, value] of this) { - accumulator = callback(accumulator, [key, value], this); - } - return accumulator; - } - - /** - * The above function is an iterator for a binary tree that can be used to traverse the tree in - * either an iterative or recursive manner. - * @param node - The `node` parameter represents the current node in the binary tree from which the - * iteration starts. It is an optional parameter with a default value of `this.root`, which means - * that if no node is provided, the iteration will start from the root of the binary tree. - * @returns The `*[Symbol.iterator]` method returns a generator object that yields the keys of the - * binary tree nodes in a specific order. - */ - * [Symbol.iterator](node = this.root): Generator<[BTNKey, V | undefined], void, undefined> { - if (!node) return; - - if (this.iterationType === IterationType.ITERATIVE) { - const stack: (N | null | undefined)[] = []; - let current: N | null | undefined = node; - - while (current || stack.length > 0) { - while (current && !isNaN(current.key)) { - stack.push(current); - current = current.left; - } - - current = stack.pop(); - - if (current && !isNaN(current.key)) { - yield [current.key, current.value]; - current = current.right; - } - } - } else { - if (node.left && !isNaN(node.key)) { - yield* this[Symbol.iterator](node.left); - } - yield [node.key, node.value]; - if (node.right && !isNaN(node.key)) { - yield* this[Symbol.iterator](node.right); - } - } - } + // // TODO Type error, need to return a TREE that is a value type only for callback function. + // // map(callback: (entry: [BTNKey, V | undefined], tree: this) => NV) { + // // const newTree = this.createTree(); + // // for (const [key, value] of this) { + // // newTree.add(key, callback([key, value], this)); + // // } + // // return newTree; + // // } + // /** * The `print` function is used to display a binary tree structure in a visually appealing way. @@ -1931,6 +1844,37 @@ export class BinaryTree = BinaryTreeNode display(beginRoot); } + protected* _getIterator(node = this.root): IterableIterator<[BTNKey, V | undefined]> { + if (!node) return; + + if (this.iterationType === IterationType.ITERATIVE) { + const stack: (N | null | undefined)[] = []; + let current: N | null | undefined = node; + + while (current || stack.length > 0) { + while (current && !isNaN(current.key)) { + stack.push(current); + current = current.left; + } + + current = stack.pop(); + + if (current && !isNaN(current.key)) { + yield [current.key, current.value]; + current = current.right; + } + } + } else { + if (node.left && !isNaN(node.key)) { + yield* this[Symbol.iterator](node.left); + } + yield [node.key, node.value]; + if (node.right && !isNaN(node.key)) { + yield* this[Symbol.iterator](node.right); + } + } + } + protected _displayAux(node: N | null | undefined, options: BinaryTreePrintOptions): NodeDisplayLayout { const { isShowNull, isShowUndefined, isShowRedBlackNIL } = options; const emptyDisplayLayout = [['─'], 1, 0, 0]; diff --git a/src/data-structures/graph/abstract-graph.ts b/src/data-structures/graph/abstract-graph.ts index 7d7f5d2..72dfcb2 100644 --- a/src/data-structures/graph/abstract-graph.ts +++ b/src/data-structures/graph/abstract-graph.ts @@ -8,8 +8,10 @@ import { uuidV4 } from '../../utils'; import { PriorityQueue } from '../priority-queue'; import type { DijkstraResult, VertexKey } from '../../types'; +import { PairCallback } from "../../types"; import { IGraph } from '../../interfaces'; import { Queue } from '../queue'; +import { IterablePairBase } from "../base"; export abstract class AbstractVertex { key: VertexKey; @@ -64,7 +66,11 @@ export abstract class AbstractGraph< E = any, VO extends AbstractVertex = AbstractVertex, EO extends AbstractEdge = AbstractEdge -> implements IGraph { +> extends IterablePairBase implements IGraph { + constructor() { + super(); + } + protected _vertices: Map = new Map(); get vertices(): Map { @@ -1159,50 +1165,71 @@ export abstract class AbstractGraph< return this.tarjan(false, true, false, false).bridges; } - * [Symbol.iterator](): Iterator<[VertexKey, V | undefined]> { - for (const vertex of this._vertices.values()) { - yield [vertex.key, vertex.value]; - } - } + /** + * Time Complexity: O(n) + * Space Complexity: O(n) + */ - forEach(callback: (entry: [VertexKey, V | undefined], index: number, map: Map) => void): void { - let index = 0; - for (const vertex of this) { - callback(vertex, index, this._vertices); - index++; - } - } - - filter(predicate: (entry: [VertexKey, V | undefined], index: number, map: Map) => boolean): [VertexKey, V | undefined][] { + /** + * Time Complexity: O(n) + * Space Complexity: O(n) + * + * The `filter` function iterates over key-value pairs in a data structure and returns an array of + * pairs that satisfy a given predicate. + * @param predicate - The `predicate` parameter is a callback function that takes four arguments: + * `value`, `key`, `index`, and `this`. It is used to determine whether an element should be included + * in the filtered array. The callback function should return `true` if the element should be + * included, and ` + * @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 provided, it will be + * @returns The `filter` method returns an array of key-value pairs `[VertexKey, V | undefined][]` + * that satisfy the given predicate function. + */ + filter(predicate: PairCallback, thisArg?: any): [VertexKey, V | undefined][] { const filtered: [VertexKey, V | undefined][] = []; let index = 0; - for (const entry of this) { - if (predicate(entry, index, this._vertices)) { - filtered.push(entry); + for (const [key, value] of this) { + if (predicate.call(thisArg, value, key, index, this)) { + filtered.push([key, value]); } index++; } return filtered; } - map(callback: (entry: [VertexKey, V | undefined], index: number, map: Map) => T): T[] { + /** + * Time Complexity: O(n) + * Space Complexity: O(n) + */ + + /** + * Time Complexity: O(n) + * Space Complexity: O(n) + * + * The `map` function iterates over the elements of a collection and applies a callback function to + * each element, returning an array of the results. + * @param callback - The callback parameter is a function that will be called for each element in the + * map. It takes four arguments: + * @param {any} [thisArg] - The `thisArg` parameter is an optional argument that allows you to + * specify the value of `this` within the callback function. If `thisArg` is provided, it will be + * used as the `this` value when calling the callback function. If `thisArg` is not provided, ` + * @returns The `map` function is returning an array of type `T[]`. + */ + map(callback: PairCallback, thisArg?: any): T[] { const mapped: T[] = []; let index = 0; - for (const entry of this) { - mapped.push(callback(entry, index, this._vertices)); + for (const [key, value] of this) { + mapped.push(callback.call(thisArg, value, key, index, this)); index++; } return mapped; } - reduce(callback: (accumulator: T, entry: [VertexKey, V | undefined], index: number, map: Map) => T, initialValue: T): T { - let accumulator: T = initialValue; - let index = 0; - for (const entry of this) { - accumulator = callback(accumulator, entry, index, this._vertices); - index++; + protected* _getIterator(): IterableIterator<[VertexKey, V | undefined]> { + for (const vertex of this._vertices.values()) { + yield [vertex.key, vertex.value]; } - return accumulator; } protected abstract _addEdgeOnly(edge: EO): boolean; diff --git a/src/data-structures/hash/hash-map.ts b/src/data-structures/hash/hash-map.ts index bcf87b9..b119cd7 100644 --- a/src/data-structures/hash/hash-map.ts +++ b/src/data-structures/hash/hash-map.ts @@ -7,9 +7,10 @@ */ import { isWeakKey, rangeCheck } from '../../utils'; -import { HashMapLinkedNode, HashMapOptions, HashMapStoreItem } from '../../types'; +import { HashMapLinkedNode, HashMapOptions, HashMapStoreItem, PairCallback } from '../../types'; +import { IterablePairBase } from "../base"; -export class HashMap { +export class HashMap extends IterablePairBase { protected _store: { [key: string]: HashMapStoreItem } = {}; protected _objMap: Map = new Map(); @@ -24,6 +25,7 @@ export class HashMap { constructor(elements: Iterable<[K, V]> = [], options?: { hashFn: (key: K) => string }) { + super(); if (options) { const { hashFn } = options; if (hashFn) { @@ -145,102 +147,14 @@ export class HashMap { } /** - * The function returns an iterator that yields key-value pairs from both an object store and an - * object map. + * Time Complexity: O(n) + * Space Complexity: O(n) */ - * [Symbol.iterator](): 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 returns an iterator that yields key-value pairs from the object. - */ - * entries(): IterableIterator<[K, V]> { - for (const item of this) { - yield item; - } - } - - /** - * The function `keys()` returns an iterator that yields all the keys of the object. - */ - * keys(): IterableIterator { - for (const [key] of this) { - yield key; - } - } - - * values(): IterableIterator { - for (const [, value] of this) { - yield value; - } - } - - /** - * The `every` function checks if every element in a HashMap satisfies 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 test each element in the map against a condition. If the predicate - * function returns false for any element, the every() method will return false. If the predicate - * function returns true for all - * @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 method is returning a boolean value. It returns true if the predicate function - * returns true for every element in the map, and false otherwise. - */ - every(predicate: (value: V, key: K, index: number, map: HashMap) => boolean, thisArg?: any): boolean { - let index = 0; - for (const [key, value] of this) { - if (!predicate.call(thisArg, value, key, index++, this)) { - return false; - } - } - return true; - } - - /** - * The "some" function checks if at least one element in a HashMap satisfies a given predicate. - * @param predicate - The `predicate` parameter is a function that takes four arguments: `value`, - * `key`, `index`, and `map`. It is used to determine whether a specific condition is met for a given - * key-value pair in the `HashMap`. - * @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 a boolean value. It returns true if the predicate function returns true for any element - * in the map, and false otherwise. - */ - some(predicate: (value: V, key: K, index: number, map: HashMap) => boolean, thisArg?: any): boolean { - let index = 0; - for (const [key, value] of this) { - if (predicate.call(thisArg, value, key, index++, this)) { - return true; - } - } - return false; - } - - /** - * The `forEach` function iterates over the elements of a HashMap and applies a callback function to - * each element. - * @param callbackfn - A 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 inside the `callbackfn` function. If `thisArg - */ - forEach(callbackfn: (value: V, key: K, index: number, map: HashMap) => void, thisArg?: any): void { - let index = 0; - for (const [key, value] of this) { - callbackfn.call(thisArg, value, key, index++, this); - } - } /** + * 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 @@ -251,7 +165,7 @@ export class HashMap { * @returns The `map` method is returning a new `HashMap` object with the transformed values based on * the provided callback function. */ - map(callbackfn: (value: V, key: K, index: number, map: HashMap) => U, thisArg?: any): HashMap { + map(callbackfn: PairCallback, thisArg?: any): HashMap { const resultMap = new HashMap(); let index = 0; for (const [key, value] of this) { @@ -261,6 +175,14 @@ export class HashMap { } /** + * Time Complexity: O(n) + * Space Complexity: O(n) + */ + + /** + * 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, @@ -273,7 +195,7 @@ export class HashMap { * @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: (value: V, key: K, index: number, map: HashMap) => boolean, thisArg?: any): HashMap { + filter(predicate: PairCallback, thisArg?: any): HashMap { const filteredMap = new HashMap(); let index = 0; for (const [key, value] of this) { @@ -284,28 +206,21 @@ export class HashMap { return filteredMap; } - /** - * The `reduce` function iterates over the elements of a HashMap and applies a callback function to - * each element, accumulating a single value. - * @param callbackfn - The callback function that will be called for each element in the HashMap. It - * takes five parameters: - * @param {U} initialValue - The initialValue parameter is the initial value of the accumulator. It - * is the value that will be used as the first argument of the callback function when reducing the - * elements of the map. - * @returns The `reduce` method is returning the final value of the accumulator after iterating over - * all the elements in the `HashMap`. - */ - reduce(callbackfn: (accumulator: U, currentValue: V, currentKey: K, index: number, map: HashMap) => U, initialValue: U): U { - let accumulator = initialValue; - let index = 0; - for (const [key, value] of this) { - accumulator = callbackfn(accumulator, value, key, index++, this); - } - return accumulator; + print(): void { + console.log([...this.entries()]); } - print(): void{ - console.log([...this.entries()]); + /** + * 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]; + } } protected _hashFn: (key: K) => string = (key: K) => String(key); @@ -333,7 +248,7 @@ export class HashMap { } } -export class LinkedHashMap { +export class LinkedHashMap extends IterablePairBase { protected _noObjMap: Record> = {}; protected _objMap = new WeakMap>(); @@ -349,6 +264,7 @@ export class LinkedHashMap { hashFn: (key: K) => String(key), objHashFn: (key: K) => (key) }) { + super(); this._sentinel = >{}; this._sentinel.prev = this._sentinel.next = this._head = this._tail = this._sentinel; @@ -492,18 +408,6 @@ export class LinkedHashMap { } } - keys(): K[] { - const keys: K[] = []; - for (const [key] of this) keys.push(key); - return keys; - } - - values(): V[] { - const values: V[] = []; - for (const [, value] of this) values.push(value); - return values; - } - /** * Time Complexity: O(1) * Space Complexity: O(1) @@ -644,36 +548,30 @@ export class LinkedHashMap { } /** - * Time Complexity: O(n), where n is the number of elements in the LinkedHashMap. - * Space Complexity: O(1) - * - * The `forEach` function iterates over each element in a LinkedHashMap and executes a callback function on - * each element. - * @param callback - The callback parameter is a function that will be called for each element in the - * LinkedHashMap. It takes three arguments: + * Time Complexity: O(n) + * Space Complexity: O(n) */ - forEach(callback: (element: [K, V], index: number, hashMap: LinkedHashMap) => void) { - let index = 0; - let node = this._head; - while (node !== this._sentinel) { - callback(<[K, V]>[node.key, node.value], index++, this); - node = node.next; - } - } /** - * The `filter` function takes a predicate function and returns a new LinkedHashMap containing only the - * key-value pairs that satisfy the predicate. - * @param predicate - The `predicate` parameter is a function that takes two arguments: `element` and - * `map`. - * @returns a new LinkedHashMap object that contains the key-value pairs from the original LinkedHashMap that - * satisfy the given predicate function. + * 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: (element: [K, V], index: number, map: LinkedHashMap) => boolean): LinkedHashMap { + filter(predicate: PairCallback, thisArg?: any): LinkedHashMap { const filteredMap = new LinkedHashMap(); let index = 0; for (const [key, value] of this) { - if (predicate([key, value], index, this)) { + if (predicate.call(thisArg, value, key, index, this)) { filteredMap.set(key, value); } index++; @@ -682,43 +580,40 @@ export class LinkedHashMap { } /** - * The `map` function takes a callback function and returns a new LinkedHashMap with the values transformed - * by the callback. - * @param callback - The `callback` parameter is a function that takes two arguments: `element` and - * `map`. - * @returns a new LinkedHashMap object with the values mapped according to the provided callback function. + * Time Complexity: O(n) + * Space Complexity: O(n) */ - map(callback: (element: [K, V], index: number, map: LinkedHashMap) => NV): LinkedHashMap { + + /** + * 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: PairCallback, thisArg?: any): LinkedHashMap { const mappedMap = new LinkedHashMap(); let index = 0; for (const [key, value] of this) { - const newValue = callback([key, value], index, this); + const newValue = callback.call(thisArg, value, key, index, this); mappedMap.set(key, newValue); index++; } return mappedMap; } - /** - * The `reduce` function iterates over the elements of a LinkedHashMap and applies a callback function to - * each element, accumulating a single value. - * @param callback - The callback parameter is a function that takes three arguments: accumulator, - * element, and map. It is called for each element in the LinkedHashMap and is used to accumulate a single - * result. - * @param {A} initialValue - The `initialValue` parameter is the initial value of the accumulator. It - * is the value that will be passed as the first argument to the `callback` function when reducing - * the elements of the map. - * @returns The `reduce` function is returning the final value of the accumulator after iterating - * over all the elements in the LinkedHashMap and applying the callback function to each element. - */ - reduce(callback: (accumulator: A, element: [K, V], index: number, map: LinkedHashMap) => A, initialValue: A): A { - let accumulator = initialValue; - let index = 0; - for (const entry of this) { - accumulator = callback(accumulator, entry, index, this); - index++; - } - return accumulator; + print() { + console.log([...this]); } /** @@ -727,7 +622,7 @@ export class LinkedHashMap { * * The above function is an iterator that yields key-value pairs from a linked list. */ - * [Symbol.iterator]() { + protected* _getIterator() { let node = this._head; while (node !== this._sentinel) { yield <[K, V]>[node.key, node.value]; @@ -735,10 +630,6 @@ export class LinkedHashMap { } } - print() { - console.log([...this]); - } - /** * Time Complexity: O(1) * Space Complexity: O(1) diff --git a/src/data-structures/heap/heap.ts b/src/data-structures/heap/heap.ts index 55b52e3..60b90ae 100644 --- a/src/data-structures/heap/heap.ts +++ b/src/data-structures/heap/heap.ts @@ -5,13 +5,15 @@ * @license MIT License */ -import type { Comparator, DFSOrderPattern } from '../../types'; +import type { Comparator, DFSOrderPattern, ElementCallback } from '../../types'; import { HeapOptions } from "../../types"; +import { IterableElementBase } from "../base"; -export class Heap { +export class Heap extends IterableElementBase { options: HeapOptions; constructor(elements?: Iterable, options?: HeapOptions) { + super(); const defaultComparator = (a: E, b: E) => { if (!(typeof a === 'number' && typeof b === 'number')) { throw new Error('The a, b params of compare function must be number'); @@ -339,56 +341,75 @@ export class Heap { for (let i = Math.floor(this.size / 2); i >= 0; i--) this._sinkDown(i, this.elements.length >> 1); } - * [Symbol.iterator]() { - for (const element of this.elements) { - yield element; - } - } + /** + * Time Complexity: O(n) + * Space Complexity: O(n) + */ - forEach(callback: (element: E, index: number, heap: this) => void): void { + /** + * Time Complexity: O(n) + * Space Complexity: O(n) + * + * The `filter` function creates a new Heap object containing elements that pass a given callback + * function. + * @param callback - The `callback` parameter is a function that will be called for each element in + * the heap. It takes three arguments: the current element, the index of the current element, and the + * heap itself. The callback function should return a boolean value indicating whether the current + * element should be included in the filtered list + * @param {any} [thisArg] - The `thisArg` parameter is an optional argument that specifies the value + * to be used as `this` when executing the `callback` function. If `thisArg` is provided, it will be + * passed as the `this` value to the `callback` function. If `thisArg` is + * @returns The `filter` method is returning a new `Heap` object that contains the elements that pass + * the filter condition specified by the `callback` function. + */ + filter(callback: ElementCallback, thisArg?: any): Heap { + const filteredList = new Heap(); let index = 0; - for (const el of this) { - callback(el, index, this); - index++; - } - } - - filter(predicate: (element: E, index: number, heap: Heap) => boolean): Heap { - const filteredHeap: Heap = new Heap([], this.options); - let index = 0; - for (const el of this) { - if (predicate(el, index, this)) { - filteredHeap.push(el); + for (const current of this) { + if (callback.call(thisArg, current, index, this)) { + filteredList.push(current); } index++; } - return filteredHeap; + return filteredList; } - map(callback: (element: E, index: number, heap: Heap) => T, comparator: Comparator): Heap { + /** + * Time Complexity: O(n) + * Space Complexity: O(n) + */ + + /** + * Time Complexity: O(n) + * Space Complexity: O(n) + * + * The `map` function creates a new heap by applying a callback function to each element of the + * original heap. + * @param callback - The callback parameter is a function that will be called for each element in the + * original heap. It takes three arguments: the current element, the index of the current element, + * and the original heap itself. The callback function should return a value of type T, which will be + * added to the mapped heap. + * @param comparator - The `comparator` parameter is a function that is used to compare elements in + * the heap. It takes two arguments, `a` and `b`, and returns a negative number if `a` is less than + * `b`, a positive number if `a` is greater than `b`, or + * @param {any} [thisArg] - The `thisArg` parameter is an optional argument that allows you to + * specify the value of `this` within the callback function. It is used when you want to bind a + * specific object as the context for the callback function. If `thisArg` is not provided, + * `undefined` is used as + * @returns a new instance of the Heap class, which is created using the mapped elements from the + * original Heap. + */ + map(callback: ElementCallback, comparator: Comparator, thisArg?: any): Heap { const mappedHeap: Heap = new Heap([], { comparator: comparator }); let index = 0; for (const el of this) { - mappedHeap.add(callback(el, index, this)); + mappedHeap.add(callback.call(thisArg, el, index, this)); index++; } return mappedHeap; } - reduce( - callback: (accumulator: T, currentValue: E, currentIndex: number, heap: Heap) => T, - initialValue: T - ): T { - let accumulator: T = initialValue; - let index = 0; - for (const el of this) { - accumulator = callback(accumulator, el, index, this); - index++; - } - return accumulator; - } - /** * Time Complexity: O(log n) * Space Complexity: O(1) @@ -398,6 +419,12 @@ export class Heap { console.log([...this]); } + protected* _getIterator() { + for (const element of this.elements) { + yield element; + } + } + /** * Time Complexity: O(n) * Space Complexity: O(1) diff --git a/src/data-structures/index.ts b/src/data-structures/index.ts index 472221d..3cd0a72 100644 --- a/src/data-structures/index.ts +++ b/src/data-structures/index.ts @@ -9,3 +9,4 @@ export * from './heap'; export * from './priority-queue'; export * from './matrix'; export * from './trie'; +export * from './base'; diff --git a/src/data-structures/linked-list/doubly-linked-list.ts b/src/data-structures/linked-list/doubly-linked-list.ts index 6fc50ce..4c4602a 100644 --- a/src/data-structures/linked-list/doubly-linked-list.ts +++ b/src/data-structures/linked-list/doubly-linked-list.ts @@ -1,3 +1,6 @@ +import { IterableElementBase } from "../base"; +import { ElementCallback } from "../../types"; + /** * data-structure-typed * @@ -22,11 +25,12 @@ export class DoublyLinkedListNode { } } -export class DoublyLinkedList { +export class DoublyLinkedList extends IterableElementBase { /** * The constructor initializes the linked list with an empty head, tail, and length. */ constructor(elements?: Iterable) { + super(); this._head = undefined; this._tail = undefined; this._length = 0; @@ -724,59 +728,32 @@ export class DoublyLinkedList { } /** - * The function returns an iterator that iterates over the values of a linked list. - */ - * [Symbol.iterator]() { - let current = this.head; - - while (current) { - yield current.value; - current = current.next; - } - } - - /** - * Time Complexity: O(n), where n is the number of elements in the linked list. - * Space Complexity: O(1) - */ - - /** - * Time Complexity: O(n), where n is the number of elements in the linked list. - * Space Complexity: O(1) - * - * The `forEach` function iterates over each element in a linked list and applies a callback function to each element. - * @param callback - The callback parameter is a function that takes two arguments: value and index. The value argument - * represents the value of the current node in the linked list, and the index argument represents the index of the - * current node in the linked list. - */ - forEach(callback: (value: E, index: number, list: DoublyLinkedList) => void): void { - let index = 0; - for (const el of this) { - callback(el, index, this); - index++; - } - } - - /** - * Time Complexity: O(n), where n is the number of elements in the linked list. + * Time Complexity: O(n) * Space Complexity: O(n) */ /** - * Time Complexity: O(n), where n is the number of elements in the linked list. + * Time Complexity: O(n) * Space Complexity: O(n) * - * The `filter` function iterates through a DoublyLinkedList and returns a new DoublyLinkedList containing only the - * elements that satisfy the given callback function. - * @param callback - The `callback` parameter is a function that takes a value of type `E` and returns a boolean value. - * It is used to determine whether a value should be included in the filtered list or not. - * @returns The filtered list, which is an instance of the DoublyLinkedList class. + * The `filter` function creates a new DoublyLinkedList by iterating over the elements of the current + * list and applying a callback function to each element, returning only the elements for which the + * callback function returns true. + * @param callback - The `callback` parameter is a function that will be called for each element in + * the DoublyLinkedList. It takes three arguments: the current element, the index of the current + * element, and the DoublyLinkedList itself. The callback function should return a boolean value + * indicating whether the current element should be included + * @param {any} [thisArg] - The `thisArg` parameter is an optional argument that specifies the value + * to be used as `this` when executing the `callback` function. If `thisArg` is provided, it will be + * passed as the `this` value to the `callback` function. If `thisArg` is + * @returns The `filter` method is returning a new `DoublyLinkedList` object that contains the + * elements that pass the filter condition specified by the `callback` function. */ - filter(callback: (value: E, index: number, list: DoublyLinkedList) => boolean): DoublyLinkedList { + filter(callback: ElementCallback, thisArg?: any): DoublyLinkedList { const filteredList = new DoublyLinkedList(); let index = 0; for (const current of this) { - if (callback(current, index, this)) { + if (callback.call(thisArg, current, index, this)) { filteredList.push(current); } index++; @@ -790,21 +767,27 @@ export class DoublyLinkedList { */ /** - * Time Complexity: O(n), where n is the number of elements in the linked list. + * Time Complexity: O(n) * Space Complexity: O(n) * - * The `map` function takes a callback function and applies it to each element in the DoublyLinkedList, returning a new - * DoublyLinkedList with the transformed values. - * @param callback - The callback parameter is a function that takes a value of type E (the type of values stored in - * the original DoublyLinkedList) and returns a value of type T (the type of values that will be stored in the mapped - * DoublyLinkedList). - * @returns The `map` function is returning a new instance of `DoublyLinkedList` that contains the mapped values. + * The `map` function creates a new DoublyLinkedList by applying a callback function to each element + * in the original list. + * @param callback - The callback parameter is a function that will be called for each element in the + * DoublyLinkedList. It takes three arguments: the current element, the index of the current element, + * and the DoublyLinkedList itself. The callback function should return a value that will be added to + * the new DoublyLinkedList that + * @param {any} [thisArg] - The `thisArg` parameter is an optional argument that specifies the value + * to be used as `this` when executing the `callback` function. If `thisArg` is provided, it will be + * passed as the `this` value to the `callback` function. If `thisArg` is + * @returns The `map` function is returning a new `DoublyLinkedList` object that contains the results + * of applying the provided `callback` function to each element in the original `DoublyLinkedList` + * object. */ - map(callback: (value: E, index: number, list: DoublyLinkedList) => T): DoublyLinkedList { + map(callback: ElementCallback, thisArg?: any): DoublyLinkedList { const mappedList = new DoublyLinkedList(); let index = 0; for (const current of this) { - mappedList.push(callback(current, index, this)); + mappedList.push(callback.call(thisArg, current, index, this)); index++; } @@ -816,31 +799,19 @@ export class DoublyLinkedList { * Space Complexity: O(n) */ - /** - * Time Complexity: O(n), where n is the number of elements in the linked list. - * Space Complexity: O(n) - * - * The `reduce` function iterates over a linked list and applies a callback function to each element, accumulating a - * single value. - * @param callback - The `callback` parameter is a function that takes two arguments: `accumulator` and `value`. It is - * used to perform a specific operation on each element of the linked list. - * @param {T} initialValue - The `initialValue` parameter is the initial value of the accumulator. It is the starting - * point for the reduction operation. - * @returns The `reduce` method is returning the final value of the accumulator after iterating through all the - * elements in the linked list. - */ - reduce(callback: (accumulator: T, value: E, index: number, list: DoublyLinkedList) => T, initialValue: T): T { - let accumulator = initialValue; - let index = 0; - for (const current of this) { - accumulator = callback(accumulator, current, index, this); - index++; - } - - return accumulator; - } - print(): void { console.log([...this]); } + + /** + * The function returns an iterator that iterates over the values of a linked list. + */ + protected* _getIterator(): IterableIterator { + let current = this.head; + + while (current) { + yield current.value; + current = current.next; + } + } } diff --git a/src/data-structures/linked-list/singly-linked-list.ts b/src/data-structures/linked-list/singly-linked-list.ts index 60bc03b..c9bd8a2 100644 --- a/src/data-structures/linked-list/singly-linked-list.ts +++ b/src/data-structures/linked-list/singly-linked-list.ts @@ -1,3 +1,6 @@ +import { IterableElementBase } from "../base"; +import { ElementCallback } from "../../types"; + /** * data-structure-typed * @@ -20,11 +23,12 @@ export class SinglyLinkedListNode { } } -export class SinglyLinkedList { +export class SinglyLinkedList extends IterableElementBase { /** * The constructor initializes the linked list with an empty head, tail, and length. */ constructor(elements?: Iterable) { + super(); this._head = undefined; this._tail = undefined; this._length = 0; @@ -670,59 +674,32 @@ export class SinglyLinkedList { } /** - * The function returns an iterator that iterates over the values of a linked list. - */ - * [Symbol.iterator]() { - let current = this.head; - - while (current) { - yield current.value; - current = current.next; - } - } - - /** - * Time Complexity: O(n), where n is the number of elements in the linked list. - * Space Complexity: O(1) - */ - - /** - * Time Complexity: O(n), where n is the number of elements in the linked list. - * Space Complexity: O(1) - * - * The `forEach` function iterates over each element in a linked list and applies a callback function to each element. - * @param callback - The callback parameter is a function that takes two arguments: value and index. The value argument - * represents the value of the current node in the linked list, and the index argument represents the index of the - * current node in the linked list. - */ - forEach(callback: (value: E, index: number, list: SinglyLinkedList) => void): void { - let index = 0; - for (const el of this) { - callback(el, index, this); - index++; - } - } - - /** - * Time Complexity: O(n), where n is the number of elements in the linked list. + * Time Complexity: O(n) * Space Complexity: O(n) */ /** - * Time Complexity: O(n), where n is the number of elements in the linked list. + * Time Complexity: O(n) * Space Complexity: O(n) * - * The `filter` function iterates through a SinglyLinkedList and returns a new SinglyLinkedList containing only the - * elements that satisfy the given callback function. - * @param callback - The `callback` parameter is a function that takes a value of type `E` and returns a boolean value. - * It is used to determine whether a value should be included in the filtered list or not. - * @returns The filtered list, which is an instance of the SinglyLinkedList class. + * The `filter` function creates a new SinglyLinkedList by iterating over the elements of the current + * list and applying a callback function to each element to determine if it should be included in the + * filtered list. + * @param callback - The callback parameter is a function that will be called for each element in the + * list. It takes three arguments: the current element, the index of the current element, and the + * list itself. The callback function should return a boolean value indicating whether the current + * element should be included in the filtered list or not + * @param {any} [thisArg] - The `thisArg` parameter is an optional argument that specifies the value + * to be used as `this` when executing the `callback` function. If `thisArg` is provided, it will be + * passed as the `this` value to the `callback` function. If `thisArg` is + * @returns The `filter` method is returning a new `SinglyLinkedList` object that contains the + * elements that pass the filter condition specified by the `callback` function. */ - filter(callback: (value: E, index: number, list: SinglyLinkedList) => boolean): SinglyLinkedList { + filter(callback: ElementCallback, thisArg?: any): SinglyLinkedList { const filteredList = new SinglyLinkedList(); let index = 0; for (const current of this) { - if (callback(current, index, this)) { + if (callback.call(thisArg, current, index, this)) { filteredList.push(current); } index++; @@ -730,27 +707,30 @@ export class SinglyLinkedList { return filteredList; } - /** - * Time Complexity: O(n), where n is the number of elements in the linked list. - * Space Complexity: O(n) - */ /** * Time Complexity: O(n), where n is the number of elements in the linked list. * Space Complexity: O(n) - * - * The `map` function takes a callback function and applies it to each element in the SinglyLinkedList, returning a new - * SinglyLinkedList with the transformed values. - * @param callback - The callback parameter is a function that takes a value of type E (the type of values stored in - * the original SinglyLinkedList) and returns a value of type T (the type of values that will be stored in the mapped - * SinglyLinkedList). - * @returns The `map` function is returning a new instance of `SinglyLinkedList` that contains the mapped values. */ - map(callback: (value: E, index: number, list: SinglyLinkedList) => T): SinglyLinkedList { + /** + * Time Complexity: O(n) + * Space Complexity: O(n) + * + * The `map` function creates a new SinglyLinkedList by applying a callback function to each element + * of the original list. + * @param callback - The `callback` parameter is a function that will be called for each element in + * the linked list. It takes three arguments: + * @param {any} [thisArg] - The `thisArg` parameter is an optional argument that specifies the value + * to be used as `this` when executing the `callback` function. If `thisArg` is provided, it will be + * passed as the `this` value to the `callback` function. If `thisArg` is + * @returns The `map` function is returning a new `SinglyLinkedList` object that contains the results + * of applying the provided `callback` function to each element in the original list. + */ + map(callback: ElementCallback, thisArg?: any): SinglyLinkedList { const mappedList = new SinglyLinkedList(); let index = 0; for (const current of this) { - mappedList.push(callback(current, index, this)); + mappedList.push(callback.call(thisArg, current, index, this)); index++; } @@ -762,31 +742,16 @@ export class SinglyLinkedList { * Space Complexity: O(n) */ - /** - * Time Complexity: O(n), where n is the number of elements in the linked list. - * Space Complexity: O(n) - * - * The `reduce` function iterates over a linked list and applies a callback function to each element, accumulating a - * single value. - * @param callback - The `callback` parameter is a function that takes two arguments: `accumulator` and `value`. It is - * used to perform a specific operation on each element of the linked list. - * @param {T} initialValue - The `initialValue` parameter is the initial value of the accumulator. It is the starting - * point for the reduction operation. - * @returns The `reduce` method is returning the final value of the accumulator after iterating through all the - * elements in the linked list. - */ - reduce(callback: (accumulator: T, value: E, index: number, list: SinglyLinkedList) => T, initialValue: T): T { - let accumulator = initialValue; - let index = 0; - for (const current of this) { - accumulator = callback(accumulator, current, index, this); - index++; - } - - return accumulator; - } - print(): void { console.log([...this]); } + + protected* _getIterator(): IterableIterator { + let current = this.head; + + while (current) { + yield current.value; + current = current.next; + } + } } diff --git a/src/data-structures/queue/deque.ts b/src/data-structures/queue/deque.ts index 9326682..18ad800 100644 --- a/src/data-structures/queue/deque.ts +++ b/src/data-structures/queue/deque.ts @@ -7,8 +7,9 @@ */ -import { IterableWithSizeOrLength } from "../../types"; +import { ElementCallback, IterableWithSizeOrLength } from "../../types"; import { calcMinUnitsRequired, rangeCheck } from "../../utils"; +import { IterableElementBase } from "../base"; /** * Deque can provide random access with O(1) time complexity @@ -17,7 +18,7 @@ import { calcMinUnitsRequired, rangeCheck } from "../../utils"; * Deque is implemented using a dynamic array. Inserting or deleting beyond both ends of the array may require moving elements or reallocating space. */ -export class Deque { +export class Deque extends IterableElementBase { protected _bucketFirst = 0; protected _firstInBucket = 0; protected _bucketLast = 0; @@ -35,7 +36,7 @@ export class Deque { * stored in each bucket. It determines the size of each bucket in the data structure. */ constructor(elements: IterableWithSizeOrLength = [], bucketSize = (1 << 12)) { - + super(); let _size: number; if ('length' in elements) { if (elements.length instanceof Function) _size = elements.length(); else _size = elements.length; @@ -699,67 +700,31 @@ export class Deque { return arr; } - /** - * Time Complexity: O(n) - * Space Complexity: O(1) - */ - - /** - * Time Complexity: O(n) - * Space Complexity: O(1) - * - * The above function is an implementation of the iterator protocol in TypeScript, allowing the - * object to be iterated over using a for...of loop. - */ - * [Symbol.iterator]() { - for (let i = 0; i < this.size; ++i) { - yield this.getAt(i); - } - } - - /** - * Time Complexity: O(n) - * Space Complexity: O(1) - */ - - /** - * Time Complexity: O(n) - * Space Complexity: O(1) - * - * The `forEach` function iterates over each element in a deque and applies a callback function to - * each element. - * @param callback - The callback parameter is a function that will be called for each element in the - * deque. It takes three parameters: - */ - forEach(callback: (element: E, index: number, deque: this) => void) { - let index = 0; - for (const el of this) { - callback(el, index, this); - index++; - } - } - /** * Time Complexity: O(n) * Space Complexity: O(n) */ - /** * Time Complexity: O(n) * Space Complexity: O(n) * - * The `filter` function creates a new deque containing only the elements that satisfy the given - * predicate function. - * @param predicate - The `predicate` parameter is a function that takes three arguments: `element`, - * `index`, and `deque`. - * @returns The `filter` method is returning a new `Deque` object that contains only the elements - * that satisfy the given `predicate` function. + * The `filter` function creates a new deque containing elements from the original deque that satisfy + * a given predicate function. + * @param predicate - The `predicate` parameter is a callback function that takes three arguments: + * the current element being iterated over, the index of the current element, and the deque itself. + * It should return a boolean value indicating whether the element should be included in the filtered + * deque or not. + * @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 `Deque` object that contains the elements that + * satisfy the given predicate function. */ - filter(predicate: (element: E, index: number, deque: this) => boolean): Deque { + filter(predicate: ElementCallback, thisArg?: any): Deque { const newDeque = new Deque([], this._bucketSize); let index = 0; for (const el of this) { - if (predicate(el, index, this)) { + if (predicate.call(thisArg, el, index, this)) { newDeque.push(el); } index++; @@ -771,21 +736,24 @@ export class Deque { * Time Complexity: O(n) * Space Complexity: O(n) */ - /** * Time Complexity: O(n) * Space Complexity: O(n) * - * The `map` function takes a callback function and applies it to each element in the deque, - * returning a new deque with the results. - * @param callback - The `callback` parameter is a function that takes three arguments: - * @returns The `map` method is returning a new `Deque` object with the transformed elements. + * The `map` function creates a new Deque by applying a callback function to each element of the + * original Deque. + * @param callback - The `callback` parameter is a function that will be called for each element in + * the deque. It takes three arguments: + * @param {any} [thisArg] - The `thisArg` parameter is an optional argument that specifies the value + * to be used as `this` when executing the `callback` function. If `thisArg` is provided, it will be + * passed as the `this` value to the `callback` function. If `thisArg` is + * @returns a new Deque object with the mapped values. */ - map(callback: (element: E, index: number, deque: this) => T): Deque { + map(callback: ElementCallback, thisArg?: any): Deque { const newDeque = new Deque([], this._bucketSize); let index = 0; for (const el of this) { - newDeque.push(callback(el, index, this)); + newDeque.push(callback.call(thisArg, el, index, this)); index++; } return newDeque; @@ -793,34 +761,24 @@ export class Deque { /** * Time Complexity: O(n) - * Space Complexity: O(1) + * Space Complexity: O(n) */ + print(): void { + console.log([...this]) + } + /** * Time Complexity: O(n) * Space Complexity: O(1) * - * The `reduce` function iterates over the elements of a deque and applies a callback function to - * each element, accumulating a single value. - * @param callback - The `callback` parameter is a function that takes four arguments: - * @param {T} initialValue - The `initialValue` parameter is the initial value of the accumulator. It - * is the value that will be passed as the first argument to the `callback` function when reducing - * the elements of the deque. - * @returns the final value of the accumulator after iterating over all elements in the deque and - * applying the callback function to each element. + * The above function is an implementation of the iterator protocol in TypeScript, allowing the + * object to be iterated over using a for...of loop. */ - reduce(callback: (accumulator: T, element: E, index: number, deque: this) => T, initialValue: T): T { - let accumulator = initialValue; - let index = 0; - for (const el of this) { - accumulator = callback(accumulator, el, index, this); - index++; + protected* _getIterator() { + for (let i = 0; i < this.size; ++i) { + yield this.getAt(i); } - return accumulator; - } - - print(): void { - console.log([...this]) } /** diff --git a/src/data-structures/queue/queue.ts b/src/data-structures/queue/queue.ts index 6be108c..ad47d46 100644 --- a/src/data-structures/queue/queue.ts +++ b/src/data-structures/queue/queue.ts @@ -4,42 +4,10 @@ * @class */ import { SinglyLinkedList } from '../linked-list'; +import { IterableElementBase } from "../base"; +import { ElementCallback } from "../../types"; -export class LinkedListQueue extends SinglyLinkedList { - /** - * The enqueue function adds a value to the end of an array. - * @param {E} value - The value parameter represents the value that you want to add to the queue. - */ - enqueue(value: E) { - this.push(value); - } - - /** - * The `dequeue` function removes and returns the first element from a queue, or returns undefined if the queue is empty. - * @returns The method is returning the element at the front of the queue, or undefined if the queue is empty. - */ - dequeue(): E | undefined { - return this.shift(); - } - - /** - * The `getFirst` function returns the value of the head node in a linked list, or `undefined` if the list is empty. - * @returns The `getFirst()` method is returning the value of the `head` node if it exists, otherwise it returns `undefined`. - */ - getFirst(): E | undefined { - return this.head?.value; - } - - /** - * The `peek` function returns the value of the head node in a linked list, or `undefined` if the list is empty. - * @returns The `peek()` method is returning the value of the `head` node if it exists, otherwise it returns `undefined`. - */ - peek(): E | undefined { - return this.getFirst(); - } -} - -export class Queue { +export class Queue extends IterableElementBase { /** * The constructor initializes an instance of a class with an optional array of elements and sets the offset to 0. * @param {E[]} [elements] - The `elements` parameter is an optional array of elements of type `E`. If provided, it @@ -47,6 +15,7 @@ export class Queue { * initialized as an empty array. */ constructor(elements?: E[]) { + super(); this._nodes = elements || []; this._offset = 0; } @@ -304,34 +273,6 @@ export class Queue { console.log([...this]); } - * [Symbol.iterator]() { - for (const item of this.nodes) { - yield item; - } - } - - /** - * Time Complexity: O(n) - * Space Complexity: O(1) - */ - - /** - * Time Complexity: O(n) - * Space Complexity: O(1) - * - * The `forEach` function iterates over each element in a deque and applies a callback function to - * each element. - * @param callback - The callback parameter is a function that will be called for each element in the - * deque. It takes three parameters: - */ - forEach(callback: (element: E, index: number, queue: this) => void) { - let index = 0; - for (const el of this) { - callback(el, index, this); - index++; - } - } - /** * Time Complexity: O(n) * Space Complexity: O(n) @@ -341,18 +282,23 @@ export class Queue { * Time Complexity: O(n) * Space Complexity: O(n) * - * The `filter` function creates a new deque containing only the elements that satisfy the given - * predicate function. - * @param predicate - The `predicate` parameter is a function that takes three arguments: `element`, - * `index`, and `deque`. - * @returns The `filter` method is returning a new `Queue` object that contains only the elements - * that satisfy the given `predicate` function. + * The `filter` function creates a new `Queue` object containing elements from the original `Queue` + * that satisfy a given predicate function. + * @param predicate - The `predicate` parameter is a callback function that takes three arguments: + * the current element being iterated over, the index of the current element, and the queue itself. + * It should return a boolean value indicating whether the element should be included in the filtered + * queue or not. + * @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 `Queue` object that contains the elements that + * satisfy the given predicate function. */ - filter(predicate: (element: E, index: number, queue: this) => boolean): Queue { + filter(predicate: ElementCallback, thisArg?: any): Queue { const newDeque = new Queue([]); let index = 0; for (const el of this) { - if (predicate(el, index, this)) { + if (predicate.call(thisArg, el, index, this)) { newDeque.push(el); } index++; @@ -364,33 +310,72 @@ export class Queue { * Time Complexity: O(n) * Space Complexity: O(n) */ - /** * Time Complexity: O(n) * Space Complexity: O(n) * - * The `map` function takes a callback function and applies it to each element in the deque, - * returning a new deque with the results. - * @param callback - The `callback` parameter is a function that takes three arguments: - * @returns The `map` method is returning a new `Queue` object with the transformed elements. + * The `map` function takes a callback function and applies it to each element in the queue, + * returning a new queue with the results. + * @param callback - The callback parameter is a function that will be called for each element in the + * queue. It takes three arguments: the current element, the index of the current element, and the + * queue itself. The callback function should return a new value that will be added to the new queue. + * @param {any} [thisArg] - The `thisArg` parameter is an optional argument that specifies the value + * to be used as `this` when executing the `callback` function. If `thisArg` is provided, it will be + * passed as the `this` value to the `callback` function. If `thisArg` is + * @returns The `map` function is returning a new `Queue` object with the transformed elements. */ - map(callback: (element: E, index: number, queue: this) => T): Queue { + map(callback: ElementCallback, thisArg?: any): Queue { const newDeque = new Queue([]); let index = 0; for (const el of this) { - newDeque.push(callback(el, index, this)); + newDeque.push(callback.call(thisArg, el, index, this)); index++; } return newDeque; } - reduce(callback: (accumulator: T, element: E, index: number, queue: this) => T, initialValue: T): T { - let accumulator = initialValue; - let index = 0; - for (const el of this) { - accumulator = callback(accumulator, el, index, this); - index++; + /** + * Time Complexity: O(n) + * Space Complexity: O(n) + */ + + protected* _getIterator() { + for (const item of this.nodes) { + yield item; } - return accumulator; } } + +export class LinkedListQueue extends SinglyLinkedList { + /** + * The enqueue function adds a value to the end of an array. + * @param {E} value - The value parameter represents the value that you want to add to the queue. + */ + enqueue(value: E) { + this.push(value); + } + + /** + * The `dequeue` function removes and returns the first element from a queue, or returns undefined if the queue is empty. + * @returns The method is returning the element at the front of the queue, or undefined if the queue is empty. + */ + dequeue(): E | undefined { + return this.shift(); + } + + /** + * The `getFirst` function returns the value of the head node in a linked list, or `undefined` if the list is empty. + * @returns The `getFirst()` method is returning the value of the `head` node if it exists, otherwise it returns `undefined`. + */ + getFirst(): E | undefined { + return this.head?.value; + } + + /** + * The `peek` function returns the value of the head node in a linked list, or `undefined` if the list is empty. + * @returns The `peek()` method is returning the value of the `head` node if it exists, otherwise it returns `undefined`. + */ + peek(): E | undefined { + return this.getFirst(); + } +} \ No newline at end of file diff --git a/src/data-structures/stack/stack.ts b/src/data-structures/stack/stack.ts index 3ba5d3a..d03a77c 100644 --- a/src/data-structures/stack/stack.ts +++ b/src/data-structures/stack/stack.ts @@ -1,9 +1,12 @@ +import { IterableElementBase } from "../base"; +import { ElementCallback } from "../../types"; + /** * @license MIT * @copyright Tyler Zeng * @class */ -export class Stack { +export class Stack extends IterableElementBase { /** * The constructor initializes an array of elements, which can be provided as an optional parameter. * @param {E[]} [elements] - The `elements` parameter is an optional parameter of type `E[]`, which represents an array @@ -11,6 +14,7 @@ export class Stack { * is provided and is an array, it is assigned to the `_elements */ constructor(elements?: Iterable) { + super(); this._elements = []; if (elements) { for (const el of elements) { @@ -154,33 +158,31 @@ export class Stack { } /** - * Custom iterator for the Stack class. - * @returns An iterator object. + * Time Complexity: O(n) + * Space Complexity: O(n) */ - * [Symbol.iterator]() { - for (let i = 0; i < this.elements.length; i++) { - yield this.elements[i]; - } - } /** - * Applies a function to each element of the stack. - * @param {function(E): void} callback - A function to apply to each element. + * Time Complexity: O(n) + * Space Complexity: O(n) + * + * The `filter` function creates a new stack containing elements from the original stack that satisfy + * a given predicate function. + * @param predicate - The `predicate` parameter is a callback function that takes three arguments: + * the current element being iterated over, the index of the current element, and the stack itself. + * It should return a boolean value indicating whether the element should be included in the filtered + * stack or not. + * @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 `Stack` object that contains the elements that + * satisfy the given predicate function. */ - forEach(callback: (element: E, index: number, stack: this) => void): void { - let index = 0; - for (const el of this) { - callback(el, index, this); - index++; - } - } - - - filter(predicate: (element: E, index: number, stack: this) => boolean): Stack { + filter(predicate: ElementCallback, thisArg?: any): Stack { const newStack = new Stack(); let index = 0; for (const el of this) { - if (predicate(el, index, this)) { + if (predicate.call(thisArg, el, index, this)) { newStack.push(el); } index++; @@ -188,28 +190,45 @@ export class Stack { return newStack; } + /** + * Time Complexity: O(n) + * Space Complexity: O(n) + */ - map(callback: (element: E, index: number, stack: this) => T): Stack { + /** + * Time Complexity: O(n) + * Space Complexity: O(n) + * + * The `map` function takes a callback function and applies it to each element in the stack, + * returning a new stack with the results. + * @param callback - The `callback` parameter is a function that will be called for each element in + * the stack. It takes three arguments: + * @param {any} [thisArg] - The `thisArg` parameter is an optional argument that specifies the value + * to be used as `this` when executing the `callback` function. If `thisArg` is provided, it will be + * passed as the `this` value to the `callback` function. If `thisArg` is + * @returns The `map` method is returning a new `Stack` object. + */ + map(callback: ElementCallback, thisArg?: any): Stack { const newStack = new Stack(); let index = 0; for (const el of this) { - newStack.push(callback(el, index, this)); + newStack.push(callback.call(thisArg, el, index, this)); index++; } return newStack; } - reduce(callback: (accumulator: T, element: E, index: number, stack: this) => T, initialValue: T): T { - let accumulator = initialValue; - let index = 0; - for (const el of this) { - accumulator = callback(accumulator, el, index, this); - index++; - } - return accumulator; - } - print(): void { console.log([...this]); } + + /** + * Custom iterator for the Stack class. + * @returns An iterator object. + */ + protected* _getIterator() { + for (let i = 0; i < this.elements.length; i++) { + yield this.elements[i]; + } + } } diff --git a/src/data-structures/trie/trie.ts b/src/data-structures/trie/trie.ts index d143cf8..42d7df9 100644 --- a/src/data-structures/trie/trie.ts +++ b/src/data-structures/trie/trie.ts @@ -6,6 +6,9 @@ * @license MIT License */ +import { IterableElementBase } from "../base"; +import { ElementCallback } from "../../types"; + /** * TrieNode represents a node in the Trie data structure. It holds a character key, a map of children nodes, * and a flag indicating whether it's the end of a word. @@ -25,8 +28,9 @@ export class TrieNode { /** * Trie represents a Trie data structure. It provides basic Trie operations and additional methods. */ -export class Trie { +export class Trie extends IterableElementBase { constructor(words?: string[], caseSensitive = true) { + super(); this._root = new TrieNode(''); this._caseSensitive = caseSensitive; this._size = 0; @@ -339,7 +343,70 @@ export class Trie { return words; } - * [Symbol.iterator](): IterableIterator { + /** + * Time Complexity: O(n) + * Space Complexity: O(n) + */ + + /** + * Time Complexity: O(n) + * Space Complexity: O(n) + * + * The `filter` function takes a predicate function and returns a new array containing all the + * elements for which the predicate function returns true. + * @param predicate - The `predicate` parameter is a callback function that takes three arguments: + * `word`, `index`, and `this`. It should return a boolean value indicating whether the current + * element should be included in the filtered results 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 provided, it will be + * @returns The `filter` method is returning an array of strings (`string[]`). + */ + filter(predicate: ElementCallback, thisArg?: any): string[] { + const results: string[] = []; + let index = 0; + for (const word of this) { + if (predicate.call(thisArg, word, index, this)) { + results.push(word); + } + index++; + } + return results; + } + + /** + * Time Complexity: O(n) + * Space Complexity: O(n) + */ + + /** + * Time Complexity: O(n) + * Space Complexity: O(n) + * + * The `map` function creates a new Trie by applying a callback function to each element in the Trie. + * @param callback - The callback parameter is a function that will be called for each element in the + * Trie. It takes three arguments: the current element in the Trie, the index of the current element, + * and the Trie itself. The callback function should return a new value for the element. + * @param {any} [thisArg] - The `thisArg` parameter is an optional argument that specifies the value + * to be used as `this` when executing the `callback` function. If `thisArg` is provided, it will be + * passed as the `this` value to the `callback` function. If `thisArg` is + * @returns The `map` function is returning a new Trie object. + */ + map(callback: ElementCallback, thisArg?: any): Trie { + const newTrie = new Trie(); + let index = 0; + for (const word of this) { + newTrie.add(callback.call(thisArg, word, index, this)); + index++; + } + return newTrie; + } + + print() { + console.log([...this]); + } + + protected* _getIterator(): IterableIterator { function* _dfs(node: TrieNode, path: string): IterableIterator { if (node.isEnd) { yield path; @@ -352,50 +419,6 @@ export class Trie { yield* _dfs(this.root, ''); } - forEach(callback: (word: string, index: number, trie: this) => void): void { - let index = 0; - for (const word of this) { - callback(word, index, this); - index++; - } - } - - filter(predicate: (word: string, index: number, trie: this) => boolean): string[] { - const results: string[] = []; - let index = 0; - for (const word of this) { - if (predicate(word, index, this)) { - results.push(word); - } - index++; - } - return results; - } - - map(callback: (word: string, index: number, trie: this) => string): Trie { - const newTrie = new Trie(); - let index = 0; - for (const word of this) { - newTrie.add(callback(word, index, this)); - index++; - } - return newTrie; - } - - reduce(callback: (accumulator: T, word: string, index: number, trie: this) => T, initialValue: T): T { - let accumulator = initialValue; - let index = 0; - for (const word of this) { - accumulator = callback(accumulator, word, index, this); - index++; - } - return accumulator; - } - - print() { - console.log([...this]); - } - /** * Time Complexity: O(M), where M is the length of the input string. * Space Complexity: O(1) - Constant space. diff --git a/src/types/data-structures/base/base.ts b/src/types/data-structures/base/base.ts new file mode 100644 index 0000000..8b8e111 --- /dev/null +++ b/src/types/data-structures/base/base.ts @@ -0,0 +1,6 @@ +import { IterableElementBase, IterablePairBase } from "../../../data-structures"; + +export type PairCallback = (value: V, key: K, index: number, container: IterablePairBase) => R; +export type ElementCallback = (element: V, index: number, container: IterableElementBase) => R; +export type ReducePairCallback = (accumulator: R, value: V, key: K, index: number, container: IterablePairBase) => R; +export type ReduceElementCallback = (accumulator: R, element: V, index: number, container: IterableElementBase) => R; diff --git a/src/types/data-structures/base/index.ts b/src/types/data-structures/base/index.ts new file mode 100644 index 0000000..7333734 --- /dev/null +++ b/src/types/data-structures/base/index.ts @@ -0,0 +1 @@ +export * from './base'; \ No newline at end of file diff --git a/src/types/data-structures/index.ts b/src/types/data-structures/index.ts index 4083893..85fc3a8 100644 --- a/src/types/data-structures/index.ts +++ b/src/types/data-structures/index.ts @@ -9,3 +9,4 @@ export * from './queue'; export * from './stack'; export * from './tree'; export * from './trie'; +export * from './base'; diff --git a/test/unit/data-structures/binary-tree/avl-tree.test.ts b/test/unit/data-structures/binary-tree/avl-tree.test.ts index 674c783..21c51f7 100644 --- a/test/unit/data-structures/binary-tree/avl-tree.test.ts +++ b/test/unit/data-structures/binary-tree/avl-tree.test.ts @@ -305,30 +305,30 @@ describe('AVLTree iterative methods test', () => { test('forEach should iterate over all elements', () => { const mockCallback = jest.fn(); - avl.forEach((entry) => { - mockCallback(entry); + avl.forEach((value, key) => { + mockCallback(value, key); }); expect(mockCallback.mock.calls.length).toBe(3); - expect(mockCallback.mock.calls[0][0]).toEqual([1, 'a']); - expect(mockCallback.mock.calls[1][0]).toEqual([2, 'b']); - expect(mockCallback.mock.calls[2][0]).toEqual([3, 'c']); + expect(mockCallback.mock.calls[0]).toEqual(['a', 1]); + expect(mockCallback.mock.calls[1]).toEqual(['b', 2]); + expect(mockCallback.mock.calls[2]).toEqual(['c', 3]); }); test('filter should return a new tree with filtered elements', () => { - const filteredTree = avl.filter(([key]) => key > 1); + const filteredTree = avl.filter((value, key) => key > 1); expect(filteredTree.size).toBe(2); expect([...filteredTree]).toEqual([[2, 'b'], [3, 'c']]); }); test('map should return a new tree with modified elements', () => { - const mappedTree = avl.map(([key]) => (key * 2).toString()); + const mappedTree = avl.map((value, key) => (key * 2).toString()); expect(mappedTree.size).toBe(3); expect([...mappedTree]).toEqual([[1, '2'], [2, '4'], [3, '6']]); }); test('reduce should accumulate values', () => { - const sum = avl.reduce((acc, [key]) => acc + key, 0); + const sum = avl.reduce((acc, value, key) => acc + key, 0); expect(sum).toBe(6); }); @@ -350,11 +350,11 @@ describe('AVLTree iterative methods test', () => { test('should keys', () => { const keys = avl.keys(); - expect(keys).toEqual([1, 2, 3]); + expect([...keys]).toEqual([1, 2, 3]); }); test('should values', () => { const values = avl.values(); - expect(values).toEqual(['a', 'b', 'c']); + expect([...values]).toEqual(['a', 'b', 'c']); }); }); diff --git a/test/unit/data-structures/binary-tree/binary-tree.test.ts b/test/unit/data-structures/binary-tree/binary-tree.test.ts index f2e520f..d43c1b8 100644 --- a/test/unit/data-structures/binary-tree/binary-tree.test.ts +++ b/test/unit/data-structures/binary-tree/binary-tree.test.ts @@ -580,30 +580,30 @@ describe('BinaryTree iterative methods test', () => { test('forEach should iterate over all elements', () => { const mockCallback = jest.fn(); - binaryTree.forEach((entry) => { - mockCallback(entry); + binaryTree.forEach((value, key) => { + mockCallback(value, key); }); expect(mockCallback.mock.calls.length).toBe(3); - expect(mockCallback.mock.calls[0][0]).toEqual([2, 'b']); - expect(mockCallback.mock.calls[1][0]).toEqual([1, 'a']); - expect(mockCallback.mock.calls[2][0]).toEqual([3, 'c']); + expect(mockCallback.mock.calls[0]).toEqual(['b', 2]); + expect(mockCallback.mock.calls[1]).toEqual(['a', 1]); + expect(mockCallback.mock.calls[2]).toEqual(['c', 3]); }); test('filter should return a new tree with filtered elements', () => { - const filteredTree = binaryTree.filter(([key]) => key > 1); + const filteredTree = binaryTree.filter((value, key) => key > 1); expect(filteredTree.size).toBe(2); expect([...filteredTree]).toEqual([[3, 'c'], [2, 'b']]); }); test('map should return a new tree with modified elements', () => { - const mappedTree = binaryTree.map(([key]) => (key * 2).toString()); + const mappedTree = binaryTree.map((value, key) => (key * 2).toString()); expect(mappedTree.size).toBe(3); expect([...mappedTree]).toEqual([[1, '2'], [2, '4'], [3, '6']]); }); test('reduce should accumulate values', () => { - const sum = binaryTree.reduce((acc, [key]) => acc + key, 0); + const sum = binaryTree.reduce((acc, currentValue, currentKey) => acc + currentKey, 0); expect(sum).toBe(6); }); @@ -625,11 +625,11 @@ describe('BinaryTree iterative methods test', () => { test('should keys', () => { const keys = binaryTree.keys(); - expect(keys).toEqual([2, 1, 3]); + expect([...keys]).toEqual([2, 1, 3]); }); test('should values', () => { const values = binaryTree.values(); - expect(values).toEqual(['b', 'a', 'c']); + expect([...values]).toEqual(['b', 'a', 'c']); }); }); \ No newline at end of file diff --git a/test/unit/data-structures/binary-tree/bst.test.ts b/test/unit/data-structures/binary-tree/bst.test.ts index e99fd2e..c22e7a8 100644 --- a/test/unit/data-structures/binary-tree/bst.test.ts +++ b/test/unit/data-structures/binary-tree/bst.test.ts @@ -864,30 +864,30 @@ describe('BST iterative methods test', () => { test('forEach should iterate over all elements', () => { const mockCallback = jest.fn(); - bst.forEach((entry) => { - mockCallback(entry); + bst.forEach((value, key) => { + mockCallback(value, key); }); expect(mockCallback.mock.calls.length).toBe(3); - expect(mockCallback.mock.calls[0][0]).toEqual([1, 'a']); - expect(mockCallback.mock.calls[1][0]).toEqual([2, 'b']); - expect(mockCallback.mock.calls[2][0]).toEqual([3, 'c']); + expect(mockCallback.mock.calls[0]).toEqual(['a', 1]); + expect(mockCallback.mock.calls[1]).toEqual(['b', 2]); + expect(mockCallback.mock.calls[2]).toEqual(['c', 3]); }); test('filter should return a new tree with filtered elements', () => { - const filteredTree = bst.filter(([key]) => key > 1); + const filteredTree = bst.filter((value, key) => key > 1); expect(filteredTree.size).toBe(2); expect([...filteredTree]).toEqual([[2, 'b'], [3, 'c']]); }); test('map should return a new tree with modified elements', () => { - const mappedTree = bst.map(([key]) => (key * 2).toString()); + const mappedTree = bst.map((value, key) => (key * 2).toString()); expect(mappedTree.size).toBe(3); expect([...mappedTree]).toEqual([[1, '2'], [2, '4'], [3, '6']]); }); test('reduce should accumulate values', () => { - const sum = bst.reduce((acc, [key]) => acc + key, 0); + const sum = bst.reduce((acc, value, key) => acc + key, 0); expect(sum).toBe(6); }); @@ -909,11 +909,11 @@ describe('BST iterative methods test', () => { test('should keys', () => { const keys = bst.keys(); - expect(keys).toEqual([1, 2, 3]); + expect([...keys]).toEqual([1, 2, 3]); }); test('should values', () => { const values = bst.values(); - expect(values).toEqual(['a', 'b', 'c']); + expect([...values]).toEqual(['a', 'b', 'c']); }); }); \ No newline at end of file diff --git a/test/unit/data-structures/binary-tree/rb-tree.test.ts b/test/unit/data-structures/binary-tree/rb-tree.test.ts index 3c675c7..68973cc 100644 --- a/test/unit/data-structures/binary-tree/rb-tree.test.ts +++ b/test/unit/data-structures/binary-tree/rb-tree.test.ts @@ -524,30 +524,30 @@ describe('RedBlackTree iterative methods test', () => { test('forEach should iterate over all elements', () => { const mockCallback = jest.fn(); - rbTree.forEach((entry) => { - mockCallback(entry); + rbTree.forEach((value, key) => { + mockCallback(value, key); }); expect(mockCallback.mock.calls.length).toBe(3); - expect(mockCallback.mock.calls[0][0]).toEqual([1, 'a']); - expect(mockCallback.mock.calls[1][0]).toEqual([2, 'b']); - expect(mockCallback.mock.calls[2][0]).toEqual([3, 'c']); + expect(mockCallback.mock.calls[0]).toEqual(['a', 1]); + expect(mockCallback.mock.calls[1]).toEqual(['b', 2]); + expect(mockCallback.mock.calls[2]).toEqual(['c', 3]); }); test('filter should return a new tree with filtered elements', () => { - const filteredTree = rbTree.filter(([key]) => key > 1); + const filteredTree = rbTree.filter((value, key) => key > 1); expect(filteredTree.size).toBe(2); expect([...filteredTree]).toEqual([[2, 'b'], [3, 'c']]); }); test('map should return a new tree with modified elements', () => { - const mappedTree = rbTree.map(([key]) => (key * 2).toString()); + const mappedTree = rbTree.map((value, key) => (key * 2).toString()); expect(mappedTree.size).toBe(3); expect([...mappedTree]).toEqual([[1, '2'], [2, '4'], [3, '6']]); }); test('reduce should accumulate values', () => { - const sum = rbTree.reduce((acc, [key]) => acc + key, 0); + const sum = rbTree.reduce((acc, value, key) => acc + key, 0); expect(sum).toBe(6); }); diff --git a/test/unit/data-structures/binary-tree/tree-multimap.test.ts b/test/unit/data-structures/binary-tree/tree-multimap.test.ts index 1eed205..4dd4015 100644 --- a/test/unit/data-structures/binary-tree/tree-multimap.test.ts +++ b/test/unit/data-structures/binary-tree/tree-multimap.test.ts @@ -619,30 +619,30 @@ describe('TreeMultimap iterative methods test', () => { test('forEach should iterate over all elements', () => { const mockCallback = jest.fn(); - treeMM.forEach((entry) => { - mockCallback(entry); + treeMM.forEach((value, key) => { + mockCallback(value, key); }); expect(mockCallback.mock.calls.length).toBe(3); - expect(mockCallback.mock.calls[0][0]).toEqual([1, 'a']); - expect(mockCallback.mock.calls[1][0]).toEqual([2, 'b']); - expect(mockCallback.mock.calls[2][0]).toEqual([3, 'c']); + expect(mockCallback.mock.calls[0]).toEqual(['a', 1]); + expect(mockCallback.mock.calls[1]).toEqual(['b', 2]); + expect(mockCallback.mock.calls[2]).toEqual(['c', 3]); }); test('filter should return a new tree with filtered elements', () => { - const filteredTree = treeMM.filter(([key]) => key > 1); + const filteredTree = treeMM.filter((value, key) => key > 1); expect(filteredTree.size).toBe(2); expect([...filteredTree]).toEqual([[2, 'b'], [3, 'c']]); }); test('map should return a new tree with modified elements', () => { - const mappedTree = treeMM.map(([key]) => (key * 2).toString()); + const mappedTree = treeMM.map((value, key) => (key * 2).toString()); expect(mappedTree.size).toBe(3); expect([...mappedTree]).toEqual([[1, '2'], [2, '4'], [3, '6']]); }); test('reduce should accumulate values', () => { - const sum = treeMM.reduce((acc, [key]) => acc + key, 0); + const sum = treeMM.reduce((acc, value, key) => acc + key, 0); expect(sum).toBe(6); }); @@ -665,11 +665,11 @@ describe('TreeMultimap iterative methods test', () => { test('should keys', () => { const keys = treeMM.keys(); - expect(keys).toEqual([1, 2, 3]); + expect([...keys]).toEqual([1, 2, 3]); }); test('should values', () => { const values = treeMM.values(); - expect(values).toEqual(['a', 'b', 'c']); + expect([...values]).toEqual(['a', 'b', 'c']); }); }); diff --git a/test/unit/data-structures/graph/directed-graph.test.ts b/test/unit/data-structures/graph/directed-graph.test.ts index f0b3281..384f5ca 100644 --- a/test/unit/data-structures/graph/directed-graph.test.ts +++ b/test/unit/data-structures/graph/directed-graph.test.ts @@ -616,22 +616,22 @@ describe('DirectedGraph iterative Methods', () => { test('forEach should apply a function to each vertex', () => { const result: VertexKey[] = []; - graph.forEach(vertex => result.push(vertex[0])); + graph.forEach((value, key) => key && result.push(key)); expect(result).toEqual(vertices); }); test('filter should return vertices that satisfy the condition', () => { - const filtered = graph.filter(vertex => vertex[0] === 'A' || vertex[0] === 'B'); + const filtered = graph.filter((value, vertex) => vertex === 'A' || vertex === 'B'); expect(filtered).toEqual([["A", undefined], ["B", undefined]]); }); test('map should apply a function to each vertex and return a new array', () => { - const mapped = graph.map(vertex => vertex[0] + '_mapped'); + const mapped = graph.map((value, vertex) => vertex + '_mapped'); expect(mapped).toEqual(vertices.map(v => v + '_mapped')); }); test('reduce should accumulate a value based on each vertex', () => { - const concatenated = graph.reduce((acc, vertex) => acc + vertex[0], ''); + const concatenated = graph.reduce((acc, value, key) => acc + key, ''); expect(concatenated).toBe(vertices.join('')); }); }); diff --git a/test/unit/data-structures/hash/hash-map.test.ts b/test/unit/data-structures/hash/hash-map.test.ts index f7135e6..41a17a6 100644 --- a/test/unit/data-structures/hash/hash-map.test.ts +++ b/test/unit/data-structures/hash/hash-map.test.ts @@ -548,10 +548,10 @@ describe('LinkedHashMap setMany, keys, values', () => { }) test('keys', () => { - expect(hm.keys()).toEqual([2, 3, 4, 5, 6]) + expect([...hm.keys()]).toEqual([2, 3, 4, 5, 6]) }); test('values', () => { - expect(hm.values()).toEqual([2, 3, 4, 5, 6]) + expect([...hm.values()]).toEqual([2, 3, 4, 5, 6]) }); }); \ No newline at end of file diff --git a/test/unit/data-structures/linked-list/doubly-linked-list.test.ts b/test/unit/data-structures/linked-list/doubly-linked-list.test.ts index 89405f2..429481e 100644 --- a/test/unit/data-structures/linked-list/doubly-linked-list.test.ts +++ b/test/unit/data-structures/linked-list/doubly-linked-list.test.ts @@ -397,3 +397,31 @@ describe('DoublyLinkedList Operation Test', () => { expect(shiftedObj).toBe(obj1); }); }); + + +describe('iterable methods', () => { + it('should forEach, some, every, filter, map, reduce of the deque', () => { + const dl = new DoublyLinkedList() + dl.push(1); + dl.push(2); + dl.push(3); + + const mockCallback = jest.fn(); + dl.forEach((element) => { + mockCallback(element); + }); + + expect(mockCallback.mock.calls.length).toBe(3); + expect(mockCallback.mock.calls[0]).toEqual([1]); + expect(mockCallback.mock.calls[1]).toEqual([2]); + expect(mockCallback.mock.calls[2]).toEqual([3]); + + expect(dl.every(element => element > 0)).toBe(true); + expect(dl.every(element => element > 1)).toBe(false); + expect(dl.some(element => element > 2)).toBe(true); + + expect([...dl.filter(element => element > 2)]).toEqual([3]); + expect([...dl.map(element => element * 2)]).toEqual([2, 4, 6]); + expect(dl.reduce((accumulator, element) => accumulator + element, 0)).toEqual(6); + }); +}); \ No newline at end of file diff --git a/test/unit/data-structures/linked-list/singly-linked-list.test.ts b/test/unit/data-structures/linked-list/singly-linked-list.test.ts index 66c531e..8bff7f6 100644 --- a/test/unit/data-structures/linked-list/singly-linked-list.test.ts +++ b/test/unit/data-structures/linked-list/singly-linked-list.test.ts @@ -471,3 +471,28 @@ describe('SinglyLinkedList', () => { expect(list1.reduce((acc, value) => acc + value, 0)).toEqual(6); }); }); + + +describe('iterable methods', () => { + it('should forEach, some, every, filter, map, reduce of the deque', () => { + const sl = new SinglyLinkedList([1, 2, 3]) + + const mockCallback = jest.fn(); + sl.forEach((element) => { + mockCallback(element); + }); + + expect(mockCallback.mock.calls.length).toBe(3); + expect(mockCallback.mock.calls[0]).toEqual([1]); + expect(mockCallback.mock.calls[1]).toEqual([2]); + expect(mockCallback.mock.calls[2]).toEqual([3]); + + expect(sl.every(element => element > 0)).toBe(true); + expect(sl.every(element => element > 1)).toBe(false); + expect(sl.some(element => element > 2)).toBe(true); + + expect([...sl.filter(element => element > 2)]).toEqual([3]); + expect([...sl.map(element => element * 2)]).toEqual([2, 4, 6]); + expect(sl.reduce((accumulator, element) => accumulator + element, 0)).toEqual(6); + }); +}); \ No newline at end of file diff --git a/test/unit/data-structures/queue/deque.test.ts b/test/unit/data-structures/queue/deque.test.ts index 12ffd8e..808f5f1 100644 --- a/test/unit/data-structures/queue/deque.test.ts +++ b/test/unit/data-structures/queue/deque.test.ts @@ -447,4 +447,29 @@ describe('Deque', () => { }); }); + describe('iterable methods', () => { + it('should forEach, some, every, filter, map, reduce of the deque', () => { + deque.push(1); + deque.push(2); + deque.push(3); + + const mockCallback = jest.fn(); + deque.forEach((element) => { + mockCallback(element); + }); + + expect(mockCallback.mock.calls.length).toBe(3); + expect(mockCallback.mock.calls[0]).toEqual([1]); + expect(mockCallback.mock.calls[1]).toEqual([2]); + expect(mockCallback.mock.calls[2]).toEqual([3]); + + expect(deque.every(element => element > 0)).toBe(true); + expect(deque.every(element => element > 1)).toBe(false); + expect(deque.some(element => element > 2)).toBe(true); + + expect([...deque.filter(element => element > 2)]).toEqual([3]); + expect([...deque.map(element => element * 2)]).toEqual([2, 4, 6]); + expect(deque.reduce((accumulator, element) => accumulator + element, 0)).toEqual(6); + }); + }); });