mirror of https://github.com/abpframework/abp.git
64 changed files with 3115 additions and 395 deletions
@ -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"}' |
|||
*/ |
|||
``` |
|||
|
|||
|
|||
|
|||
@ -0,0 +1,67 @@ |
|||
## Service Proxies |
|||
|
|||
It is common to call a REST endpoint in the server from our Angular applications. In this case, we generally create **services** (those have methods for each service method on the server side) and **model objects** (matches to [DTOs](../../Data-Transfer-Objects) in the server side). |
|||
|
|||
In addition to manually creating such server-interacting services, we could use tools like [NSWAG](https://github.com/RicoSuter/NSwag) to generate service proxies for us. But NSWAG has the following problems we've experienced: |
|||
|
|||
* It generates a **big, single** .ts file which has some problems; |
|||
* It get **too large** when your application grows. |
|||
* It doesn't fit into the **[modular](../../Module-Development-Basics) approach** of the ABP framework. |
|||
* It creates a bit **ugly code**. We want to have a clean code (just like if we write manually). |
|||
* It can not generate the same **method signature** declared in the server side (because swagger.json doesn't exactly reflect the method signature of the backend service). We've created an endpoint that exposes server side method contacts to allow clients generate a better aligned client proxies. |
|||
|
|||
ABP CLI `generate-proxies` command automatically generates the typescript client proxies by creating folders which separated by module names in the `src/app` folder. |
|||
Run the following command in the **root folder** of the angular application: |
|||
|
|||
```bash |
|||
abp generate-proxy |
|||
``` |
|||
|
|||
It only creates proxies only for your own application's services. It doesn't create proxies for the services of the application modules you're using (by default). There are several options. See the [CLI documentation](../../CLI). |
|||
|
|||
The files generated with the `--module all` option like below: |
|||
|
|||
 |
|||
|
|||
### Services |
|||
|
|||
Each generated service matches a back-end controller. The services methods call back-end APIs via [RestService](./HTTP-Requests.md#restservice). |
|||
|
|||
A variable named `apiName` (available as of v2.4) is defined in each service. `apiName` matches the module's RemoteServiceName. This variable passes to the `RestService` as a parameter at each request. If there is no microservice API defined in the environment, `RestService` uses the default. See [getting a specific API endpoint from application config](HTTP-Requests#how-to-get-a-specific-api-endpoint-from-application-config) |
|||
|
|||
The `providedIn` property of the services is defined as `'root'`. Therefore no need to add a service as a provider to a module. You can use a service by injecting it into a constructor as shown below: |
|||
|
|||
```js |
|||
import { AbpApplicationConfigurationService } from '../app/shared/services'; |
|||
|
|||
//... |
|||
export class HomeComponent{ |
|||
constructor(private appConfigService: AbpApplicationConfigurationService) {} |
|||
|
|||
ngOnInit() { |
|||
this.appConfigService.get().subscribe() |
|||
} |
|||
} |
|||
``` |
|||
|
|||
The Angular compiler removes the services that have not been injected anywhere from the final output. See the [tree-shakable providers documentation](https://angular.io/guide/dependency-injection-providers#tree-shakable-providers). |
|||
|
|||
### Models |
|||
|
|||
The generated models match the DTOs in the back-end. Each model is generated as a class under the `src/app/*/shared/models` folder. |
|||
|
|||
There are a few [base classes](https://github.com/abpframework/abp/blob/dev/npm/ng-packs/packages/core/src/lib/models/dtos.ts) in the `@abp/ng.core` package. Some models extend these classes. |
|||
|
|||
A class instance can be created as shown below: |
|||
|
|||
```js |
|||
import { IdentityRoleCreateDto } from '../identity/shared/models'; |
|||
//... |
|||
const instance = new IdentityRoleCreateDto({name: 'Role 1', isDefault: false, isPublic: true}) |
|||
``` |
|||
|
|||
Initial values can optionally be passed to each class constructor. |
|||
|
|||
## What's Next? |
|||
|
|||
* [Http Requests](./Http-Requests.md) |
|||
|
After Width: | Height: | Size: 235 KiB |
@ -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 |
|||
{ |
|||
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 './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; |
|||
@ -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