mirror of https://github.com/abpframework/abp.git
3 changed files with 961 additions and 0 deletions
@ -0,0 +1,720 @@ |
|||||
|
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(value => value === 'x'); |
||||
|
|
||||
|
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((_, index) => index === 3); |
||||
|
|
||||
|
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 node = list.find(value => value === 'x'); |
||||
|
|
||||
|
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 node = list.find(value => value === 'x'); |
||||
|
|
||||
|
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('#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(value => value === 'x'); |
||||
|
|
||||
|
expect(index1).toBe(1); |
||||
|
|
||||
|
// "a" <-> "x" <-> "b" <-> "x" <-> "c"
|
||||
|
|
||||
|
let timesFound = 0; |
||||
|
const index2 = list.findIndex(value => { |
||||
|
if (timesFound > 1) return false; |
||||
|
|
||||
|
timesFound += Number(value === 'x'); |
||||
|
|
||||
|
return timesFound > 1; |
||||
|
}); |
||||
|
|
||||
|
expect(index2).toBe(3); |
||||
|
}); |
||||
|
|
||||
|
it('should return -1 when list is empty', () => { |
||||
|
const index = list.findIndex(value => 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(value => 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', () => { |
||||
|
list.add('a').tail(); |
||||
|
list.add('b').tail(); |
||||
|
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('#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 (let 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,240 @@ |
|||||
|
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>, nextNode: ListNode<T>): 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(currentValue => compareFn(currentValue, 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(currentValue => compareFn(currentValue, 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), |
||||
|
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(currentValue => compareFn(currentValue, value)); |
||||
|
|
||||
|
if (position < 0) return undefined; |
||||
|
|
||||
|
return this.dropByIndex(position); |
||||
|
} |
||||
|
|
||||
|
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.value, 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.value, 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.value, position, this); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
get(position: number): ListNode<T> | undefined { |
||||
|
return this.find((_, index) => position === index); |
||||
|
} |
||||
|
|
||||
|
indexOf(value: T, compareFn = compare): number { |
||||
|
return this.findIndex(currentValue => compareFn(currentValue, value)); |
||||
|
} |
||||
|
|
||||
|
toArray(): T[] { |
||||
|
const array = new Array(this.size); |
||||
|
|
||||
|
this.forEach((value, index) => (array[index!] = 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> = ( |
||||
|
value: T, |
||||
|
index?: number, |
||||
|
list?: LinkedList, |
||||
|
) => R; |
||||
Loading…
Reference in new issue