Browse Source

feat(core): add methods to LinkedList for adding and dropping multiple nodes

pull/3376/head
Arman Ozak 7 years ago
parent
commit
4d93244f06
  1. 870
      npm/ng-packs/packages/core/src/lib/tests/linked-list.spec.ts
  2. 257
      npm/ng-packs/packages/core/src/lib/utils/linked-list.ts

870
npm/ng-packs/packages/core/src/lib/tests/linked-list.spec.ts

File diff suppressed because it is too large

257
npm/ng-packs/packages/core/src/lib/utils/linked-list.ts

@ -27,65 +27,107 @@ export class LinkedList<T = any> {
return this.size; return this.size;
} }
private linkWith( private attach(
value: T, value: T,
previousNode: ListNode<T> | undefined, previousNode: ListNode<T> | undefined,
nextNode: ListNode<T> | undefined, nextNode: ListNode<T> | undefined,
): ListNode<T> { ): ListNode<T> {
const node = new ListNode(value);
if (!previousNode) return this.addHead(value); if (!previousNode) return this.addHead(value);
if (!nextNode) return this.addTail(value); if (!nextNode) return this.addTail(value);
const node = new ListNode(value);
node.previous = previousNode; node.previous = previousNode;
previousNode.next = node; previousNode.next = node;
node.next = nextNode; node.next = nextNode;
nextNode.previous = node; nextNode.previous = node;
this.size += 1; this.size++;
return node;
}
private attachMany(
values: T[],
previousNode: ListNode<T> | undefined,
nextNode: ListNode<T> | undefined,
): ListNode<T>[] {
if (!values.length) return [];
if (!previousNode) return this.addManyHead(values);
if (!nextNode) return this.addManyTail(values);
const list = new LinkedList<T>();
list.addManyTail(values);
list.first!.previous = previousNode;
previousNode.next = list.first;
list.last!.next = nextNode;
nextNode.previous = list.last;
this.size += values.length;
return list.toNodeArray();
}
private detach(node: ListNode<T>) {
if (!node.previous) return this.dropHead();
if (!node.next) return this.dropTail();
node.previous.next = node.next;
node.next.previous = node.previous;
this.size--;
return node; return node;
} }
add(value: T) { add(value: T) {
return { return {
after: (previousValue: T, compareFn = compare) => { after: (previousValue: T, compareFn: ListComparisonFn<T> = compare) =>
return this.addAfter(value, previousValue, compareFn); this.addAfter(value, previousValue, compareFn),
}, before: (nextValue: T, compareFn: ListComparisonFn<T> = compare) =>
before: (nextValue: T, compareFn = compare) => { this.addBefore(value, nextValue, compareFn),
return this.addBefore(value, nextValue, compareFn); byIndex: (position: number) => this.addByIndex(value, position),
}, head: () => this.addHead(value),
byIndex: (position: number): ListNode<T> => { tail: () => this.addTail(value),
return this.addByIndex(value, position); };
}, }
head: (): ListNode<T> => {
return this.addHead(value); addMany(values: T[]) {
}, return {
tail: (): ListNode<T> => { after: (previousValue: T, compareFn: ListComparisonFn<T> = compare) =>
return this.addTail(value); this.addManyAfter(values, previousValue, compareFn),
}, before: (nextValue: T, compareFn: ListComparisonFn<T> = compare) =>
this.addManyBefore(values, nextValue, compareFn),
byIndex: (position: number) => this.addManyByIndex(values, position),
head: () => this.addManyHead(values),
tail: () => this.addManyTail(values),
}; };
} }
addAfter(value: T, previousValue: T, compareFn = compare): ListNode<T> { addAfter(value: T, previousValue: T, compareFn: ListComparisonFn<T> = compare): ListNode<T> {
const previous = this.find(node => compareFn(node.value, previousValue)); const previous = this.find(node => compareFn(node.value, previousValue));
return previous ? this.linkWith(value, previous, previous.next) : this.addTail(value); return previous ? this.attach(value, previous, previous.next) : this.addTail(value);
} }
addBefore(value: T, nextValue: T, compareFn = compare): ListNode<T> { addBefore(value: T, nextValue: T, compareFn: ListComparisonFn<T> = compare): ListNode<T> {
const next = this.find(node => compareFn(node.value, nextValue)); const next = this.find(node => compareFn(node.value, nextValue));
return next ? this.linkWith(value, next.previous, next) : this.addHead(value); return next ? this.attach(value, next.previous, next) : this.addHead(value);
} }
addByIndex(value: T, position: number): ListNode<T> { addByIndex(value: T, position: number): ListNode<T> {
if (position < 0) position += this.size;
else if (position >= this.size) return this.addTail(value);
if (position <= 0) return this.addHead(value); if (position <= 0) return this.addHead(value);
if (position >= this.size) return this.addTail(value);
const next = this.get(position)!; const next = this.get(position)!;
return this.linkWith(value, next.previous, next); return this.attach(value, next.previous, next);
} }
addHead(value: T): ListNode<T> { addHead(value: T): ListNode<T> {
@ -97,7 +139,7 @@ export class LinkedList<T = any> {
else this.last = node; else this.last = node;
this.first = node; this.first = node;
this.size += 1; this.size++;
return node; return node;
} }
@ -114,51 +156,92 @@ export class LinkedList<T = any> {
this.last = node; this.last = node;
} }
this.size += 1; this.size++;
return node; return node;
} }
addManyAfter(
values: T[],
previousValue: T,
compareFn: ListComparisonFn<T> = compare,
): ListNode<T>[] {
const previous = this.find(node => compareFn(node.value, previousValue));
return previous ? this.attachMany(values, previous, previous.next) : this.addManyTail(values);
}
addManyBefore(
values: T[],
nextValue: T,
compareFn: ListComparisonFn<T> = compare,
): ListNode<T>[] {
const next = this.find(node => compareFn(node.value, nextValue));
return next ? this.attachMany(values, next.previous, next) : this.addManyHead(values);
}
addManyByIndex(values: T[], position: number): ListNode<T>[] {
if (position < 0) position += this.size;
if (position <= 0) return this.addManyHead(values);
if (position >= this.size) return this.addManyTail(values);
const next = this.get(position)!;
return this.attachMany(values, next.previous, next);
}
addManyHead(values: T[]): ListNode<T>[] {
return values.reduceRight<ListNode<T>[]>((nodes, value) => {
nodes.unshift(this.addHead(value));
return nodes;
}, []);
}
addManyTail(values: T[]): ListNode<T>[] {
return values.map(value => this.addTail(value));
}
drop() { drop() {
return { return {
byIndex: (position: number) => this.dropByIndex(position), byIndex: (position: number) => this.dropByIndex(position),
byValue: (value: T, compareFn = compare) => this.dropByValue(value, compareFn), byValue: (value: T, compareFn: ListComparisonFn<T> = compare) =>
byValueAll: (value: T, compareFn = compare) => this.dropByValueAll(value, compareFn), this.dropByValue(value, compareFn),
byValueAll: (value: T, compareFn: ListComparisonFn<T> = compare) =>
this.dropByValueAll(value, compareFn),
head: () => this.dropHead(), head: () => this.dropHead(),
tail: () => this.dropTail(), tail: () => this.dropTail(),
}; };
} }
dropMany(count: number) {
return {
byIndex: (position: number) => this.dropManyByIndex(count, position),
head: () => this.dropManyHead(count),
tail: () => this.dropManyTail(count),
};
}
dropByIndex(position: number): ListNode<T> | undefined { dropByIndex(position: number): ListNode<T> | undefined {
if (position === 0) return this.dropHead(); if (position < 0) position += this.size;
else if (position === this.size - 1) return this.dropTail();
const current = this.get(position); const current = this.get(position);
if (current) { return current ? this.detach(current) : undefined;
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 { dropByValue(value: T, compareFn: ListComparisonFn<T> = compare): ListNode<T> | undefined {
const position = this.findIndex(node => compareFn(node.value, value)); const position = this.findIndex(node => compareFn(node.value, value));
if (position < 0) return undefined; return position < 0 ? undefined : this.dropByIndex(position);
return this.dropByIndex(position);
} }
dropByValueAll(value: T, compareFn = compare): ListNode<T>[] { dropByValueAll(value: T, compareFn: ListComparisonFn<T> = compare): ListNode<T>[] {
const dropped: ListNode<T>[] = []; const dropped: ListNode<T>[] = [];
for (let current = this.first, position = 0; current; position += 1, current = current.next) { for (let current = this.first, position = 0; current; position++, current = current.next) {
if (compareFn(current.value, value)) { if (compareFn(current.value, value)) {
dropped.push(this.dropByIndex(position - dropped.length)!); dropped.push(this.dropByIndex(position - dropped.length)!);
} }
@ -176,7 +259,7 @@ export class LinkedList<T = any> {
if (this.first) this.first.previous = undefined; if (this.first) this.first.previous = undefined;
else this.last = undefined; else this.last = undefined;
this.size -= 1; this.size--;
return head; return head;
} }
@ -193,7 +276,7 @@ export class LinkedList<T = any> {
if (this.last) this.last.next = undefined; if (this.last) this.last.next = undefined;
else this.first = undefined; else this.first = undefined;
this.size -= 1; this.size--;
return tail; return tail;
} }
@ -201,24 +284,66 @@ export class LinkedList<T = any> {
return undefined; return undefined;
} }
find(predicate: ListIteratorFunction<T>): ListNode<T> | undefined { dropManyByIndex(count: number, position: number): ListNode<T>[] {
for (let current = this.first, position = 0; current; position += 1, current = current.next) { if (count <= 0) return [];
if (position < 0) position = Math.max(position + this.size, 0);
else if (position >= this.size) return [];
count = Math.min(count, this.size - position);
const dropped: ListNode<T>[] = [];
while (count--) {
const current = this.get(position);
dropped.push(this.detach(current!)!);
}
return dropped;
}
dropManyHead(count: Exclude<number, 0>): ListNode<T>[] {
if (count <= 0) return [];
count = Math.min(count, this.size);
const dropped: ListNode<T>[] = [];
while (count--) dropped.unshift(this.dropHead()!);
return dropped;
}
dropManyTail(count: Exclude<number, 0>): ListNode<T>[] {
if (count <= 0) return [];
count = Math.min(count, this.size);
const dropped: ListNode<T>[] = [];
while (count--) dropped.push(this.dropTail()!);
return dropped;
}
find(predicate: ListIteratorFn<T>): ListNode<T> | undefined {
for (let current = this.first, position = 0; current; position++, current = current.next) {
if (predicate(current, position, this)) return current; if (predicate(current, position, this)) return current;
} }
return undefined; return undefined;
} }
findIndex(predicate: ListIteratorFunction<T>): number { findIndex(predicate: ListIteratorFn<T>): number {
for (let current = this.first, position = 0; current; position += 1, current = current.next) { for (let current = this.first, position = 0; current; position++, current = current.next) {
if (predicate(current, position, this)) return position; if (predicate(current, position, this)) return position;
} }
return -1; return -1;
} }
forEach<R = boolean>(callback: ListIteratorFunction<T, R>) { forEach<R = boolean>(callback: ListIteratorFn<T, R>) {
for (let node = this.first, position = 0; node; position += 1, node = node.next) { for (let node = this.first, position = 0; node; position++, node = node.next) {
callback(node, position, this); callback(node, position, this);
} }
} }
@ -227,7 +352,7 @@ export class LinkedList<T = any> {
return this.find((_, index) => position === index); return this.find((_, index) => position === index);
} }
indexOf(value: T, compareFn = compare): number { indexOf(value: T, compareFn: ListComparisonFn<T> = compare): number {
return this.findIndex(node => compareFn(node.value, value)); return this.findIndex(node => compareFn(node.value, value));
} }
@ -239,20 +364,32 @@ export class LinkedList<T = any> {
return array; return array;
} }
toString(): string { toNodeArray(): ListNode<T>[] {
const array = new Array(this.size);
this.forEach((node, index) => (array[index!] = node));
return array;
}
toString(mapperFn: ListMapperFn<T> = JSON.stringify): string {
return this.toArray() return this.toArray()
.map(value => JSON.stringify(value)) .map(value => mapperFn(value))
.join(' <-> '); .join(' <-> ');
} }
*[Symbol.iterator]() { *[Symbol.iterator]() {
for (let node = this.first, position = 0; node; position += 1, node = node.next) { for (let node = this.first, position = 0; node; position++, node = node.next) {
yield node.value; yield node.value;
} }
} }
} }
export type ListIteratorFunction<T = any, R = boolean> = ( export type ListMapperFn<T = any> = (value: T) => any;
export type ListComparisonFn<T = any> = (value1: T, value2: T) => boolean;
export type ListIteratorFn<T = any, R = boolean> = (
node: ListNode<T>, node: ListNode<T>,
index?: number, index?: number,
list?: LinkedList, list?: LinkedList,

Loading…
Cancel
Save