mirror of https://github.com/abpframework/abp
commit
651ca79ce0
@ -0,0 +1,6 @@
|
||||
# Getting Started with the Startup Templates
|
||||
|
||||
See the following tutorials to learn how to get started with the ABP Framework using the pre-built application startup templates:
|
||||
|
||||
* [Getting Started With the ASP.NET Core MVC / Razor Pages UI](Getting-Started-AspNetCore-MVC-Template.md)
|
||||
* [Getting Started with the Angular UI](Getting-Started-Angular-Template.md)
|
@ -0,0 +1,844 @@
|
||||
# Linked List (Doubly)
|
||||
|
||||
|
||||
|
||||
The core module provides a useful data structure known as a [doubly linked list](https://en.wikipedia.org/wiki/Doubly_linked_list). Briefly, a doubly linked list is a series of records (a.k.a. nodes) which has information on the previous node, the next node, and its own value (or data).
|
||||
|
||||
|
||||
|
||||
## Getting Started
|
||||
|
||||
To create a doubly linked list, all you have to do is to import and create a new instance of it:
|
||||
|
||||
```js
|
||||
import { LinkedList } from '@abp/ng.core';
|
||||
|
||||
const list = new LinkedList();
|
||||
```
|
||||
|
||||
|
||||
|
||||
The constructor does not get any parameters.
|
||||
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
### How to Add New Nodes
|
||||
|
||||
There are a few methods to create new nodes in a linked list and all of them are separately available as well as revealed from an `add` method.
|
||||
|
||||
|
||||
|
||||
#### addHead(value: T): ListNode\<T\>
|
||||
|
||||
Adds a node with given value as the first node in list:
|
||||
|
||||
```js
|
||||
list.addHead('a');
|
||||
|
||||
// "a"
|
||||
|
||||
list.addHead('b');
|
||||
|
||||
// "b" <-> "a"
|
||||
|
||||
list.addHead('c');
|
||||
|
||||
// "c" <-> "b" <-> "a"
|
||||
```
|
||||
|
||||
|
||||
|
||||
#### addTail(value: T): ListNode\<T\>
|
||||
|
||||
Adds a node with given value as the last node in list:
|
||||
|
||||
```js
|
||||
list.addTail('a');
|
||||
|
||||
// "a"
|
||||
|
||||
list.addTail('b');
|
||||
|
||||
// "a" <-> "b"
|
||||
|
||||
list.addTail('c');
|
||||
|
||||
// "a" <-> "b" <-> "c"
|
||||
```
|
||||
|
||||
|
||||
|
||||
#### addAfter(value: T, previousValue: T, compareFn = compare): ListNode\<T\>
|
||||
|
||||
Adds a node with given value after the first node that has the previous value:
|
||||
|
||||
```js
|
||||
list.addTail('a');
|
||||
list.addTail('b');
|
||||
list.addTail('b');
|
||||
list.addTail('c');
|
||||
|
||||
// "a" <-> "b" <-> "b" <-> "c"
|
||||
|
||||
list.addAfter('x', 'b');
|
||||
|
||||
// "a" <-> "b" <-> "x" <-> "b" <-> "c"
|
||||
```
|
||||
|
||||
|
||||
|
||||
You may pass a custom compare function to detect the searched value:
|
||||
|
||||
```js
|
||||
list.addTail({ x: 1 });
|
||||
list.addTail({ x: 2 });
|
||||
list.addTail({ x: 3 });
|
||||
|
||||
// {"x":1} <-> {"x":2} <-> {"x":3}
|
||||
|
||||
list.addAfter({ x: 0 }, { x: 2 }, (v1, v2) => v1.x === v2.x);
|
||||
|
||||
// {"x":1} <-> {"x":2} <-> {"x":0} <-> {"x":3}
|
||||
```
|
||||
|
||||
|
||||
|
||||
> The default compare function checks deep equality, so you will rarely need to pass that parameter.
|
||||
|
||||
|
||||
|
||||
#### addBefore(value: T, nextValue: T, compareFn = compare): ListNode\<T\>
|
||||
|
||||
Adds a node with given value before the first node that has the next value:
|
||||
|
||||
```js
|
||||
list.addTail('a');
|
||||
list.addTail('b');
|
||||
list.addTail('b');
|
||||
list.addTail('c');
|
||||
|
||||
// "a" <-> "b" <-> "b" <-> "c"
|
||||
|
||||
list.addBefore('x', 'b');
|
||||
|
||||
// "a" <-> "x" <-> "b" <-> "b" <-> "c"
|
||||
```
|
||||
|
||||
|
||||
|
||||
You may pass a custom compare function to detect the searched value:
|
||||
|
||||
```js
|
||||
list.addTail({ x: 1 });
|
||||
list.addTail({ x: 2 });
|
||||
list.addTail({ x: 3 });
|
||||
|
||||
// {"x":1} <-> {"x":2} <-> {"x":3}
|
||||
|
||||
list.addBefore({ x: 0 }, { x: 2 }, (v1, v2) => v1.x === v2.x);
|
||||
|
||||
// {"x":1} <-> {"x":0} <-> {"x":2} <-> {"x":3}
|
||||
```
|
||||
|
||||
|
||||
|
||||
> The default compare function checks deep equality, so you will rarely need to pass that parameter.
|
||||
|
||||
|
||||
|
||||
#### addByIndex(value: T, position: number): ListNode\<T\>
|
||||
|
||||
Adds a node with given value at the specified position in the list:
|
||||
|
||||
```js
|
||||
list.addTail('a');
|
||||
list.addTail('b');
|
||||
list.addTail('c');
|
||||
|
||||
// "a" <-> "b" <-> "c"
|
||||
|
||||
list.addByIndex('x', 2);
|
||||
|
||||
// "a" <-> "b" <-> "x" <-> "c"
|
||||
```
|
||||
|
||||
|
||||
|
||||
#### add(value: T).head(): ListNode\<T\>
|
||||
|
||||
Adds a node with given value as the first node in list:
|
||||
|
||||
```js
|
||||
list.add('a').head();
|
||||
|
||||
// "a"
|
||||
|
||||
list.add('b').head();
|
||||
|
||||
// "b" <-> "a"
|
||||
|
||||
list.add('c').head();
|
||||
|
||||
// "c" <-> "b" <-> "a"
|
||||
```
|
||||
|
||||
|
||||
|
||||
> This is an alternative API for `addHead`.
|
||||
|
||||
|
||||
|
||||
#### add(value: T).tail(): ListNode\<T\>
|
||||
|
||||
Adds a node with given value as the last node in list:
|
||||
|
||||
```js
|
||||
list.add('a').tail();
|
||||
|
||||
// "a"
|
||||
|
||||
list.add('b').tail();
|
||||
|
||||
// "a" <-> "b"
|
||||
|
||||
list.add('c').tail();
|
||||
|
||||
// "a" <-> "b" <-> "c"
|
||||
```
|
||||
|
||||
|
||||
|
||||
> This is an alternative API for `addTail`.
|
||||
|
||||
|
||||
|
||||
#### add(value: T).after(previousValue: T, compareFn = compare): ListNode\<T\>
|
||||
|
||||
Adds a node with given value after the first node that has the previous value:
|
||||
|
||||
```js
|
||||
list.add('a').tail();
|
||||
list.add('b').tail();
|
||||
list.add('b').tail();
|
||||
list.add('c').tail();
|
||||
|
||||
// "a" <-> "b" <-> "b" <-> "c"
|
||||
|
||||
list.add('x').after('b');
|
||||
|
||||
// "a" <-> "b" <-> "x" <-> "b" <-> "c"
|
||||
```
|
||||
|
||||
|
||||
|
||||
You may pass a custom compare function to detect the searched value:
|
||||
|
||||
```js
|
||||
list.add({ x: 1 }).tail();
|
||||
list.add({ x: 2 }).tail();
|
||||
list.add({ x: 3 }).tail();
|
||||
|
||||
// {"x":1} <-> {"x":2} <-> {"x":3}
|
||||
|
||||
list.add({ x: 0 }).after({ x: 2 }, (v1, v2) => v1.x === v2.x);
|
||||
|
||||
// {"x":1} <-> {"x":2} <-> {"x":0} <-> {"x":3}
|
||||
```
|
||||
|
||||
|
||||
|
||||
> This is an alternative API for `addAfter`.
|
||||
>
|
||||
> The default compare function checks deep equality, so you will rarely need to pass that parameter.
|
||||
|
||||
|
||||
|
||||
#### add(value: T).before(nextValue: T, compareFn = compare): ListNode\<T\>
|
||||
|
||||
Adds a node with given value before the first node that has the next value:
|
||||
|
||||
```js
|
||||
list.add('a').tail();
|
||||
list.add('b').tail();
|
||||
list.add('b').tail();
|
||||
list.add('c').tail();
|
||||
|
||||
// "a" <-> "b" <-> "b" <-> "c"
|
||||
|
||||
list.add('x').before('b');
|
||||
|
||||
// "a" <-> "x" <-> "b" <-> "b" <-> "c"
|
||||
```
|
||||
|
||||
|
||||
|
||||
You may pass a custom compare function to detect the searched value:
|
||||
|
||||
```js
|
||||
list.add({ x: 1 }).tail();
|
||||
list.add({ x: 2 }).tail();
|
||||
list.add({ x: 3 }).tail();
|
||||
|
||||
// {"x":1} <-> {"x":2} <-> {"x":3}
|
||||
|
||||
list.add({ x: 0 }).before({ x: 2 }, (v1, v2) => v1.x === v2.x);
|
||||
|
||||
// {"x":1} <-> {"x":0} <-> {"x":2} <-> {"x":3}
|
||||
```
|
||||
|
||||
|
||||
|
||||
> This is an alternative API for `addBefore`.
|
||||
>
|
||||
> The default compare function checks deep equality, so you will rarely need to pass that parameter.
|
||||
|
||||
|
||||
|
||||
#### add(value: T).byIndex(position: number): ListNode\<T\>
|
||||
|
||||
Adds a node with given value at the specified position in the list:
|
||||
|
||||
```js
|
||||
list.add('a').tail();
|
||||
list.add('b').tail();
|
||||
list.add('c').tail();
|
||||
|
||||
// "a" <-> "b" <-> "c"
|
||||
|
||||
list.add('x').byIndex(2);
|
||||
|
||||
// "a" <-> "b" <-> "x" <-> "c"
|
||||
```
|
||||
|
||||
|
||||
|
||||
> This is an alternative API for `addByIndex`.
|
||||
|
||||
|
||||
|
||||
### How to Remove Nodes
|
||||
|
||||
There are a few methods to remove nodes from a linked list and all of them are separately available as well as revealed from a `drop` method.
|
||||
|
||||
|
||||
|
||||
#### dropHead(): ListNode\<T\> | undefined
|
||||
|
||||
Removes the first node from the list:
|
||||
|
||||
```js
|
||||
list.addTail('a');
|
||||
list.addTail('b');
|
||||
list.addTail('c');
|
||||
|
||||
// "a" <-> "b" <-> "c"
|
||||
|
||||
list.dropHead();
|
||||
|
||||
// "b" <-> "c"
|
||||
```
|
||||
|
||||
|
||||
|
||||
#### dropTail(): ListNode\<T\> | undefined
|
||||
|
||||
Removes the last node from the list:
|
||||
|
||||
```js
|
||||
list.addTail('a');
|
||||
list.addTail('b');
|
||||
list.addTail('c');
|
||||
|
||||
// "a" <-> "b" <-> "c"
|
||||
|
||||
list.dropTail();
|
||||
|
||||
// "a" <-> "b"
|
||||
```
|
||||
|
||||
|
||||
|
||||
#### dropByIndex(position: number): ListNode\<T\> | undefined
|
||||
|
||||
Removes the node with the specified position from the list:
|
||||
|
||||
```js
|
||||
list.addTail('a');
|
||||
list.addTail('b');
|
||||
list.addTail('c');
|
||||
|
||||
// "a" <-> "b" <-> "c"
|
||||
|
||||
list.dropByIndex(1);
|
||||
|
||||
// "a" <-> "c"
|
||||
```
|
||||
|
||||
|
||||
|
||||
#### dropByValue(value: T, compareFn = compare): ListNode\<T\> | undefined
|
||||
|
||||
Removes the first node with given value from the list:
|
||||
|
||||
```js
|
||||
list.addTail('a');
|
||||
list.addTail('x');
|
||||
list.addTail('b');
|
||||
list.addTail('x');
|
||||
list.addTail('c');
|
||||
|
||||
// "a" <-> "x" <-> "b" <-> "x" <-> "c"
|
||||
|
||||
list.dropByValue('x');
|
||||
|
||||
// "a" <-> "b" <-> "x" <-> "c"
|
||||
```
|
||||
|
||||
|
||||
|
||||
You may pass a custom compare function to detect the searched value:
|
||||
|
||||
```js
|
||||
list.addTail({ x: 1 });
|
||||
list.addTail({ x: 0 });
|
||||
list.addTail({ x: 2 });
|
||||
list.addTail({ x: 0 });
|
||||
list.addTail({ x: 3 });
|
||||
|
||||
// {"x":1} <-> {"x":0} <-> {"x":2} <-> {"x":0} <-> {"x":3}
|
||||
|
||||
list.dropByValue({ x: 0 }, (v1, v2) => v1.x === v2.x);
|
||||
|
||||
// {"x":1} <-> {"x":2} <-> {"x":0} <-> {"x":3}
|
||||
```
|
||||
|
||||
|
||||
|
||||
> The default compare function checks deep equality, so you will rarely need to pass that parameter.
|
||||
|
||||
|
||||
|
||||
#### dropByValueAll(value: T, compareFn = compare): ListNode\<T\>\[\]
|
||||
|
||||
Removes all nodes with given value from the list:
|
||||
|
||||
```js
|
||||
list.addTail('a');
|
||||
list.addTail('x');
|
||||
list.addTail('b');
|
||||
list.addTail('x');
|
||||
list.addTail('c');
|
||||
|
||||
// "a" <-> "x" <-> "b" <-> "x" <-> "c"
|
||||
|
||||
list.dropByValueAll('x');
|
||||
|
||||
// "a" <-> "b" <-> "c"
|
||||
```
|
||||
|
||||
|
||||
|
||||
You may pass a custom compare function to detect the searched value:
|
||||
|
||||
```js
|
||||
list.addTail({ x: 1 });
|
||||
list.addTail({ x: 0 });
|
||||
list.addTail({ x: 2 });
|
||||
list.addTail({ x: 0 });
|
||||
list.addTail({ x: 3 });
|
||||
|
||||
// {"x":1} <-> {"x":0} <-> {"x":2} <-> {"x":0} <-> {"x":3}
|
||||
|
||||
list.dropByValue({ x: 0 }, (v1, v2) => v1.x === v2.x);
|
||||
|
||||
// {"x":1} <-> {"x":2} <-> {"x":3}
|
||||
```
|
||||
|
||||
|
||||
|
||||
> The default compare function checks deep equality, so you will rarely need to pass that parameter.
|
||||
|
||||
|
||||
|
||||
#### drop().head(): ListNode\<T\> | undefined
|
||||
|
||||
Removes the first node in list:
|
||||
|
||||
```js
|
||||
list.add('a').tail();
|
||||
list.add('b').tail();
|
||||
list.add('c').tail();
|
||||
|
||||
// "a" <-> "b" <-> "c"
|
||||
|
||||
list.drop().head();
|
||||
|
||||
// "b" <-> "c"
|
||||
```
|
||||
|
||||
|
||||
|
||||
> This is an alternative API for `dropHead`.
|
||||
|
||||
|
||||
|
||||
#### drop().tail(): ListNode\<T\> | undefined
|
||||
|
||||
Removes the last node in list:
|
||||
|
||||
```js
|
||||
list.add('a').tail();
|
||||
list.add('b').tail();
|
||||
list.add('c').tail();
|
||||
|
||||
// "a" <-> "b" <-> "c"
|
||||
|
||||
list.drop().tail();
|
||||
|
||||
// "a" <-> "b"
|
||||
```
|
||||
|
||||
|
||||
|
||||
> This is an alternative API for `dropTail`.
|
||||
|
||||
|
||||
|
||||
#### drop().byIndex(position: number): ListNode\<T\> | undefined
|
||||
|
||||
Removes the node with the specified position from the list:
|
||||
|
||||
```js
|
||||
list.add('a').tail();
|
||||
list.add('b').tail();
|
||||
list.add('c').tail();
|
||||
|
||||
// "a" <-> "b" <-> "c"
|
||||
|
||||
list.drop().byIndex(1);
|
||||
|
||||
// "a" <-> "c"
|
||||
```
|
||||
|
||||
|
||||
|
||||
> This is an alternative API for `dropByIndex`.
|
||||
|
||||
|
||||
|
||||
#### drop().byValue(value: T, compareFn = compare): ListNode\<T\> | undefined
|
||||
|
||||
Removes the first node with given value from the list:
|
||||
|
||||
```js
|
||||
list.add('a').tail();
|
||||
list.add('x').tail();
|
||||
list.add('b').tail();
|
||||
list.add('x').tail();
|
||||
list.add('c').tail();
|
||||
|
||||
// "a" <-> "x" <-> "b" <-> "x" <-> "c"
|
||||
|
||||
list.drop().byValue('x');
|
||||
|
||||
// "a" <-> "b" <-> "x" <-> "c"
|
||||
```
|
||||
|
||||
|
||||
|
||||
You may pass a custom compare function to detect the searched value:
|
||||
|
||||
```js
|
||||
list.add({ x: 1 }).tail();
|
||||
list.add({ x: 0 }).tail();
|
||||
list.add({ x: 2 }).tail();
|
||||
list.add({ x: 0 }).tail();
|
||||
list.add({ x: 3 }).tail();
|
||||
|
||||
// {"x":1} <-> {"x":0} <-> {"x":2} <-> {"x":0} <-> {"x":3}
|
||||
|
||||
list.drop().byValue({ x: 0 }, (v1, v2) => v1.x === v2.x);
|
||||
|
||||
// {"x":1} <-> {"x":2} <-> {"x":0} <-> {"x":3}
|
||||
```
|
||||
|
||||
|
||||
|
||||
> This is an alternative API for `dropByValue`.
|
||||
>
|
||||
> The default compare function checks deep equality, so you will rarely need to pass that parameter.
|
||||
|
||||
|
||||
|
||||
#### drop().byValueAll(value: T, compareFn = compare): ListNode\<T\>\[\]
|
||||
|
||||
Removes all nodes with given value from the list:
|
||||
|
||||
```js
|
||||
list.add('a').tail();
|
||||
list.add('x').tail();
|
||||
list.add('b').tail();
|
||||
list.add('x').tail();
|
||||
list.add('c').tail();
|
||||
|
||||
// "a" <-> "x" <-> "b" <-> "x" <-> "c"
|
||||
|
||||
list.drop().byValueAll('x');
|
||||
|
||||
// "a" <-> "b" <-> "c"
|
||||
```
|
||||
|
||||
|
||||
|
||||
You may pass a custom compare function to detect the searched value:
|
||||
|
||||
```js
|
||||
list.add({ x: 1 }).tail();
|
||||
list.add({ x: 0 }).tail();
|
||||
list.add({ x: 2 }).tail();
|
||||
list.add({ x: 0 }).tail();
|
||||
list.add({ x: 3 }).tail();
|
||||
|
||||
// {"x":1} <-> {"x":0} <-> {"x":2} <-> {"x":0} <-> {"x":3}
|
||||
|
||||
list.drop().byValueAll({ x: 0 }, (v1, v2) => v1.x === v2.x);
|
||||
|
||||
// {"x":1} <-> {"x":2} <-> {"x":3}
|
||||
```
|
||||
|
||||
|
||||
|
||||
> This is an alternative API for `dropByValueAll`.
|
||||
>
|
||||
> The default compare function checks deep equality, so you will rarely need to pass that parameter.
|
||||
|
||||
|
||||
|
||||
### How to Find Nodes
|
||||
|
||||
There are a few methods to find specific nodes in a linked list.
|
||||
|
||||
|
||||
|
||||
#### find(predicate: ListIteratorFunction\<T\>): ListNode\<T\> | undefined
|
||||
|
||||
Finds the first node from the list that matches the given predicate:
|
||||
|
||||
```js
|
||||
list.addTail('a');
|
||||
list.addTail('b');
|
||||
list.addTail('b');
|
||||
list.addTail('c');
|
||||
|
||||
// "a" <-> "b" <-> "b" <-> "c"
|
||||
|
||||
const found = list.find(node => node.value === 'b');
|
||||
|
||||
/*
|
||||
found.value === "b"
|
||||
found.previous.value === "a"
|
||||
found.next.value === "b"
|
||||
*/
|
||||
```
|
||||
|
||||
|
||||
|
||||
#### findIndex(predicate: ListIteratorFunction\<T\>): number
|
||||
|
||||
Finds the position of the first node from the list that matches the given predicate:
|
||||
|
||||
```js
|
||||
list.addTail('a');
|
||||
list.addTail('b');
|
||||
list.addTail('b');
|
||||
list.addTail('c');
|
||||
|
||||
// "a" <-> "b" <-> "b" <-> "c"
|
||||
|
||||
const i0 = list.findIndex(node => node.next && node.next.value === 'b');
|
||||
const i1 = list.findIndex(node => node.value === 'b');
|
||||
const i2 = list.findIndex(node => node.previous && node.previous.value === 'b');
|
||||
const i3 = list.findIndex(node => node.value === 'x');
|
||||
|
||||
/*
|
||||
i0 === 0
|
||||
i1 === 1
|
||||
i2 === 2
|
||||
i3 === -1
|
||||
*/
|
||||
```
|
||||
|
||||
|
||||
|
||||
#### get(position: number): ListNode\<T\> | undefined
|
||||
|
||||
Finds and returns the node with specific position in the list:
|
||||
|
||||
```js
|
||||
list.addTail('a');
|
||||
list.addTail('b');
|
||||
list.addTail('c');
|
||||
|
||||
// "a" <-> "b" <-> "c"
|
||||
|
||||
const found = list.get(1);
|
||||
|
||||
/*
|
||||
found.value === "b"
|
||||
found.previous.value === "a"
|
||||
found.next.value === "c"
|
||||
*/
|
||||
```
|
||||
|
||||
|
||||
|
||||
#### indexOf(value: T, compareFn = compare): number
|
||||
|
||||
Finds the position of the first node from the list that has the given value:
|
||||
|
||||
```js
|
||||
list.addTail('a');
|
||||
list.addTail('b');
|
||||
list.addTail('b');
|
||||
list.addTail('c');
|
||||
|
||||
// "a" <-> "b" <-> "b" <-> "c"
|
||||
|
||||
const i0 = list.indexOf('a');
|
||||
const i1 = list.indexOf('b');
|
||||
const i2 = list.indexOf('c');
|
||||
const i3 = list.indexOf('x');
|
||||
|
||||
/*
|
||||
i0 === 0
|
||||
i1 === 1
|
||||
i2 === 3
|
||||
i3 === -1
|
||||
*/
|
||||
```
|
||||
|
||||
|
||||
|
||||
You may pass a custom compare function to detect the searched value:
|
||||
|
||||
```js
|
||||
list.addTail({ x: 1 });
|
||||
list.addTail({ x: 0 });
|
||||
list.addTail({ x: 2 });
|
||||
list.addTail({ x: 0 });
|
||||
list.addTail({ x: 3 });
|
||||
|
||||
// {"x":1} <-> {"x":0} <-> {"x":2} <-> {"x":0} <-> {"x":3}
|
||||
|
||||
const i0 = indexOf({ x: 1 }, (v1, v2) => v1.x === v2.x);
|
||||
const i1 = indexOf({ x: 2 }, (v1, v2) => v1.x === v2.x);
|
||||
const i2 = indexOf({ x: 3 }, (v1, v2) => v1.x === v2.x);
|
||||
const i3 = indexOf({ x: 0 }, (v1, v2) => v1.x === v2.x);
|
||||
const i4 = indexOf({ x: 4 }, (v1, v2) => v1.x === v2.x);
|
||||
|
||||
/*
|
||||
i0 === 0
|
||||
i1 === 2
|
||||
i2 === 4
|
||||
i3 === 1
|
||||
i4 === -1
|
||||
*/
|
||||
```
|
||||
|
||||
|
||||
|
||||
> The default compare function checks deep equality, so you will rarely need to pass that parameter.
|
||||
|
||||
|
||||
|
||||
### How to Check All Nodes
|
||||
|
||||
There are a few ways to iterate over or display a linked list.
|
||||
|
||||
|
||||
|
||||
#### forEach(callback: ListIteratorFunction\<T\>): void
|
||||
|
||||
Runs a callback function on all nodes in a linked list from head to tail:
|
||||
|
||||
```js
|
||||
list.addTail('a');
|
||||
list.addTail('b');
|
||||
list.addTail('c');
|
||||
|
||||
// "a" <-> "b" <-> "c"
|
||||
|
||||
list.forEach((node, index) => console.log(node.value + index));
|
||||
|
||||
// 'a0'
|
||||
// 'b1'
|
||||
// 'c2'
|
||||
```
|
||||
|
||||
|
||||
|
||||
#### \*\[Symbol.iterator\]\(\)
|
||||
|
||||
A linked list is iterable. In other words, you may use methods like `for...of` on it.
|
||||
|
||||
```js
|
||||
list.addTail('a');
|
||||
list.addTail('b');
|
||||
list.addTail('c');
|
||||
|
||||
// "a" <-> "b" <-> "c"
|
||||
|
||||
for(const node of list) {
|
||||
console.log(node.value);
|
||||
}
|
||||
|
||||
// 'a'
|
||||
// 'b'
|
||||
// 'c'
|
||||
```
|
||||
|
||||
|
||||
|
||||
#### toArray(): T[]
|
||||
|
||||
Converts a linked list to an array:
|
||||
|
||||
```js
|
||||
list.addTail('a');
|
||||
list.addTail('b');
|
||||
list.addTail('c');
|
||||
|
||||
// "a" <-> "b" <-> "c"
|
||||
|
||||
const arr = list.toArray();
|
||||
|
||||
/*
|
||||
arr === ['a', 'b', 'c']
|
||||
*/
|
||||
```
|
||||
|
||||
|
||||
|
||||
#### toString(): string
|
||||
|
||||
Converts a linked list to a string representation of nodes and their relations:
|
||||
|
||||
```js
|
||||
list.addTail('a');
|
||||
list.addTail(2);
|
||||
list.addTail('c');
|
||||
list.addTail({ k: 4, v: 'd' });
|
||||
|
||||
// "a" <-> 2 <-> "c" <-> {"k":4,"v":"d"}
|
||||
|
||||
const str = list.toString();
|
||||
|
||||
/*
|
||||
str === '"a" <-> 2 <-> "c" <-> {"k":4,"v":"d"}'
|
||||
*/
|
||||
```
|
||||
|
||||
|
||||
|
After Width: | Height: | Size: 235 KiB |
@ -0,0 +1,802 @@
|
||||
import { LinkedList } from '../utils/linked-list';
|
||||
|
||||
describe('Linked List (Doubly)', () => {
|
||||
let list: LinkedList;
|
||||
|
||||
beforeEach(() => (list = new LinkedList()));
|
||||
|
||||
describe('#length', () => {
|
||||
it('should initially be 0', () => {
|
||||
expect(list.length).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#head', () => {
|
||||
it('should initially be undefined', () => {
|
||||
expect(list.head).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('#tail', () => {
|
||||
it('should initially be undefined', () => {
|
||||
expect(list.tail).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('#add', () => {
|
||||
describe('#head', () => {
|
||||
it('should add node to the head of the list', () => {
|
||||
list.addHead('a');
|
||||
|
||||
// "a"
|
||||
|
||||
expect(list.head.value).toBe('a');
|
||||
expect(list.tail.value).toBe('a');
|
||||
});
|
||||
|
||||
it('should create reference to previous and next nodes', () => {
|
||||
list.add('a').head();
|
||||
list.add('b').head();
|
||||
list.add('c').head();
|
||||
|
||||
// "c" <-> "b" <-> "a"
|
||||
|
||||
expect(list.length).toBe(3);
|
||||
expect(list.head.value).toBe('c');
|
||||
expect(list.head.next.value).toBe('b');
|
||||
expect(list.head.previous).toBeUndefined();
|
||||
expect(list.tail.value).toBe('a');
|
||||
expect(list.tail.previous.value).toBe('b');
|
||||
expect(list.tail.next).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('#tail', () => {
|
||||
it('should add node to the tail of the list', () => {
|
||||
list.addTail('a');
|
||||
|
||||
// "a"
|
||||
|
||||
expect(list.head.value).toBe('a');
|
||||
expect(list.tail.value).toBe('a');
|
||||
expect(list.tail.next).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should create reference to previous and next nodes', () => {
|
||||
list.add('a').tail();
|
||||
list.add('b').tail();
|
||||
list.add('c').tail();
|
||||
|
||||
// "a" <-> "b" <-> "c"
|
||||
|
||||
expect(list.length).toBe(3);
|
||||
expect(list.head.value).toBe('a');
|
||||
expect(list.head.next.value).toBe('b');
|
||||
expect(list.head.previous).toBeUndefined();
|
||||
expect(list.tail.value).toBe('c');
|
||||
expect(list.tail.previous.value).toBe('b');
|
||||
expect(list.tail.next).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('#after', () => {
|
||||
it('should place a node after node with given value', () => {
|
||||
list.add('a').tail();
|
||||
list.add('b').tail();
|
||||
list.add('c').tail();
|
||||
|
||||
// "a" <-> "b" <-> "c"
|
||||
|
||||
list.add('x').after('b');
|
||||
|
||||
// "a" <-> "b" <-> "x" <-> "c"
|
||||
|
||||
expect(list.length).toBe(4);
|
||||
expect(list.head.value).toBe('a');
|
||||
expect(list.head.next.value).toBe('b');
|
||||
expect(list.head.next.next.value).toBe('x');
|
||||
expect(list.head.next.next.next.value).toBe('c');
|
||||
expect(list.tail.value).toBe('c');
|
||||
expect(list.tail.previous.value).toBe('x');
|
||||
expect(list.tail.previous.previous.value).toBe('b');
|
||||
expect(list.tail.previous.previous.previous.value).toBe('a');
|
||||
});
|
||||
|
||||
it('should be able to receive a custom compareFn', () => {
|
||||
list.add({ x: 1 }).tail();
|
||||
list.add({ x: 2 }).tail();
|
||||
list.add({ x: 3 }).tail();
|
||||
|
||||
// {"x":1} <-> {"x":2} <-> {"x":3}
|
||||
|
||||
list.add({ x: 0 }).after({ x: 1 }, (v1: X, v2: X) => v1.x === v2.x);
|
||||
|
||||
// {"x":1} <-> {"x":0} <-> {"x":2} <-> {"x":3}
|
||||
|
||||
expect(list.length).toBe(4);
|
||||
expect(list.head.value.x).toBe(1);
|
||||
expect(list.head.next.value.x).toBe(0);
|
||||
expect(list.head.next.next.value.x).toBe(2);
|
||||
expect(list.head.next.next.next.value.x).toBe(3);
|
||||
expect(list.tail.value.x).toBe(3);
|
||||
expect(list.tail.previous.value.x).toBe(2);
|
||||
expect(list.tail.previous.previous.value.x).toBe(0);
|
||||
expect(list.tail.previous.previous.previous.value.x).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#before', () => {
|
||||
it('should place a node before node with given value', () => {
|
||||
list.add('a').tail();
|
||||
list.add('b').tail();
|
||||
list.add('c').tail();
|
||||
|
||||
// "a" <-> "b" <-> "c"
|
||||
|
||||
list.add('x').before('b');
|
||||
|
||||
// "a" <-> "x" <-> "b" <-> "c"
|
||||
|
||||
expect(list.length).toBe(4);
|
||||
expect(list.head.value).toBe('a');
|
||||
expect(list.head.next.value).toBe('x');
|
||||
expect(list.head.next.next.value).toBe('b');
|
||||
expect(list.head.next.next.next.value).toBe('c');
|
||||
expect(list.tail.value).toBe('c');
|
||||
expect(list.tail.previous.value).toBe('b');
|
||||
expect(list.tail.previous.previous.value).toBe('x');
|
||||
expect(list.tail.previous.previous.previous.value).toBe('a');
|
||||
});
|
||||
|
||||
it('should be able to receive a custom compareFn', () => {
|
||||
list.add({ x: 1 }).tail();
|
||||
list.add({ x: 2 }).tail();
|
||||
list.add({ x: 3 }).tail();
|
||||
|
||||
// {"x":1} <-> {"x":2} <-> {"x":3}
|
||||
|
||||
list.add({ x: 0 }).before({ x: 2 }, (v1: X, v2: X) => v1.x === v2.x);
|
||||
|
||||
// {"x":1} <-> {"x":0} <-> {"x":2} <-> {"x":3}
|
||||
|
||||
expect(list.length).toBe(4);
|
||||
expect(list.head.value.x).toBe(1);
|
||||
expect(list.head.next.value.x).toBe(0);
|
||||
expect(list.head.next.next.value.x).toBe(2);
|
||||
expect(list.head.next.next.next.value.x).toBe(3);
|
||||
expect(list.tail.value.x).toBe(3);
|
||||
expect(list.tail.previous.value.x).toBe(2);
|
||||
expect(list.tail.previous.previous.value.x).toBe(0);
|
||||
expect(list.tail.previous.previous.previous.value.x).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#byIndex', () => {
|
||||
it('should place a node at given index', () => {
|
||||
list.add('a').tail();
|
||||
list.add('b').tail();
|
||||
list.add('c').tail();
|
||||
|
||||
// "a" <-> "b" <-> "c"
|
||||
|
||||
list.add('x').byIndex(1);
|
||||
|
||||
// "a" <-> "x" <-> "b" <-> "c"
|
||||
|
||||
list.add('y').byIndex(3);
|
||||
|
||||
// "a" <-> "x" <-> "b" <-> "y" <-> "c"
|
||||
|
||||
expect(list.length).toBe(5);
|
||||
expect(list.head.value).toBe('a');
|
||||
expect(list.head.next.value).toBe('x');
|
||||
expect(list.head.next.next.value).toBe('b');
|
||||
expect(list.head.next.next.next.value).toBe('y');
|
||||
expect(list.head.next.next.next.next.value).toBe('c');
|
||||
expect(list.tail.value).toBe('c');
|
||||
expect(list.tail.previous.value).toBe('y');
|
||||
expect(list.tail.previous.previous.value).toBe('b');
|
||||
expect(list.tail.previous.previous.previous.value).toBe('x');
|
||||
expect(list.tail.previous.previous.previous.previous.value).toBe('a');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#find', () => {
|
||||
it('should return the first node found based on given predicate', () => {
|
||||
list.add('a').tail();
|
||||
list.add('x').tail();
|
||||
list.add('b').tail();
|
||||
list.add('x').tail();
|
||||
list.add('c').tail();
|
||||
|
||||
// "a" <-> "x" <-> "b" <-> "x" <-> "c"
|
||||
|
||||
const node1 = list.find(node => node.previous && node.previous.value === 'a');
|
||||
|
||||
expect(node1.value).toBe('x');
|
||||
expect(node1.previous.value).toBe('a');
|
||||
expect(node1.next.value).toBe('b');
|
||||
|
||||
// "a" <-> "x" <-> "b" <-> "x" <-> "c"
|
||||
|
||||
const node2 = list.find(node => node.next && node.next.value === 'c');
|
||||
|
||||
expect(node2.value).toBe('x');
|
||||
expect(node2.previous.value).toBe('b');
|
||||
expect(node2.next.value).toBe('c');
|
||||
});
|
||||
|
||||
it('should return undefined when list is empty', () => {
|
||||
const found = list.find(node => node.value === 'x');
|
||||
|
||||
expect(found).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return undefined when predicate finds no match', () => {
|
||||
list.add('a').tail();
|
||||
list.add('b').tail();
|
||||
list.add('c').tail();
|
||||
|
||||
// "a" <-> "b" <-> "c"
|
||||
|
||||
const found = list.find(node => node.value === 'x');
|
||||
|
||||
expect(found).toBeUndefined();
|
||||
expect(list.length).toBe(3);
|
||||
expect(list.head.value).toBe('a');
|
||||
expect(list.head.next.value).toBe('b');
|
||||
expect(list.head.next.next.value).toBe('c');
|
||||
expect(list.tail.value).toBe('c');
|
||||
expect(list.tail.previous.value).toBe('b');
|
||||
expect(list.tail.previous.previous.value).toBe('a');
|
||||
});
|
||||
});
|
||||
|
||||
describe('#findIndex', () => {
|
||||
it('should return the index of the first node found based on given predicate', () => {
|
||||
list.add('a').tail();
|
||||
list.add('x').tail();
|
||||
list.add('b').tail();
|
||||
list.add('x').tail();
|
||||
list.add('c').tail();
|
||||
|
||||
// "a" <-> "x" <-> "b" <-> "x" <-> "c"
|
||||
|
||||
const index1 = list.findIndex(node => node.previous && node.previous.value === 'a');
|
||||
|
||||
expect(index1).toBe(1);
|
||||
|
||||
// "a" <-> "x" <-> "b" <-> "x" <-> "c"
|
||||
|
||||
const index2 = list.findIndex(node => node.next && node.next.value === 'c');
|
||||
|
||||
expect(index2).toBe(3);
|
||||
});
|
||||
|
||||
it('should return -1 when list is empty', () => {
|
||||
const index = list.findIndex(node => node.value === 'x');
|
||||
|
||||
expect(index).toBe(-1);
|
||||
});
|
||||
|
||||
it('should return -1 when no match is found', () => {
|
||||
list.add('a').tail();
|
||||
list.add('b').tail();
|
||||
list.add('c').tail();
|
||||
|
||||
// "a" <-> "b" <-> "c"
|
||||
|
||||
const index = list.findIndex(node => node.value === 'x');
|
||||
|
||||
expect(index).toBe(-1);
|
||||
expect(list.length).toBe(3);
|
||||
expect(list.head.value).toBe('a');
|
||||
expect(list.head.next.value).toBe('b');
|
||||
expect(list.head.next.next.value).toBe('c');
|
||||
expect(list.tail.value).toBe('c');
|
||||
expect(list.tail.previous.value).toBe('b');
|
||||
expect(list.tail.previous.previous.value).toBe('a');
|
||||
});
|
||||
});
|
||||
|
||||
describe('#forEach', () => {
|
||||
it('should call given function for each node of the list', () => {
|
||||
const a = list.add('a').tail();
|
||||
const b = list.add('b').tail();
|
||||
const c = list.add('c').tail();
|
||||
|
||||
// "a" <-> "b" <-> "c"
|
||||
|
||||
const spy = jest.fn();
|
||||
list.forEach(spy);
|
||||
|
||||
expect(spy.mock.calls).toEqual([
|
||||
[a, 0, list],
|
||||
[b, 1, list],
|
||||
[c, 2, list],
|
||||
]);
|
||||
});
|
||||
|
||||
it('should not call given function when list is empty', () => {
|
||||
const spy = jest.fn();
|
||||
list.forEach(spy);
|
||||
|
||||
expect(spy).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('#drop', () => {
|
||||
describe('#head', () => {
|
||||
it('should return undefined when there is no head', () => {
|
||||
expect(list.drop().head()).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should remove the node from the head of the list', () => {
|
||||
list.add('a').tail();
|
||||
list.add('b').tail();
|
||||
list.add('c').tail();
|
||||
|
||||
// "a" <-> "b" <-> "c"
|
||||
|
||||
list.drop().head();
|
||||
|
||||
// "b" <-> "c"
|
||||
|
||||
expect(list.length).toBe(2);
|
||||
expect(list.head.value).toBe('b');
|
||||
expect(list.head.next.value).toBe('c');
|
||||
expect(list.head.previous).toBeUndefined();
|
||||
expect(list.tail.value).toBe('c');
|
||||
expect(list.tail.previous.value).toBe('b');
|
||||
expect(list.tail.next).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('#head', () => {
|
||||
it('should return undefined when there is no tail', () => {
|
||||
expect(list.drop().tail()).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should remove the node from the tail of the list', () => {
|
||||
list.add('a').tail();
|
||||
list.add('b').tail();
|
||||
list.add('c').tail();
|
||||
|
||||
// "a" <-> "b" <-> "c"
|
||||
|
||||
list.drop().tail();
|
||||
|
||||
// "a" <-> "b"
|
||||
|
||||
expect(list.length).toBe(2);
|
||||
expect(list.head.value).toBe('a');
|
||||
expect(list.head.next.value).toBe('b');
|
||||
expect(list.head.previous).toBeUndefined();
|
||||
expect(list.tail.value).toBe('b');
|
||||
expect(list.tail.previous.value).toBe('a');
|
||||
expect(list.tail.next).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('#byIndex', () => {
|
||||
it('should remove the node at given index', () => {
|
||||
list.add('a').tail();
|
||||
list.add('b').tail();
|
||||
list.add('c').tail();
|
||||
list.add('d').tail();
|
||||
list.add('e').tail();
|
||||
|
||||
// "a" <-> "b" <-> "c" <-> "d" <-> "e"
|
||||
|
||||
list.drop().byIndex(1);
|
||||
|
||||
// "a" <-> "c" <-> "d" <-> "e"
|
||||
|
||||
list.drop().byIndex(2);
|
||||
|
||||
// "a" <-> "c" <-> "e"
|
||||
|
||||
expect(list.length).toBe(3);
|
||||
expect(list.head.value).toBe('a');
|
||||
expect(list.head.next.value).toBe('c');
|
||||
expect(list.head.next.next.value).toBe('e');
|
||||
expect(list.tail.value).toBe('e');
|
||||
expect(list.tail.previous.value).toBe('c');
|
||||
expect(list.tail.previous.previous.value).toBe('a');
|
||||
});
|
||||
|
||||
it('should return undefined when list is empty', () => {
|
||||
const node = list.drop().byIndex(0);
|
||||
expect(node).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return undefined when given index does not exist', () => {
|
||||
list.add('a').tail();
|
||||
list.add('b').tail();
|
||||
list.add('c').tail();
|
||||
|
||||
// "a" <-> "b" <-> "c"
|
||||
|
||||
const node1 = list.drop().byIndex(4);
|
||||
|
||||
// "a" <-> "b" <-> "c"
|
||||
|
||||
expect(node1).toBeUndefined();
|
||||
expect(list.length).toBe(3);
|
||||
expect(list.head.value).toBe('a');
|
||||
expect(list.head.next.value).toBe('b');
|
||||
expect(list.head.next.next.value).toBe('c');
|
||||
expect(list.tail.value).toBe('c');
|
||||
expect(list.tail.previous.value).toBe('b');
|
||||
expect(list.tail.previous.previous.value).toBe('a');
|
||||
|
||||
// "a" <-> "b" <-> "c"
|
||||
|
||||
const node2 = list.drop().byIndex(-1);
|
||||
|
||||
// "a" <-> "b" <-> "c"
|
||||
|
||||
expect(node2).toBeUndefined();
|
||||
expect(list.length).toBe(3);
|
||||
expect(list.head.value).toBe('a');
|
||||
expect(list.head.next.value).toBe('b');
|
||||
expect(list.head.next.next.value).toBe('c');
|
||||
expect(list.tail.value).toBe('c');
|
||||
expect(list.tail.previous.value).toBe('b');
|
||||
expect(list.tail.previous.previous.value).toBe('a');
|
||||
});
|
||||
});
|
||||
|
||||
describe('#byValue', () => {
|
||||
it('should remove the first node with given value', () => {
|
||||
list.add('a').tail();
|
||||
list.add('x').tail();
|
||||
list.add('b').tail();
|
||||
list.add('x').tail();
|
||||
list.add('c').tail();
|
||||
|
||||
// "a" <-> "x" <-> "b" <-> "x" <-> "c"
|
||||
|
||||
list.drop().byValue('x');
|
||||
|
||||
// "a" <-> "b" <-> "x" <-> "c"
|
||||
|
||||
expect(list.length).toBe(4);
|
||||
expect(list.head.value).toBe('a');
|
||||
expect(list.head.next.value).toBe('b');
|
||||
expect(list.head.next.next.value).toBe('x');
|
||||
expect(list.head.next.next.next.value).toBe('c');
|
||||
expect(list.tail.value).toBe('c');
|
||||
expect(list.tail.previous.value).toBe('x');
|
||||
expect(list.tail.previous.previous.value).toBe('b');
|
||||
expect(list.tail.previous.previous.previous.value).toBe('a');
|
||||
|
||||
// "a" <-> "b" <-> "x" <-> "c"
|
||||
|
||||
list.drop().byValue('x');
|
||||
|
||||
// "a" <-> "b" <-> "c"
|
||||
|
||||
expect(list.length).toBe(3);
|
||||
expect(list.head.value).toBe('a');
|
||||
expect(list.head.next.value).toBe('b');
|
||||
expect(list.head.next.next.value).toBe('c');
|
||||
expect(list.tail.value).toBe('c');
|
||||
expect(list.tail.previous.value).toBe('b');
|
||||
expect(list.tail.previous.previous.value).toBe('a');
|
||||
});
|
||||
|
||||
it('should be able to receive a custom compareFn', () => {
|
||||
list.add({ x: 1 }).tail();
|
||||
list.add({ x: 2 }).tail();
|
||||
list.add({ x: 3 }).tail();
|
||||
|
||||
// {"x":1} <-> {"x":2} <-> {"x":3}
|
||||
|
||||
list.drop().byValue({ x: 2 }, (v1: X, v2: X) => v1.x === v2.x);
|
||||
|
||||
// {"x":1} <-> {"x":3}
|
||||
|
||||
expect(list.length).toBe(2);
|
||||
expect(list.head.value.x).toBe(1);
|
||||
expect(list.head.next.value.x).toBe(3);
|
||||
expect(list.tail.value.x).toBe(3);
|
||||
expect(list.tail.previous.value.x).toBe(1);
|
||||
});
|
||||
|
||||
it('should return undefined when list is empty', () => {
|
||||
const node = list.drop().byValue('x');
|
||||
expect(node).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return undefined when given value is not found', () => {
|
||||
list.add('a').tail();
|
||||
list.add('b').tail();
|
||||
list.add('c').tail();
|
||||
|
||||
// "a" <-> "b" <-> "c"
|
||||
|
||||
const node = list.drop().byValue('x');
|
||||
|
||||
// "a" <-> "b" <-> "c"
|
||||
|
||||
expect(node).toBeUndefined();
|
||||
expect(list.length).toBe(3);
|
||||
expect(list.head.value).toBe('a');
|
||||
expect(list.head.next.value).toBe('b');
|
||||
expect(list.head.next.next.value).toBe('c');
|
||||
expect(list.tail.value).toBe('c');
|
||||
expect(list.tail.previous.value).toBe('b');
|
||||
expect(list.tail.previous.previous.value).toBe('a');
|
||||
});
|
||||
});
|
||||
|
||||
describe('#byValueAll', () => {
|
||||
it('should remove all nodes with given value', () => {
|
||||
list.add('a').tail();
|
||||
list.add('x').tail();
|
||||
list.add('b').tail();
|
||||
list.add('x').tail();
|
||||
list.add('c').tail();
|
||||
|
||||
// "a" <-> "x" <-> "b" <-> "x" <-> "c"
|
||||
|
||||
const dropped = list.drop().byValueAll('x');
|
||||
|
||||
// "a" <-> "b" <-> "c"
|
||||
|
||||
expect(dropped.length).toBe(2);
|
||||
expect(dropped[0].value).toEqual('x');
|
||||
expect(dropped[0].previous.value).toEqual('a');
|
||||
expect(dropped[0].next.value).toEqual('b');
|
||||
expect(dropped[1].value).toEqual('x');
|
||||
expect(dropped[1].previous.value).toEqual('b');
|
||||
expect(dropped[1].next.value).toEqual('c');
|
||||
|
||||
expect(list.length).toBe(3);
|
||||
expect(list.head.value).toBe('a');
|
||||
expect(list.head.next.value).toBe('b');
|
||||
expect(list.head.next.next.value).toBe('c');
|
||||
expect(list.tail.value).toBe('c');
|
||||
expect(list.tail.previous.value).toBe('b');
|
||||
expect(list.tail.previous.previous.value).toBe('a');
|
||||
});
|
||||
|
||||
it('should be able to receive a custom compareFn', () => {
|
||||
list.add({ x: 1 }).tail();
|
||||
list.add({ x: 0 }).tail();
|
||||
list.add({ x: 2 }).tail();
|
||||
list.add({ x: 0 }).tail();
|
||||
list.add({ x: 3 }).tail();
|
||||
|
||||
// {"x":1} <-> {"x":0} <-> {"x":2} <-> {"x":0} <-> {"x":3}
|
||||
|
||||
const dropped = list.drop().byValueAll({ x: 0 }, (v1: X, v2: X) => v1.x === v2.x);
|
||||
|
||||
// {"x":1} <-> {"x":2} <-> {"x":3}
|
||||
|
||||
expect(dropped.length).toBe(2);
|
||||
expect(dropped[0].value.x).toEqual(0);
|
||||
expect(dropped[0].previous.value.x).toEqual(1);
|
||||
expect(dropped[0].next.value.x).toEqual(2);
|
||||
expect(dropped[1].value.x).toEqual(0);
|
||||
expect(dropped[1].previous.value.x).toEqual(2);
|
||||
expect(dropped[1].next.value.x).toEqual(3);
|
||||
|
||||
expect(list.length).toBe(3);
|
||||
expect(list.head.value.x).toBe(1);
|
||||
expect(list.head.next.value.x).toBe(2);
|
||||
expect(list.head.next.next.value.x).toBe(3);
|
||||
expect(list.tail.value.x).toBe(3);
|
||||
expect(list.tail.previous.value.x).toBe(2);
|
||||
expect(list.tail.previous.previous.value.x).toBe(1);
|
||||
});
|
||||
|
||||
it('should return empty array when list is empty', () => {
|
||||
const dropped = list.drop().byValueAll('x');
|
||||
expect(dropped).toEqual([]);
|
||||
});
|
||||
|
||||
it('should return empty array when given value is not found', () => {
|
||||
list.add('a').tail();
|
||||
list.add('b').tail();
|
||||
list.add('c').tail();
|
||||
|
||||
// "a" <-> "b" <-> "c"
|
||||
|
||||
const dropped = list.drop().byValueAll('x');
|
||||
|
||||
// "a" <-> "b" <-> "c"
|
||||
|
||||
expect(dropped).toEqual([]);
|
||||
|
||||
expect(list.length).toBe(3);
|
||||
expect(list.head.value).toBe('a');
|
||||
expect(list.head.next.value).toBe('b');
|
||||
expect(list.head.next.next.value).toBe('c');
|
||||
expect(list.tail.value).toBe('c');
|
||||
expect(list.tail.previous.value).toBe('b');
|
||||
expect(list.tail.previous.previous.value).toBe('a');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#get', () => {
|
||||
it('should return node at given index', () => {
|
||||
list.add('a').tail();
|
||||
list.add('b').tail();
|
||||
list.add('c').tail();
|
||||
|
||||
// "a" <-> "b" <-> "c"
|
||||
|
||||
const node = list.get(1);
|
||||
|
||||
expect(node.value).toBe('b');
|
||||
expect(node.previous.value).toBe('a');
|
||||
expect(node.next.value).toBe('c');
|
||||
});
|
||||
|
||||
it('should return undefined when list is empty', () => {
|
||||
const node = list.get(1);
|
||||
|
||||
expect(node).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return undefined when predicate finds no match', () => {
|
||||
list.add('a').tail();
|
||||
list.add('b').tail();
|
||||
list.add('c').tail();
|
||||
|
||||
// "a" <-> "b" <-> "c"
|
||||
|
||||
const node1 = list.get(4);
|
||||
|
||||
expect(node1).toBeUndefined();
|
||||
expect(list.length).toBe(3);
|
||||
expect(list.head.value).toBe('a');
|
||||
expect(list.head.next.value).toBe('b');
|
||||
expect(list.head.next.next.value).toBe('c');
|
||||
expect(list.tail.value).toBe('c');
|
||||
expect(list.tail.previous.value).toBe('b');
|
||||
expect(list.tail.previous.previous.value).toBe('a');
|
||||
|
||||
// "a" <-> "b" <-> "c"
|
||||
|
||||
const node2 = list.get(-1);
|
||||
|
||||
expect(node2).toBeUndefined();
|
||||
expect(list.length).toBe(3);
|
||||
expect(list.head.value).toBe('a');
|
||||
expect(list.head.next.value).toBe('b');
|
||||
expect(list.head.next.next.value).toBe('c');
|
||||
expect(list.tail.value).toBe('c');
|
||||
expect(list.tail.previous.value).toBe('b');
|
||||
expect(list.tail.previous.previous.value).toBe('a');
|
||||
});
|
||||
});
|
||||
|
||||
describe('#indexOf', () => {
|
||||
it('should return the index of the first node found based on given value', () => {
|
||||
list.add('a').tail();
|
||||
list.add('x').tail();
|
||||
list.add('b').tail();
|
||||
list.add('x').tail();
|
||||
list.add('c').tail();
|
||||
|
||||
// "a" <-> "x" <-> "b" <-> "x" <-> "c"
|
||||
|
||||
const index1 = list.indexOf('x');
|
||||
|
||||
expect(index1).toBe(1);
|
||||
|
||||
// "a" <-> "x" <-> "b" <-> "x" <-> "c"
|
||||
|
||||
let timesFound = 0;
|
||||
const index2 = list.indexOf('x', (v1: string, v2: string) => {
|
||||
if (timesFound > 1) return false;
|
||||
|
||||
timesFound += Number(v1 === v2);
|
||||
|
||||
return timesFound > 1;
|
||||
});
|
||||
|
||||
expect(index2).toBe(3);
|
||||
});
|
||||
|
||||
it('should be able to receive a custom compareFn', () => {
|
||||
list.add({ x: 1 }).tail();
|
||||
list.add({ x: 2 }).tail();
|
||||
list.add({ x: 3 }).tail();
|
||||
|
||||
// {"x":1} <-> {"x":2} <-> {"x":3}
|
||||
|
||||
const index = list.indexOf({ x: 2 }, (v1: X, v2: X) => v1.x === v2.x);
|
||||
|
||||
expect(index).toBe(1);
|
||||
});
|
||||
|
||||
it('should return -1 when list is empty', () => {
|
||||
const index = list.indexOf('x');
|
||||
|
||||
expect(index).toBe(-1);
|
||||
});
|
||||
|
||||
it('should return -1 when no match is found', () => {
|
||||
list.add('a').tail();
|
||||
list.add('b').tail();
|
||||
list.add('c').tail();
|
||||
|
||||
// "a" <-> "b" <-> "c"
|
||||
|
||||
const index = list.indexOf('x');
|
||||
|
||||
expect(index).toBe(-1);
|
||||
expect(list.length).toBe(3);
|
||||
expect(list.head.value).toBe('a');
|
||||
expect(list.head.next.value).toBe('b');
|
||||
expect(list.head.next.next.value).toBe('c');
|
||||
expect(list.tail.value).toBe('c');
|
||||
expect(list.tail.previous.value).toBe('b');
|
||||
expect(list.tail.previous.previous.value).toBe('a');
|
||||
});
|
||||
});
|
||||
|
||||
describe('#toArray', () => {
|
||||
it('should return array representation', () => {
|
||||
list.addTail('a');
|
||||
list.addTail(2);
|
||||
list.addTail('c');
|
||||
list.addTail({ k: 4, v: 'd' });
|
||||
|
||||
// "a" <-> 2 <-> "c" <-> {"k":4,"v":"d"}
|
||||
|
||||
const arr = list.toArray();
|
||||
expect(arr).toEqual(['a', 2, 'c', { k: 4, v: 'd' }]);
|
||||
});
|
||||
|
||||
it('should return empty array when list is empty', () => {
|
||||
const arr = list.toArray();
|
||||
expect(arr).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#toString', () => {
|
||||
it('should return string representation', () => {
|
||||
list.addTail('a');
|
||||
list.addTail(2);
|
||||
list.addTail('c');
|
||||
list.addTail({ k: 4, v: 'd' });
|
||||
|
||||
// "a" <-> 2 <-> "c" <-> {"k":4,"v":"d"}
|
||||
|
||||
const str = list.toString();
|
||||
expect(str).toBe('"a" <-> 2 <-> "c" <-> {"k":4,"v":"d"}');
|
||||
});
|
||||
|
||||
it('should return empty string when list is empty', () => {
|
||||
const str = list.toString();
|
||||
expect(str).toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
it('should be iterable', () => {
|
||||
list.addTail('a');
|
||||
list.addTail('b');
|
||||
list.addTail('c');
|
||||
|
||||
// "a" <-> "b" <-> "c"
|
||||
|
||||
const arr = [];
|
||||
|
||||
for (const value of list) {
|
||||
arr.push(value);
|
||||
}
|
||||
|
||||
expect(arr).toEqual(['a', 'b', 'c']);
|
||||
});
|
||||
});
|
||||
|
||||
interface X {
|
||||
[k: string]: any;
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
export * from './common-utils';
|
||||
export * from './generator-utils';
|
||||
export * from './initial-utils';
|
||||
export * from './linked-list';
|
||||
export * from './route-utils';
|
||||
export * from './rxjs-utils';
|
||||
|
@ -0,0 +1,259 @@
|
||||
/* tslint:disable:no-non-null-assertion */
|
||||
|
||||
import compare from 'just-compare';
|
||||
|
||||
export class ListNode<T = any> {
|
||||
readonly value: T;
|
||||
next: ListNode | undefined;
|
||||
previous: ListNode | undefined;
|
||||
|
||||
constructor(value: T) {
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
export class LinkedList<T = any> {
|
||||
private first: ListNode<T> | undefined;
|
||||
private last: ListNode<T> | undefined;
|
||||
private size = 0;
|
||||
|
||||
get head(): ListNode<T> | undefined {
|
||||
return this.first;
|
||||
}
|
||||
get tail(): ListNode<T> | undefined {
|
||||
return this.last;
|
||||
}
|
||||
get length(): number {
|
||||
return this.size;
|
||||
}
|
||||
|
||||
private linkWith(
|
||||
value: T,
|
||||
previousNode: ListNode<T> | undefined,
|
||||
nextNode: ListNode<T> | undefined,
|
||||
): ListNode<T> {
|
||||
const node = new ListNode(value);
|
||||
|
||||
if (!previousNode) return this.addHead(value);
|
||||
if (!nextNode) return this.addTail(value);
|
||||
|
||||
node.previous = previousNode;
|
||||
previousNode.next = node;
|
||||
node.next = nextNode;
|
||||
nextNode.previous = node;
|
||||
|
||||
this.size += 1;
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
add(value: T) {
|
||||
return {
|
||||
after: (previousValue: T, compareFn = compare) => {
|
||||
return this.addAfter(value, previousValue, compareFn);
|
||||
},
|
||||
before: (nextValue: T, compareFn = compare) => {
|
||||
return this.addBefore(value, nextValue, compareFn);
|
||||
},
|
||||
byIndex: (position: number): ListNode<T> => {
|
||||
return this.addByIndex(value, position);
|
||||
},
|
||||
head: (): ListNode<T> => {
|
||||
return this.addHead(value);
|
||||
},
|
||||
tail: (): ListNode<T> => {
|
||||
return this.addTail(value);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
addAfter(value: T, previousValue: T, compareFn = compare): ListNode<T> {
|
||||
const previous = this.find(node => compareFn(node.value, previousValue));
|
||||
|
||||
return previous ? this.linkWith(value, previous, previous.next) : this.addTail(value);
|
||||
}
|
||||
|
||||
addBefore(value: T, nextValue: T, compareFn = compare): ListNode<T> {
|
||||
const next = this.find(node => compareFn(node.value, nextValue));
|
||||
|
||||
return next ? this.linkWith(value, next.previous, next) : this.addHead(value);
|
||||
}
|
||||
|
||||
addByIndex(value: T, position: number): ListNode<T> {
|
||||
if (position <= 0) return this.addHead(value);
|
||||
if (position >= this.size) return this.addTail(value);
|
||||
|
||||
const next = this.get(position)!;
|
||||
|
||||
return this.linkWith(value, next.previous, next);
|
||||
}
|
||||
|
||||
addHead(value: T): ListNode<T> {
|
||||
const node = new ListNode(value);
|
||||
|
||||
node.next = this.first;
|
||||
|
||||
if (this.first) this.first.previous = node;
|
||||
else this.last = node;
|
||||
|
||||
this.first = node;
|
||||
this.size += 1;
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
addTail(value: T): ListNode<T> {
|
||||
const node = new ListNode(value);
|
||||
|
||||
if (this.first) {
|
||||
node.previous = this.last;
|
||||
this.last!.next = node;
|
||||
this.last = node;
|
||||
} else {
|
||||
this.first = node;
|
||||
this.last = node;
|
||||
}
|
||||
|
||||
this.size += 1;
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
drop() {
|
||||
return {
|
||||
byIndex: (position: number) => this.dropByIndex(position),
|
||||
byValue: (value: T, compareFn = compare) => this.dropByValue(value, compareFn),
|
||||
byValueAll: (value: T, compareFn = compare) => this.dropByValueAll(value, compareFn),
|
||||
head: () => this.dropHead(),
|
||||
tail: () => this.dropTail(),
|
||||
};
|
||||
}
|
||||
|
||||
dropByIndex(position: number): ListNode<T> | undefined {
|
||||
if (position === 0) return this.dropHead();
|
||||
else if (position === this.size - 1) return this.dropTail();
|
||||
|
||||
const current = this.get(position);
|
||||
|
||||
if (current) {
|
||||
current.previous!.next = current.next;
|
||||
current.next!.previous = current.previous;
|
||||
|
||||
this.size -= 1;
|
||||
|
||||
return current;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
dropByValue(value: T, compareFn = compare): ListNode<T> | undefined {
|
||||
const position = this.findIndex(node => compareFn(node.value, value));
|
||||
|
||||
if (position < 0) return undefined;
|
||||
|
||||
return this.dropByIndex(position);
|
||||
}
|
||||
|
||||
dropByValueAll(value: T, compareFn = compare): ListNode<T>[] {
|
||||
const dropped: ListNode<T>[] = [];
|
||||
|
||||
for (let current = this.first, position = 0; current; position += 1, current = current.next) {
|
||||
if (compareFn(current.value, value)) {
|
||||
dropped.push(this.dropByIndex(position - dropped.length)!);
|
||||
}
|
||||
}
|
||||
|
||||
return dropped;
|
||||
}
|
||||
|
||||
dropHead(): ListNode<T> | undefined {
|
||||
const head = this.first;
|
||||
|
||||
if (head) {
|
||||
this.first = head.next;
|
||||
|
||||
if (this.first) this.first.previous = undefined;
|
||||
else this.last = undefined;
|
||||
|
||||
this.size -= 1;
|
||||
|
||||
return head;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
dropTail(): ListNode<T> | undefined {
|
||||
const tail = this.last;
|
||||
|
||||
if (tail) {
|
||||
this.last = tail.previous;
|
||||
|
||||
if (this.last) this.last.next = undefined;
|
||||
else this.first = undefined;
|
||||
|
||||
this.size -= 1;
|
||||
|
||||
return tail;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
find(predicate: ListIteratorFunction<T>): ListNode<T> | undefined {
|
||||
for (let current = this.first, position = 0; current; position += 1, current = current.next) {
|
||||
if (predicate(current, position, this)) return current;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
findIndex(predicate: ListIteratorFunction<T>): number {
|
||||
for (let current = this.first, position = 0; current; position += 1, current = current.next) {
|
||||
if (predicate(current, position, this)) return position;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
forEach<R = boolean>(callback: ListIteratorFunction<T, R>) {
|
||||
for (let node = this.first, position = 0; node; position += 1, node = node.next) {
|
||||
callback(node, position, this);
|
||||
}
|
||||
}
|
||||
|
||||
get(position: number): ListNode<T> | undefined {
|
||||
return this.find((_, index) => position === index);
|
||||
}
|
||||
|
||||
indexOf(value: T, compareFn = compare): number {
|
||||
return this.findIndex(node => compareFn(node.value, value));
|
||||
}
|
||||
|
||||
toArray(): T[] {
|
||||
const array = new Array(this.size);
|
||||
|
||||
this.forEach((node, index) => (array[index!] = node.value));
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
return this.toArray()
|
||||
.map(value => JSON.stringify(value))
|
||||
.join(' <-> ');
|
||||
}
|
||||
|
||||
*[Symbol.iterator]() {
|
||||
for (let node = this.first, position = 0; node; position += 1, node = node.next) {
|
||||
yield node.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export type ListIteratorFunction<T = any, R = boolean> = (
|
||||
node: ListNode<T>,
|
||||
index?: number,
|
||||
list?: LinkedList,
|
||||
) => R;
|
Loading…
Reference in new issue