mirror of https://github.com/abpframework/abp.git
49 changed files with 2810 additions and 295 deletions
@ -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"}' |
||||
|
*/ |
||||
|
``` |
||||
|
|
||||
|
|
||||
|
|
||||
@ -0,0 +1,14 @@ |
|||||
|
using System.Collections.Generic; |
||||
|
|
||||
|
namespace Volo.Abp.EntityFrameworkCore.Extensions |
||||
|
{ |
||||
|
public class EntityExtensionInfo |
||||
|
{ |
||||
|
public Dictionary<string, PropertyExtensionInfo> Properties { get; set; } |
||||
|
|
||||
|
public EntityExtensionInfo() |
||||
|
{ |
||||
|
Properties = new Dictionary<string, PropertyExtensionInfo>(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,133 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using JetBrains.Annotations; |
||||
|
using Microsoft.EntityFrameworkCore.Metadata.Builders; |
||||
|
using Volo.Abp.Data; |
||||
|
|
||||
|
namespace Volo.Abp.EntityFrameworkCore.Extensions |
||||
|
{ |
||||
|
public static class EntityExtensionManager |
||||
|
{ |
||||
|
private static readonly Dictionary<Type, EntityExtensionInfo> ExtensionInfos; |
||||
|
|
||||
|
static EntityExtensionManager() |
||||
|
{ |
||||
|
ExtensionInfos = new Dictionary<Type, EntityExtensionInfo>(); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Adds an extension property for an entity.
|
||||
|
/// If it is already added, replaces the <paramref name="propertyBuildAction"/>
|
||||
|
/// by the given one!
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TEntity">Type of the entity</typeparam>
|
||||
|
/// <typeparam name="TProperty">Type of the new property</typeparam>
|
||||
|
/// <param name="propertyName">Name of the property</param>
|
||||
|
/// <param name="propertyBuildAction">An action to configure the database mapping for the new property</param>
|
||||
|
public static void AddProperty<TEntity, TProperty>( |
||||
|
[NotNull]string propertyName, |
||||
|
[NotNull]Action<PropertyBuilder> propertyBuildAction) |
||||
|
{ |
||||
|
AddProperty( |
||||
|
typeof(TEntity), |
||||
|
typeof(TProperty), |
||||
|
propertyName, |
||||
|
propertyBuildAction |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Adds an extension property for an entity.
|
||||
|
/// If it is already added, replaces the <paramref name="propertyBuildAction"/>
|
||||
|
/// by the given one!
|
||||
|
/// </summary>
|
||||
|
/// <param name="entityType">Type of the entity</param>
|
||||
|
/// <param name="propertyType">Type of the new property</param>
|
||||
|
/// <param name="propertyName">Name of the property</param>
|
||||
|
/// <param name="propertyBuildAction">An action to configure the database mapping for the new property</param>
|
||||
|
public static void AddProperty( |
||||
|
Type entityType, |
||||
|
Type propertyType, |
||||
|
[NotNull]string propertyName, |
||||
|
[NotNull]Action<PropertyBuilder> propertyBuildAction) |
||||
|
{ |
||||
|
Check.NotNull(entityType, nameof(entityType)); |
||||
|
Check.NotNull(propertyType, nameof(propertyType)); |
||||
|
Check.NotNullOrWhiteSpace(propertyName, nameof(propertyName)); |
||||
|
Check.NotNull(propertyBuildAction, nameof(propertyBuildAction)); |
||||
|
|
||||
|
var extensionInfo = ExtensionInfos |
||||
|
.GetOrAdd(entityType, () => new EntityExtensionInfo()); |
||||
|
|
||||
|
var propertyExtensionInfo = extensionInfo.Properties |
||||
|
.GetOrAdd(propertyName, () => new PropertyExtensionInfo(propertyType)); |
||||
|
|
||||
|
propertyExtensionInfo.Action = propertyBuildAction; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Configures the entity mapping for the defined extensions.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TEntity">The entity tye</typeparam>
|
||||
|
/// <param name="entityTypeBuilder">Entity type builder</param>
|
||||
|
public static void ConfigureExtensions<TEntity>( |
||||
|
[NotNull] this EntityTypeBuilder<TEntity> entityTypeBuilder) |
||||
|
where TEntity : class, IHasExtraProperties |
||||
|
{ |
||||
|
ConfigureExtensions(typeof(TEntity), entityTypeBuilder); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Configures the entity mapping for the defined extensions.
|
||||
|
/// </summary>
|
||||
|
/// <param name="entityType">Type of the entity</param>
|
||||
|
/// <param name="entityTypeBuilder">Entity type builder</param>
|
||||
|
public static void ConfigureExtensions( |
||||
|
[NotNull] Type entityType, |
||||
|
[NotNull] EntityTypeBuilder entityTypeBuilder) |
||||
|
{ |
||||
|
Check.NotNull(entityType, nameof(entityType)); |
||||
|
Check.NotNull(entityTypeBuilder, nameof(entityTypeBuilder)); |
||||
|
|
||||
|
var entityExtensionInfo = ExtensionInfos.GetOrDefault(entityType); |
||||
|
if (entityExtensionInfo == null) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
foreach (var propertyExtensionInfo in entityExtensionInfo.Properties) |
||||
|
{ |
||||
|
var propertyName = propertyExtensionInfo.Key; |
||||
|
var propertyType = propertyExtensionInfo.Value.PropertyType; |
||||
|
|
||||
|
/* Prevent multiple calls to the entityTypeBuilder.Property(...) method */ |
||||
|
if (entityTypeBuilder.Metadata.FindProperty(propertyName) != null) |
||||
|
{ |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
var property = entityTypeBuilder.Property( |
||||
|
propertyType, |
||||
|
propertyName |
||||
|
); |
||||
|
|
||||
|
propertyExtensionInfo.Value.Action(property); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public static string[] GetPropertyNames(Type entityType) |
||||
|
{ |
||||
|
var entityExtensionInfo = ExtensionInfos.GetOrDefault(entityType); |
||||
|
if (entityExtensionInfo == null) |
||||
|
{ |
||||
|
return Array.Empty<string>(); |
||||
|
} |
||||
|
|
||||
|
return entityExtensionInfo |
||||
|
.Properties |
||||
|
.Select(p => p.Key) |
||||
|
.ToArray(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,17 @@ |
|||||
|
using System; |
||||
|
using Microsoft.EntityFrameworkCore.Metadata.Builders; |
||||
|
|
||||
|
namespace Volo.Abp.EntityFrameworkCore.Extensions |
||||
|
{ |
||||
|
public class PropertyExtensionInfo |
||||
|
{ |
||||
|
public Action<PropertyBuilder> Action { get; set; } |
||||
|
|
||||
|
public Type PropertyType { get; } |
||||
|
|
||||
|
public PropertyExtensionInfo(Type propertyType) |
||||
|
{ |
||||
|
PropertyType = propertyType; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,41 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion; |
||||
|
using Newtonsoft.Json; |
||||
|
using Volo.Abp.EntityFrameworkCore.Extensions; |
||||
|
|
||||
|
namespace Volo.Abp.EntityFrameworkCore.ValueConverters |
||||
|
{ |
||||
|
public class ExtraPropertiesValueConverter : ValueConverter<Dictionary<string, object>, string> |
||||
|
{ |
||||
|
public ExtraPropertiesValueConverter(Type entityType) |
||||
|
: base( |
||||
|
d => SerializeObject(d, entityType), |
||||
|
s => DeserializeObject(s)) |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
|
||||
|
private static string SerializeObject(Dictionary<string, object> extraProperties, Type entityType) |
||||
|
{ |
||||
|
var copyDictionary = new Dictionary<string, object>(extraProperties); |
||||
|
|
||||
|
if (entityType != null) |
||||
|
{ |
||||
|
var propertyNames = EntityExtensionManager.GetPropertyNames(entityType); |
||||
|
|
||||
|
foreach (var propertyName in propertyNames) |
||||
|
{ |
||||
|
copyDictionary.Remove(propertyName); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return JsonConvert.SerializeObject(copyDictionary, Formatting.None); |
||||
|
} |
||||
|
|
||||
|
private static Dictionary<string, object> DeserializeObject(string extraPropertiesAsJson) |
||||
|
{ |
||||
|
return JsonConvert.DeserializeObject<Dictionary<string, object>>(extraPropertiesAsJson); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,9 +1,32 @@ |
|||||
using Volo.Abp.TestApp.Testing; |
using System.Threading.Tasks; |
||||
|
using Shouldly; |
||||
|
using Volo.Abp.Data; |
||||
|
using Volo.Abp.TestApp.Testing; |
||||
|
using Xunit; |
||||
|
|
||||
namespace Volo.Abp.EntityFrameworkCore.Domain |
namespace Volo.Abp.EntityFrameworkCore.Domain |
||||
{ |
{ |
||||
public class ExtraProperties_Tests : ExtraProperties_Tests<AbpEntityFrameworkCoreTestModule> |
public class ExtraProperties_Tests : ExtraProperties_Tests<AbpEntityFrameworkCoreTestModule> |
||||
{ |
{ |
||||
|
[Fact] |
||||
|
public async Task Should_Get_An_Extra_Property_Configured_As_Extension() |
||||
|
{ |
||||
|
var london = await CityRepository.FindByNameAsync("London"); |
||||
|
london.HasProperty("PhoneCode").ShouldBeTrue(); |
||||
|
london.GetProperty<string>("PhoneCode").ShouldBe("42"); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_Update_An_Existing_Extra_Property_Configured_As_Extension() |
||||
|
{ |
||||
|
var london = await CityRepository.FindByNameAsync("London"); |
||||
|
london.GetProperty<string>("PhoneCode").ShouldBe("42"); |
||||
|
|
||||
|
london.ExtraProperties["PhoneCode"] = "53"; |
||||
|
await CityRepository.UpdateAsync(london); |
||||
|
|
||||
|
var london2 = await CityRepository.FindByNameAsync("London"); |
||||
|
london2.GetProperty<string>("PhoneCode").ShouldBe("53"); |
||||
|
} |
||||
} |
} |
||||
} |
} |
||||
|
|||||
@ -0,0 +1,22 @@ |
|||||
|
using Volo.Abp.EntityFrameworkCore.Extensions; |
||||
|
using Volo.Abp.TestApp.Domain; |
||||
|
using Volo.Abp.Threading; |
||||
|
|
||||
|
namespace Volo.Abp.EntityFrameworkCore.Domain |
||||
|
{ |
||||
|
public static class TestEntityExtensionConfigurator |
||||
|
{ |
||||
|
private static readonly OneTimeRunner OneTimeRunner = new OneTimeRunner(); |
||||
|
|
||||
|
public static void Configure() |
||||
|
{ |
||||
|
OneTimeRunner.Run(() => |
||||
|
{ |
||||
|
EntityExtensionManager.AddProperty<City, string>( |
||||
|
"PhoneCode", |
||||
|
p => p.HasMaxLength(8) |
||||
|
); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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 './common-utils'; |
||||
export * from './generator-utils'; |
export * from './generator-utils'; |
||||
export * from './initial-utils'; |
export * from './initial-utils'; |
||||
|
export * from './linked-list'; |
||||
export * from './route-utils'; |
export * from './route-utils'; |
||||
export * from './rxjs-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; |
||||
@ -0,0 +1,33 @@ |
|||||
|
using Volo.Abp.EntityFrameworkCore.Extensions; |
||||
|
using Volo.Abp.Identity; |
||||
|
using Volo.Abp.Threading; |
||||
|
|
||||
|
namespace MyCompanyName.MyProjectName.EntityFrameworkCore |
||||
|
{ |
||||
|
public static class MyProjectNameEntityExtensions |
||||
|
{ |
||||
|
private static readonly OneTimeRunner OneTimeRunner = new OneTimeRunner(); |
||||
|
|
||||
|
public static void Configure() |
||||
|
{ |
||||
|
OneTimeRunner.Run(() => |
||||
|
{ |
||||
|
/* You can configure entity extension properties for the |
||||
|
* entities defined in the used modules. |
||||
|
* |
||||
|
* Example: |
||||
|
* |
||||
|
* EntityExtensionManager.AddProperty<IdentityUser, string>( |
||||
|
* "MyProperty", |
||||
|
* b => |
||||
|
* { |
||||
|
* b.HasMaxLength(128); |
||||
|
* }); |
||||
|
* |
||||
|
* See the documentation for more: |
||||
|
* https://docs.abp.io/en/abp/latest/Customizing-Application-Modules-Extending-Entities
|
||||
|
*/ |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue