Skip to content

Commit

Permalink
Better linked list (#10)
Browse files Browse the repository at this point in the history
  • Loading branch information
vdolek authored Mar 2, 2021
1 parent 8fe151c commit d37708c
Show file tree
Hide file tree
Showing 32 changed files with 406 additions and 352 deletions.
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,9 @@ const adultsGroupedBySexArray = adultsGroupedBySex.toArray();
|--------------|-------------------------|-----------------------------------------------------------------------------|
| `Enumerable` | - | Represents a collection which supports a simple iteration. |
| `List` | `ReadOnlyList` | Represents a list of objects that can be accessed by index. |
| `LinkedList` | `ReadOnlyLinkedList` | Represents a linked list of objects. |
| `Dictionary` | `ReadOnlyDictionary` | Represents a collection of keys and values. Values can be accessed by keys. |
| `HashSet` | `ReadOnlyHashSet` | Represents a set of values. |
| `LinkedList` | - | Represents a linked list of objects. |
| `Stack` | - | Represents a stack of objects. |
| `Queue` | - | Represents a queue of objects. |

Expand Down Expand Up @@ -147,7 +147,6 @@ const adultsGroupedBySexArray = adultsGroupedBySex.toArray();
| `toLookup` | Converts sequence to a `Lookup`. |
| `toMap` | Converts sequence to a `Map`. |
| `toReadOnlyHashSet` | Converts sequence to a `ReadOnlyHashSet`. |
| `toReadOnlyLinkedList` | Converts sequence to a `ReadOnlyLinkedList`. |
| `toReadOnlyList` | Converts sequence to a `ReadOnlyList`. |
| `toReadOnlyMap` | Converts sequence to a `ReadOnlyMap`. |
| `toReadOnlySet` | Converts sequence to a `ReadOnlySet`. |
Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "sharp-collections",
"version": "1.6.0",
"version": "1.6.1",
"description": ".NET Linq like collection library for TypeScript and JavaScript.",
"scripts": {
"build": "tsc",
Expand All @@ -24,6 +24,7 @@
"HashSet",
"Stack",
"Queue",
"ReadOnly",
"HashCode",
"EqualityComparer"
],
Expand Down
8 changes: 8 additions & 0 deletions src/Errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ export class Errors {
return new Error('The given key was not present in the dictionary');
}

public static linkedListEmpty(): Error {
return new Error('LinkedList is empty');
}

public static noElements(): Error {
return new Error('Sequence contains no elements');
}
Expand Down Expand Up @@ -54,4 +58,8 @@ export class Errors {
public static valueIsNotNumber(): Error {
return new TypeError('Value is not a number');
}

public static valueNotFoundLinkedList(): Error {
return new Error('The value is not present in LinkedList');
}
}
213 changes: 175 additions & 38 deletions src/collections/LinkedList.ts
Original file line number Diff line number Diff line change
@@ -1,70 +1,207 @@
import { LinkedListItemInternal } from '../models/LinkedListItemInternal';
import { IteratorEnumerable } from '../enumerables/IteratorEnumerable';
import { Errors } from '../Errors';
import { LinkedListNode } from '../models/LinkedListNode';
import { LinkedListNodeInternal } from '../models/LinkedListNodeInternal';

import { ReadOnlyLinkedList } from './ReadOnlyLinkedList';
import { Enumerable } from './Enumerable';

/**
* Represents a linked list of objects.
*/
export class LinkedList<T> extends ReadOnlyLinkedList<T> {
public asReadOnly(): ReadOnlyLinkedList<T> {
return this;
export class LinkedList<T> extends Enumerable<T> {
protected sizeInternal = 0;
protected headInternal: LinkedListNodeInternal<T> | undefined;
protected tailInternal: LinkedListNodeInternal<T> | undefined;

public constructor(source?: Iterable<T>) {
super();

if (source != null) {
for (const item of source) {
this.addTail(item);
}
}
}

public addFirst(value: T): void {
const item = new LinkedListItemInternal<T>(value);
if (this.size === 0) {
this.firstInternal = this.lastInternal = item;
} else {
this.firstInternal!.previous = item;
item.next = this.firstInternal;
this.firstInternal = item;
public *[Symbol.iterator](): Iterator<T> {
for (let node = this.headOrDefault; node != null; node = node.next) {
yield node.value;
}
}

++this.sizeInternal;
public get fromTail(): Enumerable<T> {
return this.nodesFromTail.select(x => x.value);
}

public get nodes(): Enumerable<LinkedListNode<T>> {
return new IteratorEnumerable(this.nodesInternal());
}

public get nodesFromTail(): Enumerable<LinkedListNode<T>> {
return new IteratorEnumerable(this.nodesReverseInternal());
}

public addLast(value: T): void {
this.add(value);
public get size(): number {
return this.sizeInternal;
}

public removeFirst(): void {
if (this.size === 0) {
return;
public get head(): LinkedListNode<T> {
if (this.headInternal == null) {
throw Errors.linkedListEmpty();
}

const oldFirst = this.firstInternal;
this.firstInternal = this.firstInternal!.next;
oldFirst!.next = undefined;
return this.headInternal;
}

public get headOrDefault(): LinkedListNode<T> | undefined {
return this.headInternal;
}

if (this.firstInternal == null) {
this.lastInternal = undefined;
} else {
this.firstInternal.previous = undefined;
public get tail(): LinkedListNode<T> {
if (this.tailInternal == null) {
throw Errors.linkedListEmpty();
}

--this.sizeInternal;
return this.tailInternal;
}

public removeLast(): void {
if (this.size === 0) {
return;
public get tailOrDefault(): LinkedListNode<T> | undefined {
return this.tailInternal;
}

public find(value: T): LinkedListNode<T> {
const node = this.findOrDefault(value);
if (node == null) {
throw Errors.valueNotFoundLinkedList();
}

const oldLast = this.lastInternal;
this.lastInternal = this.lastInternal!.previous;
oldLast!.previous = undefined;
return node;
}

public findOrDefault(value: T): LinkedListNode<T> | undefined {
const node = this.nodes.firstOrDefault(x => x.value === value);
return node;
}

if (this.lastInternal == null) {
this.firstInternal = undefined;
} else {
this.lastInternal.next = undefined;
public findLast(value: T): LinkedListNode<T> {
const node = this.findLastOrDefault(value);
if (node == null) {
throw Errors.valueNotFoundLinkedList();
}

return node;
}

public findLastOrDefault(value: T): LinkedListNode<T> | undefined {
const node = this.nodes.reverse().firstOrDefault(x => x.value === value);
return node;
}

public addHead(value: T): void {
this.addBefore(this.headInternal, value);
}

public addTail(value: T): void {
this.addAfter(this.tailInternal, value);
}

public addAfter(node: LinkedListNode<T> | undefined, value: T): void {
const nodeInternal = this.extractNode(node);
const newNode = new LinkedListNodeInternal(value, this, nodeInternal, nodeInternal?.nextInternal);
this.addNode(newNode);
}

public addBefore(node: LinkedListNode<T> | undefined, value: T): void {
const nodeInternal = this.extractNode(node);
const newNode = new LinkedListNodeInternal(value, this, nodeInternal?.previousInternal, nodeInternal);
this.addNode(newNode);
}

public removeHead(): void {
this.remove(this.head);
}

public removeTail(): void {
this.remove(this.tail);
}

public remove(node: LinkedListNode<T>): void {
const n = this.extractNode(node);
const prev = n.previousInternal;
const next = n.nextInternal;

if (prev != null) {
prev.nextInternal = next;
}

if (next != null) {
next.previousInternal = prev;
}

if (node === this.headInternal) {
this.headInternal = next;
}

if (node === this.tailInternal) {
this.tailInternal = prev;
}

--this.sizeInternal;

n.previousInternal = undefined;
n.nextInternal = undefined;
}

public clear(): void {
this.sizeInternal = 0;
this.firstInternal = this.lastInternal = undefined;
this.headInternal = this.tailInternal = undefined;
}

private addNode(node: LinkedListNodeInternal<T>): void {
if (node.previousInternal != null) {
node.previousInternal.nextInternal = node;
}

if (node.nextInternal != null) {
node.nextInternal.previousInternal = node;
}

if (node.previousInternal == null) {
this.headInternal = node;
}

if (node.nextInternal == null) {
this.tailInternal = node;
}

++this.sizeInternal;
}

private extractNode(node: LinkedListNode<T>): LinkedListNodeInternal<T>;
private extractNode(node: LinkedListNode<T> | undefined): LinkedListNodeInternal<T> | undefined;
private extractNode(node: LinkedListNode<T> | undefined): LinkedListNodeInternal<T> | undefined {
if (node == null) {
return undefined;
}

if (node instanceof LinkedListNodeInternal) {
if (node.linkedList === this) {
return node;
}
}

throw new Error('Node does not belong to LinkedList');
}

private *nodesInternal(): Iterator<LinkedListNodeInternal<T>> {
for (let node = this.headInternal; node != null; node = node.nextInternal) {
yield node;
}
}

private *nodesReverseInternal(): Iterator<LinkedListNodeInternal<T>> {
for (let node = this.tailInternal; node != null; node = node.previousInternal) {
yield node;
}
}
}
4 changes: 0 additions & 4 deletions src/collections/Lookup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,6 @@ export class Lookup<TKey, TElement> extends Enumerable<Grouping<TKey, TElement>>
return this.dict.containsKey(key);
}

public count(): number {
return this.dict.count();
}

public get(key: TKey): Grouping<TKey, TElement> {
return this.dict.get(key);
}
Expand Down
8 changes: 4 additions & 4 deletions src/collections/Queue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,21 +32,21 @@ export class Queue<T> extends Enumerable<T> {
throw Errors.queueEmpty();
}

return this.source.firstItem!.value;
return this.source.head.value;
}

public dequeue(): T {
if (this.size === 0) {
throw Errors.queueEmpty();
}

const value = this.source.firstItem!.value;
this.source.removeFirst();
const value = this.source.head.value;
this.source.removeHead();
return value;
}

public enqueue(value: T): void {
this.source.addLast(value);
this.source.addTail(value);
}

public clear(): void {
Expand Down
Loading

0 comments on commit d37708c

Please sign in to comment.