mirror of
https://github.com/zrwusa/data-structure-typed.git
synced 2025-01-18 11:14:05 +00:00
perf: Optimize the performance of indexOf, lastIndexOf, slice, and splice methods in Queue, Deque, SinglyLinkedList, and DoublyLinkedList. The performance is now satisfactory with a dataset of 1 million elements.
This commit is contained in:
parent
4a522db1e3
commit
9224252367
48
package-lock.json
generated
48
package-lock.json
generated
|
@ -19,11 +19,11 @@
|
|||
"@typescript-eslint/eslint-plugin": "^8.12.1",
|
||||
"@typescript-eslint/parser": "^8.12.1",
|
||||
"auto-changelog": "^2.5.0",
|
||||
"avl-tree-typed": "^1.54.2",
|
||||
"avl-tree-typed": "^1.54.3",
|
||||
"benchmark": "^2.1.4",
|
||||
"binary-tree-typed": "^1.54.2",
|
||||
"bst-typed": "^1.54.2",
|
||||
"data-structure-typed": "^1.54.2",
|
||||
"binary-tree-typed": "^1.54.3",
|
||||
"bst-typed": "^1.54.3",
|
||||
"data-structure-typed": "^1.54.3",
|
||||
"dependency-cruiser": "^16.5.0",
|
||||
"doctoc": "^2.2.1",
|
||||
"eslint": "^9.13.0",
|
||||
|
@ -32,7 +32,7 @@
|
|||
"eslint-import-resolver-typescript": "^3.6.3",
|
||||
"eslint-plugin-import": "^2.31.0",
|
||||
"fast-glob": "^3.3.2",
|
||||
"heap-typed": "^1.54.2",
|
||||
"heap-typed": "^1.54.3",
|
||||
"istanbul-badges-readme": "^1.9.0",
|
||||
"jest": "^29.7.0",
|
||||
"js-sdsl": "^4.4.2",
|
||||
|
@ -3450,13 +3450,13 @@
|
|||
}
|
||||
},
|
||||
"node_modules/avl-tree-typed": {
|
||||
"version": "1.54.2",
|
||||
"resolved": "https://registry.npmjs.org/avl-tree-typed/-/avl-tree-typed-1.54.2.tgz",
|
||||
"integrity": "sha512-bp+MMq2CpASeAB7up5kggHZhcqxx3sKdd5AJ8aWMqabZin13BxRnzwRY/II9gp4hb4Tvtitw6AYfJliGcdWYyQ==",
|
||||
"version": "1.54.3",
|
||||
"resolved": "https://registry.npmjs.org/avl-tree-typed/-/avl-tree-typed-1.54.3.tgz",
|
||||
"integrity": "sha512-lJYYqt0rdS6iPDhh7hbaMGkxur9/GJLljas1LqojMZbLmUabVx4T4MszdsCghJy94wThlXN+jjMeCxVB4k4XRw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"data-structure-typed": "^1.54.2"
|
||||
"data-structure-typed": "^1.54.3"
|
||||
}
|
||||
},
|
||||
"node_modules/babel-jest": {
|
||||
|
@ -3615,13 +3615,13 @@
|
|||
}
|
||||
},
|
||||
"node_modules/binary-tree-typed": {
|
||||
"version": "1.54.2",
|
||||
"resolved": "https://registry.npmjs.org/binary-tree-typed/-/binary-tree-typed-1.54.2.tgz",
|
||||
"integrity": "sha512-U6qW0bIh6o2qGz8OzWzUHnNwXa6HjYp0I5Ds4VURqtl4OEOeJh57cNOlKgbiEHZ5viai7S5aRqXIZHW6vStezA==",
|
||||
"version": "1.54.3",
|
||||
"resolved": "https://registry.npmjs.org/binary-tree-typed/-/binary-tree-typed-1.54.3.tgz",
|
||||
"integrity": "sha512-azYUQ8qCx5JHKEqZvaxKpSWN7BrYccW729CIc61G7/0ECCzw8AJxYL1jHIzfYTUJgRxGxbzsCkRMjaKaVtuD5w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"data-structure-typed": "^1.54.2"
|
||||
"data-structure-typed": "^1.54.3"
|
||||
}
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
|
@ -3705,13 +3705,13 @@
|
|||
}
|
||||
},
|
||||
"node_modules/bst-typed": {
|
||||
"version": "1.54.2",
|
||||
"resolved": "https://registry.npmjs.org/bst-typed/-/bst-typed-1.54.2.tgz",
|
||||
"integrity": "sha512-hl49IvHT/uJMphP1AL6sifiCKVc0pCdsGG8PW5ealU2K0LO9kO3nMBU+FcfwsmMcvhRrhikW/1fC63c9X7Lq+Q==",
|
||||
"version": "1.54.3",
|
||||
"resolved": "https://registry.npmjs.org/bst-typed/-/bst-typed-1.54.3.tgz",
|
||||
"integrity": "sha512-4E4HWana/ON/KZSrOPghMhF81H9G7U9nt9nXvJx2CKCc30qAH8rC/RC9gtxyLoB1QziUFHL73ZNyWORP1wR3cQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"data-structure-typed": "^1.54.2"
|
||||
"data-structure-typed": "^1.54.3"
|
||||
}
|
||||
},
|
||||
"node_modules/buffer-from": {
|
||||
|
@ -4083,9 +4083,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/data-structure-typed": {
|
||||
"version": "1.54.2",
|
||||
"resolved": "https://registry.npmjs.org/data-structure-typed/-/data-structure-typed-1.54.2.tgz",
|
||||
"integrity": "sha512-H4Ct6XqKsGk7O6/6mb9MHsZdrp5fAYHTgv2Tb+LrbTHnbenKu2ZfM0wP7fbrkhrRPzCFFvZKKDTfSatgykolmw==",
|
||||
"version": "1.54.3",
|
||||
"resolved": "https://registry.npmjs.org/data-structure-typed/-/data-structure-typed-1.54.3.tgz",
|
||||
"integrity": "sha512-hAJ+2cbsfAWMHNP3lupdM9Fji3nqu2jQECWlJ9Hcy7g/Iwu90Xj0YwN3qfTlae86fnMZniv3nVWoeuEhTpMWng==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
|
@ -5904,13 +5904,13 @@
|
|||
}
|
||||
},
|
||||
"node_modules/heap-typed": {
|
||||
"version": "1.54.2",
|
||||
"resolved": "https://registry.npmjs.org/heap-typed/-/heap-typed-1.54.2.tgz",
|
||||
"integrity": "sha512-BEgfIZndyoxN4XRY9kWSS8217u6nt4GuWrBWBnNxZwp8EVyhExS/CA3NsOH8xzj3mgsxqU88LdWwGFpQqBxMbA==",
|
||||
"version": "1.54.3",
|
||||
"resolved": "https://registry.npmjs.org/heap-typed/-/heap-typed-1.54.3.tgz",
|
||||
"integrity": "sha512-XdfkyJuiKnXrfV8vSGNonu+7nON35BuP7/EBt7m8hIipsgxQT4d21pgCwNhsU1TH5xawV9sr6P1GSZkqrQOMZA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"data-structure-typed": "^1.54.2"
|
||||
"data-structure-typed": "^1.54.3"
|
||||
}
|
||||
},
|
||||
"node_modules/html-escaper": {
|
||||
|
|
10
package.json
10
package.json
|
@ -65,11 +65,11 @@
|
|||
"@typescript-eslint/eslint-plugin": "^8.12.1",
|
||||
"@typescript-eslint/parser": "^8.12.1",
|
||||
"auto-changelog": "^2.5.0",
|
||||
"avl-tree-typed": "^1.54.2",
|
||||
"avl-tree-typed": "^1.54.3",
|
||||
"benchmark": "^2.1.4",
|
||||
"binary-tree-typed": "^1.54.2",
|
||||
"bst-typed": "^1.54.2",
|
||||
"data-structure-typed": "^1.54.2",
|
||||
"binary-tree-typed": "^1.54.3",
|
||||
"bst-typed": "^1.54.3",
|
||||
"data-structure-typed": "^1.54.3",
|
||||
"dependency-cruiser": "^16.5.0",
|
||||
"doctoc": "^2.2.1",
|
||||
"eslint": "^9.13.0",
|
||||
|
@ -78,7 +78,7 @@
|
|||
"eslint-import-resolver-typescript": "^3.6.3",
|
||||
"eslint-plugin-import": "^2.31.0",
|
||||
"fast-glob": "^3.3.2",
|
||||
"heap-typed": "^1.54.2",
|
||||
"heap-typed": "^1.54.3",
|
||||
"istanbul-badges-readme": "^1.9.0",
|
||||
"jest": "^29.7.0",
|
||||
"js-sdsl": "^4.4.2",
|
||||
|
|
|
@ -1,7 +1,45 @@
|
|||
import { ElementCallback, LinearBaseOptions, ReduceLinearCallback } from '../../types';
|
||||
import { IterableElementBase } from './iterable-element-base';
|
||||
|
||||
export abstract class LinearBase<E, R = any, NODE = any> extends IterableElementBase<E, R> {
|
||||
export class LinkedListNode<E = any> {
|
||||
constructor(value: E) {
|
||||
this._value = value;
|
||||
this._next = undefined;
|
||||
}
|
||||
|
||||
protected _value: E;
|
||||
|
||||
get value(): E {
|
||||
return this._value;
|
||||
}
|
||||
|
||||
set value(value: E) {
|
||||
this._value = value;
|
||||
}
|
||||
|
||||
protected _next: LinkedListNode<E> | undefined;
|
||||
|
||||
get next(): LinkedListNode<E> | undefined {
|
||||
return this._next;
|
||||
}
|
||||
|
||||
set next(value: LinkedListNode<E> | undefined) {
|
||||
this._next = value;
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class LinearBase<
|
||||
E,
|
||||
R = any,
|
||||
NODE extends LinkedListNode<E> = LinkedListNode<E>
|
||||
> extends IterableElementBase<E, R> {
|
||||
/**
|
||||
* The constructor initializes the LinearBase class with optional options, setting the maximum length
|
||||
* if provided.
|
||||
* @param [options] - The `options` parameter is an optional object that can be passed to the
|
||||
* constructor. It is of type `LinearBaseOptions<E, R>`. The constructor checks if the `options`
|
||||
* object is provided and then extracts the `maxLen` property from it. If `maxLen` is a
|
||||
*/
|
||||
protected constructor(options?: LinearBaseOptions<E, R>) {
|
||||
super(options);
|
||||
if (options) {
|
||||
|
@ -103,7 +141,7 @@ export abstract class LinearBase<E, R = any, NODE = any> extends IterableElement
|
|||
return -1;
|
||||
}
|
||||
|
||||
concat(...items: LinearBase<E, R>[]): this;
|
||||
concat(...items: this[]): this;
|
||||
|
||||
/**
|
||||
* Time Complexity: O(n + m)
|
||||
|
@ -111,12 +149,12 @@ export abstract class LinearBase<E, R = any, NODE = any> extends IterableElement
|
|||
*
|
||||
* The `concat` function in TypeScript concatenates multiple items into a new list, handling both
|
||||
* individual elements and instances of `LinearBase`.
|
||||
* @param {(E | LinearBase<E, R>)[]} items - The `concat` method takes in an array of items, where
|
||||
* @param {(E | this)[]} items - The `concat` method takes in an array of items, where
|
||||
* each item can be either of type `E` or an instance of `LinearBase<E, R>`.
|
||||
* @returns The `concat` method is returning a new instance of the class that it belongs to, with the
|
||||
* items passed as arguments concatenated to it.
|
||||
*/
|
||||
concat(...items: (E | LinearBase<E, R>)[]): this {
|
||||
concat(...items: (E | this)[]): this {
|
||||
const newList = this.clone();
|
||||
|
||||
for (const item of items) {
|
||||
|
@ -171,30 +209,27 @@ export abstract class LinearBase<E, R = any, NODE = any> extends IterableElement
|
|||
* during the operation.
|
||||
*/
|
||||
splice(start: number, deleteCount: number = 0, ...items: E[]): this {
|
||||
const removedList = this._createInstance();
|
||||
|
||||
// Handling negative indexes and bounds
|
||||
start = start < 0 ? this.length + start : start;
|
||||
if (start < 0) start = 0;
|
||||
if (start > this.length) start = this.length;
|
||||
|
||||
const removedList = this._createInstance({ toElementFn: this._toElementFn });
|
||||
|
||||
// Move to starting position
|
||||
let currentNode = this.at(start);
|
||||
let currentIndex = start;
|
||||
start = Math.max(0, Math.min(start, this.length));
|
||||
deleteCount = Math.max(0, Math.min(deleteCount, this.length - start));
|
||||
|
||||
// Delete elements
|
||||
for (let i = 0; i < deleteCount && currentNode !== undefined; i++) {
|
||||
removedList.push(currentNode);
|
||||
this.delete(currentNode);
|
||||
currentNode = this.at(currentIndex); // Update node reference
|
||||
for (let i = 0; i < deleteCount; i++) {
|
||||
const removed = this.deleteAt(start); // Always delete the start position
|
||||
if (removed !== undefined) {
|
||||
removedList.push(removed); // Add removed elements to the returned list
|
||||
}
|
||||
}
|
||||
|
||||
// Insert new element
|
||||
for (const item of items) {
|
||||
this.addAt(currentIndex, item);
|
||||
currentIndex++;
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
this.addAt(start + i, items[i]); // Insert new elements one by one at the current position
|
||||
}
|
||||
|
||||
return removedList;
|
||||
return removedList; // Returns a list of removed elements
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -337,7 +372,7 @@ export abstract class LinearBase<E, R = any, NODE = any> extends IterableElement
|
|||
|
||||
abstract at(index: number): E | undefined;
|
||||
|
||||
abstract deleteAt(pos: number): boolean;
|
||||
abstract deleteAt(pos: number): E | undefined;
|
||||
|
||||
abstract addAt(index: number, newElementOrNode: E | NODE): boolean;
|
||||
|
||||
|
@ -346,7 +381,18 @@ export abstract class LinearBase<E, R = any, NODE = any> extends IterableElement
|
|||
protected abstract _getReverseIterator(...args: any[]): IterableIterator<E>;
|
||||
}
|
||||
|
||||
export abstract class LinearLinkedBase<E, R = any, NODE = any> extends LinearBase<E, R, NODE> {
|
||||
export abstract class LinearLinkedBase<
|
||||
E,
|
||||
R = any,
|
||||
NODE extends LinkedListNode<E> = LinkedListNode<E>
|
||||
> extends LinearBase<E, R, NODE> {
|
||||
/**
|
||||
* The constructor initializes the LinearBase class with optional options, setting the maximum length
|
||||
* if provided and valid.
|
||||
* @param [options] - The `options` parameter is an optional object that can be passed to the
|
||||
* constructor. It is of type `LinearBaseOptions<E, R>`. This object may contain properties such as
|
||||
* `maxLen`, which is a number representing the maximum length. If `maxLen` is a positive integer,
|
||||
*/
|
||||
protected constructor(options?: LinearBaseOptions<E, R>) {
|
||||
super(options);
|
||||
if (options) {
|
||||
|
@ -458,6 +504,41 @@ export abstract class LinearLinkedBase<E, R = any, NODE = any> extends LinearBas
|
|||
return newList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Time Complexity: O(m)
|
||||
* Space Complexity: O(m)
|
||||
*
|
||||
* The `slice` method is overridden to improve performance by creating a new instance and iterating
|
||||
* through the array to extract a subset based on the specified start and end indices.
|
||||
* @param {number} [start=0] - The `start` parameter in the `slice` method specifies the index at
|
||||
* which to begin extracting elements from the array. If no `start` parameter is provided, the
|
||||
* default value is 0, indicating that extraction should start from the beginning of the array.
|
||||
* @param {number} end - The `end` parameter in the `slice` method represents the index at which to
|
||||
* end the slicing of the array. If not provided, it defaults to the length of the array.
|
||||
* @returns The `slice` method is returning a new instance of the array implementation with elements
|
||||
* sliced from the original array based on the `start` and `end` parameters.
|
||||
*/
|
||||
override slice(start: number = 0, end: number = this.length): this {
|
||||
// In order to improve performance, it is best to override this method in the subclass of the array implementation
|
||||
start = start < 0 ? this.length + start : start;
|
||||
end = end < 0 ? this.length + end : end;
|
||||
|
||||
const newList = this._createInstance();
|
||||
const iterator = this._getIterator();
|
||||
let current = iterator.next();
|
||||
let c = 0;
|
||||
while (c < start) {
|
||||
current = iterator.next();
|
||||
c++;
|
||||
}
|
||||
for (let i = start; i < end; i++) {
|
||||
newList.push(current.value);
|
||||
current = iterator.next();
|
||||
}
|
||||
|
||||
return newList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Time Complexity: O(n + m)
|
||||
* Space Complexity: O(m)
|
||||
|
@ -478,27 +559,45 @@ export abstract class LinearLinkedBase<E, R = any, NODE = any> extends LinearBas
|
|||
* elements provided in the `items` array.
|
||||
*/
|
||||
override splice(start: number, deleteCount: number = 0, ...items: E[]): this {
|
||||
const removedList = this._createInstance(); // Used to store deleted elements
|
||||
|
||||
// Handling negative indexes
|
||||
start = start < 0 ? this.length + start : start;
|
||||
if (start < 0) start = 0;
|
||||
if (start > this.length) start = this.length;
|
||||
start = Math.max(0, Math.min(start, this.length)); // Correct start range
|
||||
deleteCount = Math.max(0, deleteCount); // Make sure deleteCount is non-negative
|
||||
|
||||
const removedList = this._createInstance({ toElementFn: this._toElementFn });
|
||||
let currentIndex = 0;
|
||||
let currentNode: NODE | undefined = undefined;
|
||||
let previousNode: NODE | undefined = undefined;
|
||||
|
||||
// Move to starting position
|
||||
let currentNode = this.at(start);
|
||||
let currentIndex = start;
|
||||
|
||||
// Delete elements
|
||||
for (let i = 0; i < deleteCount && currentNode !== undefined; i++) {
|
||||
removedList.push(currentNode);
|
||||
this.delete(currentNode);
|
||||
currentNode = this.at(currentIndex); // Update node reference
|
||||
// Find the starting point using an iterator
|
||||
const iterator = this._getNodeIterator();
|
||||
for (const node of iterator) {
|
||||
if (currentIndex === start) {
|
||||
currentNode = node; // Find the starting node
|
||||
break;
|
||||
}
|
||||
previousNode = node; // Update the previous node
|
||||
currentIndex++;
|
||||
}
|
||||
|
||||
// Insert new element
|
||||
for (const item of items) {
|
||||
this.addAt(currentIndex, item);
|
||||
currentIndex++;
|
||||
// Delete nodes
|
||||
for (let i = 0; i < deleteCount && currentNode; i++) {
|
||||
removedList.push(currentNode.value); // Store the deleted value in removedList
|
||||
const nextNode = currentNode.next; // Save next node
|
||||
this.delete(currentNode); // Delete current node
|
||||
currentNode = nextNode as NODE;
|
||||
}
|
||||
|
||||
// Insert new value
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
if (previousNode) {
|
||||
this.addAfter(previousNode, items[i]); // Insert after previousNode
|
||||
previousNode = previousNode.next as NODE; // Move to newly inserted node
|
||||
} else {
|
||||
this.addAt(0, items[i]); // Insert at the head of the linked list
|
||||
previousNode = this._getNodeIterator().next().value; // Update the head node to be the first inserted node
|
||||
}
|
||||
}
|
||||
|
||||
return removedList;
|
||||
|
@ -534,38 +633,17 @@ export abstract class LinearLinkedBase<E, R = any, NODE = any> extends LinearBas
|
|||
return accumulator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Time Complexity: O(m)
|
||||
* Space Complexity: O(m)
|
||||
*
|
||||
* The `slice` method is overridden to improve performance by creating a new instance and iterating
|
||||
* through the array to extract a subset based on the specified start and end indices.
|
||||
* @param {number} [start=0] - The `start` parameter in the `slice` method specifies the index at
|
||||
* which to begin extracting elements from the array. If no `start` parameter is provided, the
|
||||
* default value is 0, indicating that extraction should start from the beginning of the array.
|
||||
* @param {number} end - The `end` parameter in the `slice` method represents the index at which to
|
||||
* end the slicing of the array. If not provided, it defaults to the length of the array.
|
||||
* @returns The `slice` method is returning a new instance of the array implementation with elements
|
||||
* sliced from the original array based on the `start` and `end` parameters.
|
||||
*/
|
||||
override slice(start: number = 0, end: number = this.length): this {
|
||||
// In order to improve performance, it is best to override this method in the subclass of the array implementation
|
||||
start = start < 0 ? this.length + start : start;
|
||||
end = end < 0 ? this.length + end : end;
|
||||
abstract override delete(elementOrNode: E | NODE | undefined): boolean;
|
||||
|
||||
const newList = this._createInstance();
|
||||
const iterator = this._getIterator();
|
||||
let current = iterator.next();
|
||||
let c = 0;
|
||||
while (c < start) {
|
||||
current = iterator.next();
|
||||
c++;
|
||||
}
|
||||
for (let i = start; i < end; i++) {
|
||||
newList.push(current.value);
|
||||
current = iterator.next();
|
||||
}
|
||||
abstract addBefore(existingElementOrNode: E | NODE, newElementOrNode: E | NODE): boolean;
|
||||
|
||||
return newList;
|
||||
}
|
||||
abstract addAfter(existingElementOrNode: E | NODE, newElementOrNode: E | NODE): boolean;
|
||||
|
||||
abstract getNodeAt(index: number): NODE | undefined;
|
||||
|
||||
protected abstract _getNodeIterator(...args: any[]): IterableIterator<NODE>;
|
||||
|
||||
// protected abstract _getReverseNodeIterator(...args: any[]): IterableIterator<NODE>;
|
||||
|
||||
protected abstract _getPrevNode(node: NODE): NODE | undefined;
|
||||
}
|
||||
|
|
|
@ -6,37 +6,28 @@
|
|||
* @license MIT License
|
||||
*/
|
||||
import type { DoublyLinkedListOptions, ElementCallback } from '../../types';
|
||||
import { LinearLinkedBase } from '../base/linear-base';
|
||||
import { LinearLinkedBase, LinkedListNode } from '../base/linear-base';
|
||||
|
||||
export class DoublyLinkedListNode<E = any> {
|
||||
export class DoublyLinkedListNode<E = any> extends LinkedListNode<E> {
|
||||
/**
|
||||
* The constructor function initializes the value, next, and previous properties of an object.
|
||||
* @param {E} value - The "value" parameter is the value that will be stored in the node. It can be of any data type, as it
|
||||
* is defined as a generic type "E".
|
||||
*/
|
||||
constructor(value: E) {
|
||||
super(value);
|
||||
this._value = value;
|
||||
this._next = undefined;
|
||||
this._prev = undefined;
|
||||
}
|
||||
|
||||
protected _value: E;
|
||||
protected override _next: DoublyLinkedListNode<E> | undefined;
|
||||
|
||||
get value(): E {
|
||||
return this._value;
|
||||
}
|
||||
|
||||
set value(value: E) {
|
||||
this._value = value;
|
||||
}
|
||||
|
||||
protected _next: DoublyLinkedListNode<E> | undefined;
|
||||
|
||||
get next(): DoublyLinkedListNode<E> | undefined {
|
||||
override get next(): DoublyLinkedListNode<E> | undefined {
|
||||
return this._next;
|
||||
}
|
||||
|
||||
set next(value: DoublyLinkedListNode<E> | undefined) {
|
||||
override set next(value: DoublyLinkedListNode<E> | undefined) {
|
||||
this._next = value;
|
||||
}
|
||||
|
||||
|
@ -52,10 +43,11 @@ export class DoublyLinkedListNode<E = any> {
|
|||
}
|
||||
|
||||
/**
|
||||
*1. Node Structure: Each node contains three parts: a data field, a pointer (or reference) to the previous node, and a pointer to the next node. This structure allows traversal of the linked list in both directions.
|
||||
* 1. Node Structure: Each node contains three parts: a data field, a pointer (or reference) to the previous node, and a pointer to the next node. This structure allows traversal of the linked list in both directions.
|
||||
* 2. Bidirectional Traversal: Unlike singly linked lists, doubly linked lists can be easily traversed forwards or backwards. This makes insertions and deletions in the list more flexible and efficient.
|
||||
* 3. No Centralized Index: Unlike arrays, elements in a linked list are not stored contiguously, so there is no centralized index. Accessing elements in a linked list typically requires traversing from the head or tail node.
|
||||
* 4. High Efficiency in Insertion and Deletion: Adding or removing elements in a linked list does not require moving other elements, making these operations more efficient than in arrays.
|
||||
* Caution: Although our linked list classes provide methods such as at, setAt, addAt, and indexOf that are based on array indices, their time complexity, like that of the native Array.lastIndexOf, is 𝑂(𝑛). If you need to use these methods frequently, you might want to consider other data structures, such as Deque or Queue (designed for random access). Similarly, since the native Array.shift method has a time complexity of 𝑂(𝑛), using an array to simulate a queue can be inefficient. In such cases, you should use Queue or Deque, as these data structures leverage deferred array rearrangement, effectively reducing the average time complexity to 𝑂(1).
|
||||
* @example
|
||||
* // text editor operation history
|
||||
* const actions = [
|
||||
|
@ -787,6 +779,7 @@ export class DoublyLinkedList<E = any, R = any> extends LinearLinkedBase<E, R, D
|
|||
elementNodeOrPredicate: E | DoublyLinkedListNode<E> | ((node: DoublyLinkedListNode<E>) => boolean) | undefined
|
||||
): DoublyLinkedListNode<E> | undefined {
|
||||
if (elementNodeOrPredicate === undefined) return;
|
||||
if (this.isNode(elementNodeOrPredicate)) return elementNodeOrPredicate;
|
||||
const predicate = this._ensurePredicate(elementNodeOrPredicate);
|
||||
let current = this.head;
|
||||
|
||||
|
@ -948,15 +941,18 @@ export class DoublyLinkedList<E = any, R = any> extends LinearLinkedBase<E, R, D
|
|||
* @returns The method `deleteAt` returns the value of the node that was deleted, or `undefined` if the index is out of
|
||||
* bounds.
|
||||
*/
|
||||
deleteAt(index: number): boolean {
|
||||
if (index < 0 || index >= this._length) return false;
|
||||
deleteAt(index: number): E | undefined {
|
||||
if (index < 0 || index >= this._length) return;
|
||||
let deleted: E | undefined;
|
||||
if (index === 0) {
|
||||
deleted = this.first;
|
||||
this.shift();
|
||||
return true;
|
||||
return deleted;
|
||||
}
|
||||
if (index === this._length - 1) {
|
||||
deleted = this.last;
|
||||
this.pop();
|
||||
return true;
|
||||
return deleted;
|
||||
}
|
||||
|
||||
const removedNode = this.getNodeAt(index);
|
||||
|
@ -965,7 +961,7 @@ export class DoublyLinkedList<E = any, R = any> extends LinearLinkedBase<E, R, D
|
|||
prevNode!.next = nextNode;
|
||||
nextNode!.prev = prevNode;
|
||||
this._length--;
|
||||
return true;
|
||||
return removedNode?.value;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1121,7 +1117,7 @@ export class DoublyLinkedList<E = any, R = any> extends LinearLinkedBase<E, R, D
|
|||
* elements that pass the filter condition specified by the `callback` function.
|
||||
*/
|
||||
filter(callback: ElementCallback<E, R, boolean>, thisArg?: any): DoublyLinkedList<E, R> {
|
||||
const filteredList = new DoublyLinkedList<E, R>([], { toElementFn: this.toElementFn });
|
||||
const filteredList = this._createInstance({ toElementFn: this.toElementFn, maxLen: this._maxLen });
|
||||
let index = 0;
|
||||
for (const current of this) {
|
||||
if (callback.call(thisArg, current, index, this)) {
|
||||
|
@ -1157,7 +1153,7 @@ export class DoublyLinkedList<E = any, R = any> extends LinearLinkedBase<E, R, D
|
|||
toElementFn?: (rawElement: RM) => EM,
|
||||
thisArg?: any
|
||||
): DoublyLinkedList<EM, RM> {
|
||||
const mappedList = new DoublyLinkedList<EM, RM>([], { toElementFn });
|
||||
const mappedList = new DoublyLinkedList<EM, RM>([], { toElementFn, maxLen: this._maxLen });
|
||||
let index = 0;
|
||||
for (const current of this) {
|
||||
mappedList.push(callback.call(thisArg, current, index, this));
|
||||
|
@ -1205,6 +1201,40 @@ export class DoublyLinkedList<E = any, R = any> extends LinearLinkedBase<E, R, D
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The function returns an iterator that iterates over the elements of a data structure in reverse
|
||||
* order.
|
||||
*/
|
||||
protected *_getReverseIterator(): IterableIterator<E> {
|
||||
let current = this.tail;
|
||||
|
||||
while (current) {
|
||||
yield current.value;
|
||||
current = current.prev;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The function returns an iterator that iterates over the nodes of a doubly linked list starting
|
||||
* from the head.
|
||||
*/
|
||||
protected *_getNodeIterator(): IterableIterator<DoublyLinkedListNode<E>> {
|
||||
let current = this.head;
|
||||
|
||||
while (current) {
|
||||
yield current;
|
||||
current = current.next;
|
||||
}
|
||||
}
|
||||
|
||||
// protected *_getReverseNodeIterator(): IterableIterator<DoublyLinkedListNode<E>> {
|
||||
// const reversedArr = [...this._getNodeIterator()].reverse();
|
||||
//
|
||||
// for (const item of reversedArr) {
|
||||
// yield item;
|
||||
// }
|
||||
// }
|
||||
|
||||
/**
|
||||
* The function `_isPredicate` checks if the input is a function that takes a `DoublyLinkedListNode`
|
||||
* as an argument and returns a boolean.
|
||||
|
@ -1268,15 +1298,15 @@ export class DoublyLinkedList<E = any, R = any> extends LinearLinkedBase<E, R, D
|
|||
}
|
||||
|
||||
/**
|
||||
* The function returns an iterator that iterates over the elements of a data structure in reverse
|
||||
* order.
|
||||
* The function `_getPrevNode` returns the previous node of a given node in a doubly linked list.
|
||||
* @param node - The parameter `node` in the `_getPrevNode` method is of type
|
||||
* `DoublyLinkedListNode<E>`, which represents a node in a doubly linked list containing an element
|
||||
* of type `E`.
|
||||
* @returns The `_getPrevNode` method is returning the previous node of the input `node` in a doubly
|
||||
* linked list. If the input node has a previous node, it will return that node. Otherwise, it will
|
||||
* return `undefined`.
|
||||
*/
|
||||
protected *_getReverseIterator(): IterableIterator<E> {
|
||||
let current = this.tail;
|
||||
|
||||
while (current) {
|
||||
yield current.value;
|
||||
current = current.prev;
|
||||
}
|
||||
protected _getPrevNode(node: DoublyLinkedListNode<E>): DoublyLinkedListNode<E> | undefined {
|
||||
return node.prev;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,60 +6,37 @@
|
|||
* @license MIT License
|
||||
*/
|
||||
import type { ElementCallback, SinglyLinkedListOptions } from '../../types';
|
||||
import { LinearLinkedBase } from '../base/linear-base';
|
||||
import { LinearLinkedBase, LinkedListNode } from '../base/linear-base';
|
||||
|
||||
export class SinglyLinkedListNode<E = any> {
|
||||
export class SinglyLinkedListNode<E = any> extends LinkedListNode<E> {
|
||||
/**
|
||||
* The constructor function initializes an instance of a class with a given value and sets the next property to undefined.
|
||||
* @param {E} value - The "value" parameter is of type E, which means it can be any data type. It represents the value that
|
||||
* will be stored in the node of a linked list.
|
||||
*/
|
||||
constructor(value: E) {
|
||||
super(value);
|
||||
this._value = value;
|
||||
this._next = undefined;
|
||||
}
|
||||
|
||||
protected _value: E;
|
||||
protected override _next: SinglyLinkedListNode<E> | undefined;
|
||||
|
||||
/**
|
||||
* The function returns the value of a protected variable.
|
||||
* @returns The value of the variable `_value` is being returned.
|
||||
*/
|
||||
get value(): E {
|
||||
return this._value;
|
||||
}
|
||||
|
||||
/**
|
||||
* The above function sets the value of a variable.
|
||||
* @param {E} value - The parameter "value" is of type E, which means it can be any type.
|
||||
*/
|
||||
set value(value: E) {
|
||||
this._value = value;
|
||||
}
|
||||
|
||||
protected _next: SinglyLinkedListNode<E> | undefined;
|
||||
|
||||
/**
|
||||
* The `next` function returns the next node in a singly linked list.
|
||||
* @returns The `next` property is being returned. It can be either a `SinglyLinkedListNode<E>`
|
||||
* object or `undefined`.
|
||||
*/
|
||||
get next(): SinglyLinkedListNode<E> | undefined {
|
||||
override get next(): SinglyLinkedListNode<E> | undefined {
|
||||
return this._next;
|
||||
}
|
||||
|
||||
/**
|
||||
* The "next" property of a SinglyLinkedListNode is set to the provided value.
|
||||
* @param {SinglyLinkedListNode<E> | undefined} value - The `value` parameter is of type
|
||||
* `SinglyLinkedListNode<E> | undefined`. This means that it can accept either a
|
||||
* `SinglyLinkedListNode` object or `undefined` as its value.
|
||||
*/
|
||||
set next(value: SinglyLinkedListNode<E> | undefined) {
|
||||
override set next(value: SinglyLinkedListNode<E> | undefined) {
|
||||
this._next = value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Node Structure: Each node contains three parts: a data field, a pointer (or reference) to the previous node, and a pointer to the next node. This structure allows traversal of the linked list in both directions.
|
||||
* 2. Bidirectional Traversal: Unlike doubly linked lists, singly linked lists can be easily traversed forwards but not backwards.
|
||||
* 3. No Centralized Index: Unlike arrays, elements in a linked list are not stored contiguously, so there is no centralized index. Accessing elements in a linked list typically requires traversing from the head or tail node.
|
||||
* 4. High Efficiency in Insertion and Deletion: Adding or removing elements in a linked list does not require moving other elements, making these operations more efficient than in arrays.
|
||||
* Caution: Although our linked list classes provide methods such as at, setAt, addAt, and indexOf that are based on array indices, their time complexity, like that of the native Array.lastIndexOf, is 𝑂(𝑛). If you need to use these methods frequently, you might want to consider other data structures, such as Deque or Queue (designed for random access). Similarly, since the native Array.shift method has a time complexity of 𝑂(𝑛), using an array to simulate a queue can be inefficient. In such cases, you should use Queue or Deque, as these data structures leverage deferred array rearrangement, effectively reducing the average time complexity to 𝑂(1).
|
||||
*
|
||||
*/
|
||||
export class SinglyLinkedList<E = any, R = any> extends LinearLinkedBase<E, R, SinglyLinkedListNode<E>> {
|
||||
|
@ -348,22 +325,27 @@ export class SinglyLinkedList<E = any, R = any> extends LinearLinkedBase<E, R, S
|
|||
* @returns The method `deleteAt` returns the value of the node that was deleted, or `undefined` if the index is out of
|
||||
* bounds.
|
||||
*/
|
||||
deleteAt(index: number): boolean {
|
||||
if (index < 0 || index >= this._length) return false;
|
||||
deleteAt(index: number): E | undefined {
|
||||
if (index < 0 || index >= this._length) return;
|
||||
let deleted: E | undefined;
|
||||
if (index === 0) {
|
||||
deleted = this.first;
|
||||
this.shift();
|
||||
return true;
|
||||
}
|
||||
if (index === this._length - 1) {
|
||||
this.pop();
|
||||
return true;
|
||||
return deleted;
|
||||
}
|
||||
|
||||
const prevNode = this.getNodeAt(index - 1);
|
||||
const removedNode = prevNode!.next;
|
||||
prevNode!.next = removedNode!.next;
|
||||
this._length--;
|
||||
return true;
|
||||
const targetNode = this.getNodeAt(index);
|
||||
const prevNode = this._getPrevNode(targetNode!);
|
||||
|
||||
if (prevNode && targetNode) {
|
||||
deleted = targetNode.value;
|
||||
prevNode.next = targetNode.next;
|
||||
if (targetNode === this.tail) this._tail = prevNode;
|
||||
this._length--;
|
||||
return deleted;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -377,37 +359,25 @@ export class SinglyLinkedList<E = any, R = any> extends LinearLinkedBase<E, R, S
|
|||
* successfully deleted from the linked list, and `false` if the value or node is not found in the linked list.
|
||||
*/
|
||||
delete(elementOrNode: E | SinglyLinkedListNode<E> | undefined): boolean {
|
||||
if (elementOrNode === undefined) return false;
|
||||
let value: E;
|
||||
if (elementOrNode instanceof SinglyLinkedListNode) {
|
||||
value = elementOrNode.value;
|
||||
if (elementOrNode === undefined || !this.head) return false;
|
||||
|
||||
const node = this.isNode(elementOrNode) ? elementOrNode : this.getNode(elementOrNode);
|
||||
|
||||
if (!node) return false;
|
||||
|
||||
const prevNode = this._getPrevNode(node);
|
||||
|
||||
if (!prevNode) {
|
||||
// The node is the head
|
||||
this._head = node.next;
|
||||
if (node === this.tail) this._tail = undefined;
|
||||
} else {
|
||||
value = elementOrNode;
|
||||
}
|
||||
let current = this.head,
|
||||
prev = undefined;
|
||||
|
||||
while (current) {
|
||||
if (current.value === value) {
|
||||
if (prev === undefined) {
|
||||
this._head = current.next;
|
||||
if (current === this.tail) {
|
||||
this._tail = undefined;
|
||||
}
|
||||
} else {
|
||||
prev.next = current.next;
|
||||
if (current === this.tail) {
|
||||
this._tail = prev;
|
||||
}
|
||||
}
|
||||
this._length--;
|
||||
return true;
|
||||
}
|
||||
prev = current;
|
||||
current = current.next;
|
||||
prevNode.next = node.next;
|
||||
if (node === this.tail) this._tail = prevNode;
|
||||
}
|
||||
|
||||
return false;
|
||||
this._length--;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -534,6 +504,7 @@ export class SinglyLinkedList<E = any, R = any> extends LinearLinkedBase<E, R, S
|
|||
elementNodeOrPredicate: E | SinglyLinkedListNode<E> | ((node: SinglyLinkedListNode<E>) => boolean) | undefined
|
||||
): SinglyLinkedListNode<E> | undefined {
|
||||
if (elementNodeOrPredicate === undefined) return;
|
||||
if (this.isNode(elementNodeOrPredicate)) return elementNodeOrPredicate;
|
||||
const predicate = this._ensurePredicate(elementNodeOrPredicate);
|
||||
let current = this.head;
|
||||
|
||||
|
@ -567,32 +538,22 @@ export class SinglyLinkedList<E = any, R = any> extends LinearLinkedBase<E, R, S
|
|||
existingElementOrNode: E | SinglyLinkedListNode<E>,
|
||||
newElementOrNode: E | SinglyLinkedListNode<E>
|
||||
): boolean {
|
||||
if (!this.head) return false;
|
||||
const existingNode = this.getNode(existingElementOrNode);
|
||||
if (!existingNode) return false;
|
||||
|
||||
let existingValue: E;
|
||||
if (this.isNode(existingElementOrNode)) {
|
||||
existingValue = existingElementOrNode.value;
|
||||
const prevNode = this._getPrevNode(existingNode);
|
||||
const newNode = this._ensureNode(newElementOrNode);
|
||||
|
||||
if (!prevNode) {
|
||||
// Add at the head
|
||||
this.unshift(newNode);
|
||||
} else {
|
||||
existingValue = existingElementOrNode;
|
||||
}
|
||||
if (this.head.value === existingValue) {
|
||||
this.unshift(newElementOrNode);
|
||||
return true;
|
||||
prevNode.next = newNode;
|
||||
newNode.next = existingNode;
|
||||
this._length++;
|
||||
}
|
||||
|
||||
let current = this.head;
|
||||
while (current.next) {
|
||||
if (current.next.value === existingValue) {
|
||||
const newNode = this._ensureNode(newElementOrNode);
|
||||
newNode.next = current.next;
|
||||
current.next = newNode;
|
||||
this._length++;
|
||||
return true;
|
||||
}
|
||||
current = current.next;
|
||||
}
|
||||
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -628,6 +589,75 @@ export class SinglyLinkedList<E = any, R = any> extends LinearLinkedBase<E, R, S
|
|||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Time Complexity: O(n)
|
||||
* Space Complexity: O(1)
|
||||
*
|
||||
* The function `splice` in TypeScript overrides the default behavior to remove and insert elements
|
||||
* in a singly linked list while handling boundary cases.
|
||||
* @param {number} start - The `start` parameter in the `splice` method indicates the index at which
|
||||
* to start modifying the list. It specifies the position where elements will be added or removed.
|
||||
* @param {number} [deleteCount=0] - The `deleteCount` parameter in the `splice` method specifies the
|
||||
* number of elements to remove from the array starting at the specified `start` index. If
|
||||
* `deleteCount` is not provided, it defaults to 0, meaning no elements will be removed but new
|
||||
* elements can still be inserted at
|
||||
* @param {E[]} items - The `items` parameter in the `splice` method represents the elements to be
|
||||
* inserted into the list at the specified `start` index. These elements will be inserted in place of
|
||||
* the elements that are removed from the list. The `splice` method allows you to add new elements to
|
||||
* the list while
|
||||
* @returns The `splice` method is returning a `SinglyLinkedList` containing the elements that were
|
||||
* removed from the original list during the splice operation.
|
||||
*/
|
||||
override splice(start: number, deleteCount: number = 0, ...items: E[]): this {
|
||||
const removedList = this._createInstance({ toElementFn: this._toElementFn, maxLen: this._maxLen });
|
||||
|
||||
// If `start` is out of range, perform boundary processing
|
||||
start = Math.max(0, Math.min(start, this.length));
|
||||
deleteCount = Math.max(0, deleteCount);
|
||||
|
||||
// Find the predecessor node of `start`
|
||||
const prevNode = start === 0 ? undefined : this.getNodeAt(start - 1);
|
||||
const startNode = prevNode ? prevNode.next : this.head;
|
||||
|
||||
let current = startNode;
|
||||
for (let i = 0; i < deleteCount && current; i++) {
|
||||
removedList.push(current.value);
|
||||
current = current.next;
|
||||
}
|
||||
|
||||
const nextNode = current;
|
||||
let lastInsertedNode: SinglyLinkedListNode<E> | undefined = undefined;
|
||||
|
||||
for (const item of items) {
|
||||
const newNode = this._ensureNode(item);
|
||||
if (!lastInsertedNode) {
|
||||
if (prevNode) {
|
||||
prevNode.next = newNode;
|
||||
} else {
|
||||
this._head = newNode;
|
||||
}
|
||||
} else {
|
||||
lastInsertedNode.next = newNode;
|
||||
}
|
||||
lastInsertedNode = newNode;
|
||||
}
|
||||
|
||||
// Connect new node to `nextNode`
|
||||
if (lastInsertedNode) {
|
||||
lastInsertedNode.next = nextNode;
|
||||
} else if (prevNode) {
|
||||
prevNode.next = nextNode;
|
||||
}
|
||||
|
||||
// Update tail node and length
|
||||
if (!nextNode) {
|
||||
this._tail = lastInsertedNode || prevNode;
|
||||
}
|
||||
this._length += items.length - removedList.length;
|
||||
|
||||
return removedList as this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Time Complexity: O(n)
|
||||
* Space Complexity: O(1)
|
||||
|
@ -685,7 +715,7 @@ export class SinglyLinkedList<E = any, R = any> extends LinearLinkedBase<E, R, S
|
|||
* elements that pass the filter condition specified by the `callback` function.
|
||||
*/
|
||||
filter(callback: ElementCallback<E, R, boolean>, thisArg?: any): SinglyLinkedList<E, R> {
|
||||
const filteredList = new SinglyLinkedList<E, R>([], { toElementFn: this.toElementFn });
|
||||
const filteredList = this._createInstance({ toElementFn: this.toElementFn, maxLen: this._maxLen });
|
||||
let index = 0;
|
||||
for (const current of this) {
|
||||
if (callback.call(thisArg, current, index, this)) {
|
||||
|
@ -721,7 +751,7 @@ export class SinglyLinkedList<E = any, R = any> extends LinearLinkedBase<E, R, S
|
|||
toElementFn?: (rawElement: RM) => EM,
|
||||
thisArg?: any
|
||||
): SinglyLinkedList<EM, RM> {
|
||||
const mappedList = new SinglyLinkedList<EM, RM>([], { toElementFn });
|
||||
const mappedList = new SinglyLinkedList<EM, RM>([], { toElementFn, maxLen: this._maxLen });
|
||||
let index = 0;
|
||||
for (const current of this) {
|
||||
mappedList.push(callback.call(thisArg, current, index, this));
|
||||
|
@ -731,6 +761,20 @@ export class SinglyLinkedList<E = any, R = any> extends LinearLinkedBase<E, R, S
|
|||
return mappedList;
|
||||
}
|
||||
|
||||
/**
|
||||
* The function `_createInstance` returns a new instance of `SinglyLinkedList` with the specified
|
||||
* options.
|
||||
* @param [options] - The `options` parameter in the `_createInstance` method is of type
|
||||
* `SinglyLinkedListOptions<E, R>`, which is used to configure the behavior of the `SinglyLinkedList`
|
||||
* instance being created. It is an optional parameter, meaning it can be omitted when calling the
|
||||
* method.
|
||||
* @returns An instance of the `SinglyLinkedList` class with an empty array and the provided options
|
||||
* is being returned.
|
||||
*/
|
||||
protected override _createInstance(options?: SinglyLinkedListOptions<E, R>): this {
|
||||
return new SinglyLinkedList<E, R>([], options) as this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The function `_getIterator` returns an iterable iterator that yields the values of a linked list.
|
||||
*/
|
||||
|
@ -743,6 +787,38 @@ export class SinglyLinkedList<E = any, R = any> extends LinearLinkedBase<E, R, S
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The function returns an iterator that iterates over the elements of a collection in reverse order.
|
||||
*/
|
||||
protected *_getReverseIterator(): IterableIterator<E> {
|
||||
const reversedArr = [...this].reverse();
|
||||
|
||||
for (const item of reversedArr) {
|
||||
yield item;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The function `_getNodeIterator` returns an iterator that iterates over the nodes of a singly
|
||||
* linked list.
|
||||
*/
|
||||
protected *_getNodeIterator(): IterableIterator<SinglyLinkedListNode<E>> {
|
||||
let current = this.head;
|
||||
|
||||
while (current) {
|
||||
yield current;
|
||||
current = current.next;
|
||||
}
|
||||
}
|
||||
|
||||
// protected *_getReverseNodeIterator(): IterableIterator<SinglyLinkedListNode<E>> {
|
||||
// const reversedArr = [...this._getNodeIterator()].reverse();
|
||||
//
|
||||
// for (const item of reversedArr) {
|
||||
// yield item;
|
||||
// }
|
||||
// }
|
||||
|
||||
/**
|
||||
* The _isPredicate function in TypeScript checks if the input is a function that takes a
|
||||
* SinglyLinkedListNode as an argument and returns a boolean.
|
||||
|
@ -793,27 +869,22 @@ export class SinglyLinkedList<E = any, R = any> extends LinearLinkedBase<E, R, S
|
|||
}
|
||||
|
||||
/**
|
||||
* The function `_createInstance` returns a new instance of `SinglyLinkedList` with the specified
|
||||
* options.
|
||||
* @param [options] - The `options` parameter in the `_createInstance` method is of type
|
||||
* `SinglyLinkedListOptions<E, R>`, which is used to configure the behavior of the `SinglyLinkedList`
|
||||
* instance being created. It is an optional parameter, meaning it can be omitted when calling the
|
||||
* method.
|
||||
* @returns An instance of the `SinglyLinkedList` class with an empty array and the provided options
|
||||
* is being returned.
|
||||
* The function `_getPrevNode` returns the node before a given node in a singly linked list.
|
||||
* @param node - The `node` parameter in the `_getPrevNode` method is a reference to a node in a
|
||||
* singly linked list. The method is used to find the node that comes before the given node in the
|
||||
* linked list.
|
||||
* @returns The `_getPrevNode` method returns either the previous node of the input node in a singly
|
||||
* linked list or `undefined` if the input node is the head of the list or if the input node is not
|
||||
* found in the list.
|
||||
*/
|
||||
protected override _createInstance(options?: SinglyLinkedListOptions<E, R>): this {
|
||||
return new SinglyLinkedList<E, R>([], options) as this;
|
||||
}
|
||||
protected _getPrevNode(node: SinglyLinkedListNode<E>): SinglyLinkedListNode<E> | undefined {
|
||||
if (!this.head || this.head === node) return undefined;
|
||||
|
||||
/**
|
||||
* The function returns an iterator that iterates over the elements of a collection in reverse order.
|
||||
*/
|
||||
protected *_getReverseIterator(): IterableIterator<E> {
|
||||
const reversedArr = [...this].reverse();
|
||||
|
||||
for (const item of reversedArr) {
|
||||
yield item;
|
||||
let current = this.head;
|
||||
while (current.next && current.next !== node) {
|
||||
current = current.next;
|
||||
}
|
||||
|
||||
return current.next === node ? current : undefined;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -486,7 +486,11 @@ export class Deque<E = any, R = any> extends LinearBase<E, R> {
|
|||
this._length = pos + 1;
|
||||
return this;
|
||||
} else {
|
||||
const newDeque = new Deque<E>([], { bucketSize: this._bucketSize });
|
||||
const newDeque = this._createInstance({
|
||||
bucketSize: this._bucketSize,
|
||||
toElementFn: this._toElementFn,
|
||||
maxLen: this._maxLen
|
||||
});
|
||||
|
||||
for (let i = 0; i <= pos; i++) {
|
||||
newDeque.push(this.at(i));
|
||||
|
@ -496,6 +500,61 @@ export class Deque<E = any, R = any> extends LinearBase<E, R> {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Time Complexity: O(n)
|
||||
* Space Complexity: O(1)
|
||||
*
|
||||
* The `splice` function in TypeScript overrides the default behavior to remove and insert elements
|
||||
* in a Deque data structure while ensuring the starting position and delete count are within bounds.
|
||||
* @param {number} start - The `start` parameter in the `splice` method represents the index at which
|
||||
* to start changing the array. Items will be removed or added starting from this index.
|
||||
* @param {number} deleteCount - The `deleteCount` parameter in the `splice` method represents the
|
||||
* number of elements to remove from the array starting at the specified `start` index. If
|
||||
* `deleteCount` is not provided, it defaults to the number of elements from the `start` index to the
|
||||
* end of the array (`
|
||||
* @param {E[]} items - The `items` parameter in the `splice` method represents the elements that
|
||||
* will be inserted into the deque at the specified `start` index. These elements will be inserted in
|
||||
* place of the elements that are removed based on the `start` and `deleteCount` parameters.
|
||||
* @returns The `splice` method is returning the array `deletedElements` which contains the elements
|
||||
* that were removed from the Deque during the splice operation.
|
||||
*/
|
||||
override splice(start: number, deleteCount: number = this._length - start, ...items: E[]): this {
|
||||
// Check whether the starting position is legal
|
||||
rangeCheck(start, 0, this._length);
|
||||
|
||||
// Adjust the value of deleteCount
|
||||
if (deleteCount < 0) deleteCount = 0;
|
||||
if (start + deleteCount > this._length) deleteCount = this._length - start;
|
||||
|
||||
// Save deleted elements
|
||||
const deletedElements = this._createInstance();
|
||||
|
||||
// Add removed elements to the result
|
||||
for (let i = 0; i < deleteCount; i++) {
|
||||
deletedElements.push(this.at(start + i));
|
||||
}
|
||||
|
||||
// Calculate the range that needs to be deleted
|
||||
const elementsAfter = [];
|
||||
for (let i = start + deleteCount; i < this._length; i++) {
|
||||
elementsAfter.push(this.at(i));
|
||||
}
|
||||
|
||||
// Adjust the length of the current Deque
|
||||
this.cut(start - 1, true);
|
||||
|
||||
for (const item of items) {
|
||||
this.push(item);
|
||||
}
|
||||
|
||||
// Insert subsequent elements back
|
||||
for (const element of elementsAfter) {
|
||||
this.push(element);
|
||||
}
|
||||
|
||||
return deletedElements;
|
||||
}
|
||||
|
||||
/**
|
||||
* Time Complexity: O(1)
|
||||
* Space Complexity: O(1) or O(n)
|
||||
|
@ -522,7 +581,11 @@ export class Deque<E = any, R = any> extends LinearBase<E, R> {
|
|||
this._length = this._length - pos;
|
||||
return this;
|
||||
} else {
|
||||
const newDeque = new Deque<E>([], { bucketSize: this._bucketSize });
|
||||
const newDeque = this._createInstance({
|
||||
bucketSize: this._bucketSize,
|
||||
toElementFn: this._toElementFn,
|
||||
maxLen: this._maxLen
|
||||
});
|
||||
if (pos < 0) pos = 0;
|
||||
for (let i = pos; i < this._length; i++) {
|
||||
newDeque.push(this.at(i));
|
||||
|
@ -543,22 +606,34 @@ export class Deque<E = any, R = any> extends LinearBase<E, R> {
|
|||
* the index of the element to be deleted.
|
||||
* @returns The size of the data structure after the deletion operation is performed.
|
||||
*/
|
||||
deleteAt(pos: number): boolean {
|
||||
deleteAt(pos: number): E | undefined {
|
||||
rangeCheck(pos, 0, this._length - 1);
|
||||
if (pos === 0) this.shift();
|
||||
else if (pos === this._length - 1) this.pop();
|
||||
else {
|
||||
const length = this._length - 1;
|
||||
let { bucketIndex: curBucket, indexInBucket: curPointer } = this._getBucketAndPosition(pos);
|
||||
for (let i = pos; i < length; ++i) {
|
||||
const { bucketIndex: nextBucket, indexInBucket: nextPointer } = this._getBucketAndPosition(pos + 1);
|
||||
this._buckets[curBucket][curPointer] = this._buckets[nextBucket][nextPointer];
|
||||
curBucket = nextBucket;
|
||||
curPointer = nextPointer;
|
||||
}
|
||||
|
||||
let deleted: E | undefined;
|
||||
if (pos === 0) {
|
||||
//If it is the first element, use shift() directly
|
||||
return this.shift();
|
||||
} else if (pos === this._length - 1) {
|
||||
// If it is the last element, just use pop()
|
||||
deleted = this.last;
|
||||
this.pop();
|
||||
return deleted;
|
||||
} else {
|
||||
// Delete the middle element
|
||||
const length = this._length - 1;
|
||||
const { bucketIndex: targetBucket, indexInBucket: targetPointer } = this._getBucketAndPosition(pos);
|
||||
deleted = this._buckets[targetBucket][targetPointer];
|
||||
|
||||
for (let i = pos; i < length; i++) {
|
||||
const { bucketIndex: curBucket, indexInBucket: curPointer } = this._getBucketAndPosition(i);
|
||||
const { bucketIndex: nextBucket, indexInBucket: nextPointer } = this._getBucketAndPosition(i + 1);
|
||||
this._buckets[curBucket][curPointer] = this._buckets[nextBucket][nextPointer];
|
||||
}
|
||||
|
||||
// Remove last duplicate element
|
||||
this.pop();
|
||||
return deleted;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -588,6 +663,21 @@ export class Deque<E = any, R = any> extends LinearBase<E, R> {
|
|||
return true;
|
||||
}
|
||||
|
||||
// /**
|
||||
// * Time Complexity: O(n)
|
||||
// * Space Complexity: O(1)
|
||||
// *
|
||||
// * This function overrides the indexOf method to search for an element within a custom data
|
||||
// * structure.
|
||||
// * @param {E} searchElement - The `searchElement` parameter is the element that you are searching for
|
||||
// * within the data structure. The `indexOf` method will return the index of the first occurrence of
|
||||
// * this element within the data structure.
|
||||
// * @param {number} [fromIndex=0] - The `fromIndex` parameter in the `indexOf` method specifies the
|
||||
// * index at which to start searching for the `searchElement` within the data structure. If provided,
|
||||
// * the search will begin at this index instead of the beginning of the data structure.
|
||||
// * @returns The indexOf method is returning the index of the searchElement if it is found in the data
|
||||
// * structure, or -1 if the searchElement is not found.
|
||||
// */
|
||||
// override indexOf(searchElement: E, fromIndex: number = 0): number {
|
||||
// let index = fromIndex;
|
||||
// let bucketIndex = this._bucketFirst;
|
||||
|
@ -720,7 +810,11 @@ export class Deque<E = any, R = any> extends LinearBase<E, R> {
|
|||
* satisfy the given predicate function.
|
||||
*/
|
||||
filter(predicate: ElementCallback<E, R, boolean>, thisArg?: any): Deque<E, R> {
|
||||
const newDeque = new Deque<E, R>([], { bucketSize: this._bucketSize, toElementFn: this.toElementFn });
|
||||
const newDeque = this._createInstance({
|
||||
bucketSize: this._bucketSize,
|
||||
toElementFn: this.toElementFn,
|
||||
maxLen: this._maxLen
|
||||
});
|
||||
let index = 0;
|
||||
for (const el of this) {
|
||||
if (predicate.call(thisArg, el, index, this)) {
|
||||
|
@ -750,7 +844,7 @@ export class Deque<E = any, R = any> extends LinearBase<E, R> {
|
|||
* @returns a new Deque object with elements of type EM and raw elements of type RM.
|
||||
*/
|
||||
map<EM, RM>(callback: ElementCallback<E, R, EM>, toElementFn?: (rawElement: RM) => EM, thisArg?: any): Deque<EM, RM> {
|
||||
const newDeque = new Deque<EM, RM>([], { bucketSize: this._bucketSize, toElementFn });
|
||||
const newDeque = new Deque<EM, RM>([], { bucketSize: this._bucketSize, toElementFn, maxLen: this._maxLen });
|
||||
let index = 0;
|
||||
for (const el of this) {
|
||||
newDeque.push(callback.call(thisArg, el, index, this));
|
||||
|
|
|
@ -154,7 +154,7 @@ export class Queue<E = any, R = any> extends LinearBase<E, R> {
|
|||
*/
|
||||
delete(element: E): boolean {
|
||||
const index = this.elements.indexOf(element);
|
||||
return this.deleteAt(index);
|
||||
return !!this.deleteAt(index);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -165,9 +165,10 @@ export class Queue<E = any, R = any> extends LinearBase<E, R> {
|
|||
* @param {number} index - Determine the index of the element to be deleted
|
||||
* @return A boolean value
|
||||
*/
|
||||
deleteAt(index: number): boolean {
|
||||
const spliced = this.elements.splice(index, 1);
|
||||
return spliced.length === 1;
|
||||
deleteAt(index: number): E | undefined {
|
||||
const deleted = this.elements[index];
|
||||
this.elements.splice(index, 1);
|
||||
return deleted;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -275,6 +276,40 @@ export class Queue<E = any, R = any> extends LinearBase<E, R> {
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Time Complexity: O(n)
|
||||
* Space Complexity: O(n)
|
||||
*
|
||||
* The function overrides the splice method to remove and insert elements in a queue-like data
|
||||
* structure.
|
||||
* @param {number} start - The `start` parameter in the `splice` method specifies the index at which
|
||||
* to start changing the array. Items will be added or removed starting from this index.
|
||||
* @param {number} [deleteCount=0] - The `deleteCount` parameter in the `splice` method specifies the
|
||||
* number of elements to remove from the array starting at the specified `start` index. If
|
||||
* `deleteCount` is not provided, it defaults to 0, meaning no elements will be removed but new
|
||||
* elements can still be inserted at
|
||||
* @param {E[]} items - The `items` parameter in the `splice` method represents the elements that
|
||||
* will be added to the array at the specified `start` index. These elements will replace the
|
||||
* existing elements starting from the `start` index for the `deleteCount` number of elements.
|
||||
* @returns The `splice` method is returning the `removedQueue`, which is an instance of the same
|
||||
* class as the original object.
|
||||
*/
|
||||
override splice(start: number, deleteCount: number = 0, ...items: E[]): this {
|
||||
const removedQueue = this._createInstance();
|
||||
|
||||
start = Math.max(0, Math.min(start, this.length));
|
||||
deleteCount = Math.max(0, Math.min(deleteCount, this.length - start));
|
||||
|
||||
const globalStartIndex = this.offset + start;
|
||||
|
||||
const removedElements = this._elements.splice(globalStartIndex, deleteCount, ...items);
|
||||
removedQueue.pushMany(removedElements);
|
||||
|
||||
this.compact();
|
||||
|
||||
return removedQueue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Time Complexity: O(n)
|
||||
* Space Complexity: O(n)
|
||||
|
@ -303,7 +338,11 @@ export class Queue<E = any, R = any> extends LinearBase<E, R> {
|
|||
* satisfy the given predicate function.
|
||||
*/
|
||||
filter(predicate: ElementCallback<E, R, boolean>, thisArg?: any): Queue<E, R> {
|
||||
const newDeque = new Queue<E, R>([], { toElementFn: this.toElementFn });
|
||||
const newDeque = this._createInstance({
|
||||
toElementFn: this._toElementFn,
|
||||
autoCompactRatio: this._autoCompactRatio,
|
||||
maxLen: this._maxLen
|
||||
});
|
||||
let index = 0;
|
||||
for (const el of this) {
|
||||
if (predicate.call(thisArg, el, index, this)) {
|
||||
|
@ -333,7 +372,11 @@ export class Queue<E = any, R = any> extends LinearBase<E, R> {
|
|||
* callback function to each element in the original Queue object.
|
||||
*/
|
||||
map<EM, RM>(callback: ElementCallback<E, R, EM>, toElementFn?: (rawElement: RM) => EM, thisArg?: any): Queue<EM, RM> {
|
||||
const newDeque = new Queue<EM, RM>([], { toElementFn });
|
||||
const newDeque = new Queue<EM, RM>([], {
|
||||
toElementFn,
|
||||
autoCompactRatio: this._autoCompactRatio,
|
||||
maxLen: this._maxLen
|
||||
});
|
||||
let index = 0;
|
||||
for (const el of this) {
|
||||
newDeque.push(callback.call(thisArg, el, index, this));
|
||||
|
|
|
@ -23,7 +23,7 @@ describe('AVL Tree Test from data-structure-typed', () => {
|
|||
expect(getMinNodeBySpecificNode?.key).toBe(12);
|
||||
|
||||
let subTreeSum = 0;
|
||||
if (node15) tree.dfs(node => (subTreeSum += node.key), 'PRE', 15);
|
||||
if (node15) tree.dfs(node => (subTreeSum += node.key), 'PRE', false, 15);
|
||||
expect(subTreeSum).toBe(70);
|
||||
|
||||
let lesserSum = 0;
|
||||
|
|
|
@ -40,7 +40,7 @@ describe('AVL Tree Test', () => {
|
|||
expect(getMinNodeBySpecificNode?.key).toBe(12);
|
||||
|
||||
let subTreeSum = 0;
|
||||
if (node15) tree.dfs(node => (subTreeSum += node.key), 'IN', 15);
|
||||
if (node15) tree.dfs(node => (subTreeSum += node.key), 'IN', false, 15);
|
||||
expect(subTreeSum).toBe(70);
|
||||
|
||||
let lesserSum = 0;
|
||||
|
|
|
@ -37,7 +37,7 @@ describe('Individual package BST operations test', () => {
|
|||
expect(minNodeBySpecificNode?.key).toBe(14);
|
||||
|
||||
let subTreeSum = 0;
|
||||
if (node15) bst.dfs(node => (subTreeSum += node.key), 'IN', 15);
|
||||
if (node15) bst.dfs(node => (subTreeSum += node.key), 'IN', false, 15);
|
||||
expect(subTreeSum).toBe(45);
|
||||
|
||||
let lesserSum = 0;
|
||||
|
@ -236,7 +236,7 @@ describe('Individual package BST operations test', () => {
|
|||
expect(minNodeBySpecificNode?.key).toBe(14);
|
||||
|
||||
let subTreeSum = 0;
|
||||
if (node15) objBST.dfs(node => (subTreeSum += node.key), 'IN', node15);
|
||||
if (node15) objBST.dfs(node => (subTreeSum += node.key), 'IN', false, node15);
|
||||
expect(subTreeSum).toBe(45);
|
||||
|
||||
let lesserSum = 0;
|
||||
|
|
|
@ -32,14 +32,14 @@ describe('DoublyLinkedList Operation Test', () => {
|
|||
});
|
||||
|
||||
it('should deleteAt', () => {
|
||||
expect(list.deleteAt(1)).toBe(true);
|
||||
expect(list.deleteAt(-1)).toBe(false);
|
||||
expect(list.deleteAt(list.length)).toBe(false);
|
||||
expect(list.deleteAt(1)).toBe(2);
|
||||
expect(list.deleteAt(-1)).toBe(undefined);
|
||||
expect(list.deleteAt(list.length)).toBe(undefined);
|
||||
expect(list.length).toBe(4);
|
||||
expect(list.deleteAt(4)).toBe(false);
|
||||
expect(list.deleteAt(4)).toBe(undefined);
|
||||
expect([...list]).toEqual([1, 3, 4, 5]);
|
||||
expect(list.isEmpty()).toBe(false);
|
||||
expect(list.deleteAt(3)).toBe(true);
|
||||
expect(list.deleteAt(3)).toBe(5);
|
||||
expect([...list]).toEqual([1, 3, 4]);
|
||||
});
|
||||
|
||||
|
@ -190,7 +190,7 @@ describe('DoublyLinkedList Operation Test', () => {
|
|||
|
||||
// Deleting from the beginning
|
||||
const deletedValue = list.deleteAt(0);
|
||||
expect(deletedValue).toBe(true);
|
||||
expect(deletedValue).toBe(1);
|
||||
expect(list.length).toBe(2);
|
||||
expect(list.head!.value).toBe(2);
|
||||
|
||||
|
|
|
@ -300,14 +300,14 @@ describe('SinglyLinkedList Operation Test', () => {
|
|||
list.push(2);
|
||||
list.push(3);
|
||||
const removed = list.deleteAt(1);
|
||||
expect(removed).toBe(true);
|
||||
expect(removed).toBe(2);
|
||||
expect(list.toArray()).toEqual([1, 3]);
|
||||
});
|
||||
|
||||
it('should return undefined for an out-of-bounds index', () => {
|
||||
list.push(1);
|
||||
const removed = list.deleteAt(1);
|
||||
expect(removed).toBe(false);
|
||||
expect(removed).toBe(undefined);
|
||||
});
|
||||
|
||||
it('should delete and return the first element', () => {
|
||||
|
@ -318,7 +318,7 @@ describe('SinglyLinkedList Operation Test', () => {
|
|||
const removed = list.deleteAt(0);
|
||||
expect(list.first).toBe(2);
|
||||
expect(list.last).toBe(2);
|
||||
expect(removed).toBe(true);
|
||||
expect(removed).toBe(1);
|
||||
expect(list.toArray()).toEqual([2]);
|
||||
});
|
||||
|
||||
|
@ -326,7 +326,7 @@ describe('SinglyLinkedList Operation Test', () => {
|
|||
list.push(1);
|
||||
list.push(2);
|
||||
const removed = list.deleteAt(1);
|
||||
expect(removed).toBe(true);
|
||||
expect(removed).toBe(2);
|
||||
expect(list.toArray()).toEqual([1]);
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue