Reworked LinkedList and Queue

- Reworked LinkedList and Queue
- Written tests for LinkedList and Queue
This commit is contained in:
TheSparta 2023-12-21 22:27:19 +00:00
parent a2f0b4584f
commit 4f8670c657
7 changed files with 646 additions and 224 deletions

View File

@ -12,14 +12,13 @@ export class ApplicationContext
/** /**
* Called like: * Called like:
* * ```
* const registerPlayerInfo = this.applicationContext.getLatestValue(ContextVariableType.REGISTER_PLAYER_REQUEST).getValue<IRegisterPlayerRequestData>(); * const registerPlayerInfo = this.applicationContext.getLatestValue(ContextVariableType.REGISTER_PLAYER_REQUEST).getValue<IRegisterPlayerRequestData>();
* *
* const activePlayerSessionId = this.applicationContext.getLatestValue(ContextVariableType.SESSION_ID).getValue<string>(); * const activePlayerSessionId = this.applicationContext.getLatestValue(ContextVariableType.SESSION_ID).getValue<string>();
* *
* const matchInfo = this.applicationContext.getLatestValue(ContextVariableType.RAID_CONFIGURATION).getValue<IGetRaidConfigurationRequestData>(); * const matchInfo = this.applicationContext.getLatestValue(ContextVariableType.RAID_CONFIGURATION).getValue<IGetRaidConfigurationRequestData>();
* @param type * ```
* @returns
*/ */
public getLatestValue(type: ContextVariableType): ContextVariable public getLatestValue(type: ContextVariableType): ContextVariable
{ {
@ -27,18 +26,21 @@ export class ApplicationContext
{ {
return this.variables.get(type)?.getTail()?.getValue(); return this.variables.get(type)?.getTail()?.getValue();
} }
return undefined;
} }
public getValues(type: ContextVariableType): ContextVariable[] public getValues(type: ContextVariableType): ContextVariable[]
{ {
if (this.variables.has(type)) if (this.variables.has(type))
{ {
return this.variables.get(type).toList(); const res: ContextVariable[] = [];
}
return undefined; for (const value of this.variables.get(type).values())
{
res.push(value);
}
return res;
}
} }
public addValue(type: ContextVariableType, value: any): void public addValue(type: ContextVariableType, value: any): void
@ -53,12 +55,12 @@ export class ApplicationContext
list = new LinkedList<ContextVariable>(); list = new LinkedList<ContextVariable>();
} }
if (list.getSize() >= ApplicationContext.holderMaxSize) if (list.length >= ApplicationContext.holderMaxSize)
{ {
list.removeFirst(); list.shift();
} }
list.add(new ContextVariable(value, type)); list.append(new ContextVariable(value, type));
this.variables.set(type, list); this.variables.set(type, list);
} }

View File

@ -127,14 +127,14 @@ export class ImporterUtil
directoriesToRead.enqueueAll(directories.map((d) => `${filepath}${d}`)); directoriesToRead.enqueueAll(directories.map((d) => `${filepath}${d}`));
filesToProcess.enqueueAll(files.map((f) => new VisitNode(filepath, f))); filesToProcess.enqueueAll(files.map((f) => new VisitNode(filepath, f)));
while (!directoriesToRead.isEmpty()) while (directoriesToRead.length !== 0)
{ {
const directory = directoriesToRead.dequeue(); const directory = directoriesToRead.dequeue();
filesToProcess.enqueueAll(this.vfs.getFiles(directory).map((f) => new VisitNode(`${directory}/`, f))); filesToProcess.enqueueAll(this.vfs.getFiles(directory).map((f) => new VisitNode(`${directory}/`, f)));
directoriesToRead.enqueueAll(this.vfs.getDirs(directory).map((d) => `${directory}/${d}`)); directoriesToRead.enqueueAll(this.vfs.getDirs(directory).map((d) => `${directory}/${d}`));
} }
while (!filesToProcess.isEmpty()) while (filesToProcess.length !== 0)
{ {
const fileNode = filesToProcess.dequeue(); const fileNode = filesToProcess.dequeue();
if (this.vfs.getFileExtension(fileNode.fileName) === "json") if (this.vfs.getFileExtension(fileNode.fileName) === "json")

View File

@ -1,231 +1,317 @@
import { LinkedListNode } from "./Nodes";
export class LinkedList<T> export class LinkedList<T>
{ {
private head: LinkedListNode<T>; private head?: LinkedListNode<T>;
private tail: LinkedListNode<T>; private tail?: LinkedListNode<T>;
private _length: number;
public add(t: T): void public get length(): number
{ {
return this._length;
}
private set length(value: number)
{
this._length = value;
}
constructor()
{
this.length = 0;
this.head = this.tail = undefined;
}
/**
* Adds an element to the start of the list.
*/
public prepend(value: T): void
{
const node = new LinkedListNode(value);
this.length++;
if (!this.head) if (!this.head)
{ {
const node = new LinkedListNode(t); this.head = this.tail = node;
this.head = node; return;
this.tail = node;
} }
else
node.next = this.head;
this.head.prev = node;
this.head = node;
}
/**
* Adds an element at the given index to the list.
*/
public insertAt(value: T, idx: number): void
{
if (idx < 0 || idx > this.length)
{ {
let ref = this.head; return;
let next = this.head.getNextNode();
while (next)
{
ref = next;
next = ref.getNextNode();
}
const node = new LinkedListNode(t, ref);
ref.setNextNode(node);
this.tail = node;
} }
}
public addRange(list: T[]): void if (idx === 0)
{
for (const item of list)
{ {
this.add(item); this.prepend(value);
return;
} }
}
public getHead(): LinkedListNode<T> if (idx === this.length)
{
return this.head;
}
public getTail(): LinkedListNode<T>
{
return this.tail;
}
public isEmpty(): boolean
{
return this.head === undefined || this.head === null;
}
public getSize(): number
{
let size = 0;
let next = this.head;
while (next)
{ {
size++; this.append(value);
next = next.getNextNode(); return;
}
let ref = this.head;
for (let i = 0; i <= idx; ++i)
{
ref = ref.next;
}
const node = new LinkedListNode(value);
this.length++;
node.next = ref;
node.prev = ref.prev;
ref.prev = node;
if (node.prev)
{
node.prev.next = node;
} }
return size;
} }
public removeFirst(): LinkedListNode<T> /**
* Adds an element to the end of the list.
*/
public append(value: T): void
{ {
if (!this.head) const node = new LinkedListNode(value);
{ this.length++;
return undefined;
}
const node = this.head;
if (this.head.getNextNode())
{
this.head = this.head.getNextNode();
this.head.setPreviousNode(undefined);
}
else
{
this.head = undefined;
}
return node;
}
public removeLast(): LinkedListNode<T>
{
if (!this.tail) if (!this.tail)
{ {
return undefined; this.head = this.tail = node;
return;
} }
const node = this.tail; node.prev = this.tail;
if (this.tail.getPreviousNode()) this.tail.next = node;
this.tail = this.tail.next;
}
/**
* Returns the first element's value.
*/
public getHead(): T
{
return this.head?.value;
}
/**
* Finds the element from the list at the given index and returns it's value.
*/
public get(idx: number): T
{
if (idx < 0 || idx >= this.length)
{ {
this.tail = this.tail.getPreviousNode(); return;
this.tail.setNextNode(undefined);
} }
else
if (idx === 0)
{
return this.getHead();
}
if (idx === this.length - 1)
{
return this.getTail();
}
for (const [index, value] of this.entries())
{
if (idx === index)
{
return value;
}
}
}
/**
* Returns the last element's value.
*/
public getTail(): T
{
return this.tail?.value;
}
/**
* Finds and removes the first element from a list that has a value equal to the given value, returns it's value if it successfully removed it.
*/
public remove(value: T): T
{
let ref = this.head;
for (let i = 0; ref && i < this.length; ++i)
{
if (ref.value === value)
{
break;
}
ref = ref.next;
}
if (!ref)
{
return;
}
this.length--;
if (this.length === 0)
{
const out = this.head.value;
this.head = this.tail = undefined;
return out;
}
if (ref.prev)
{
ref.prev.next = ref.next;
}
if (ref.next)
{
ref.next.prev = ref.prev;
}
if (ref === this.head)
{
this.head = ref.next;
}
if (ref === this.tail)
{
this.tail = ref.prev;
}
ref.prev = ref.next = undefined;
return ref.value;
}
/**
* Removes the first element from the list and returns it's value. If the list is empty, undefined is returned and the list is not modified.
*/
public shift(): T
{
if (!this.head)
{
return;
}
this.length--;
const ref = this.head;
this.head = this.head.next;
ref.next = undefined;
if (this.length === 0)
{ {
this.tail = undefined; this.tail = undefined;
} }
return node;
return ref.value;
} }
public indexOf(func: (t: T) => boolean): number /**
* Removes the element from the list at the given index and returns it's value.
*/
public removeAt(idx: number): T
{ {
const node = this.head; if (idx < 0 || idx >= this.length)
let index = 0;
while (node)
{ {
if (func(node.getValue())) return;
{
return index;
}
index++;
} }
return undefined;
if (idx === 0)
{
return this.shift();
}
if (idx === this.length - 1)
{
return this.pop();
}
let ref = this.head;
this.length--;
for (let i = 0; i < idx; ++i)
{
ref = ref.next;
}
if (ref.prev)
{
ref.prev.next = ref.next;
}
if (ref.next)
{
ref.next.prev = ref.prev;
}
return ref.value;
} }
public contains(func: (t: T) => boolean): boolean /**
* Removes the last element from the list and returns it's value. If the list is empty, undefined is returned and the list is not modified.
*/
public pop(): T
{
if (!this.tail)
{
return;
}
this.length--;
const ref = this.tail;
this.tail = this.tail.prev;
ref.prev = undefined;
if (this.length === 0)
{
this.head = undefined;
}
return ref.value;
}
/**
* Returns an iterable of index, value pairs for every entry in the list.
*/
public *entries(): IterableIterator<[number, T]>
{
let node = this.head;
for (let i = 0; i < this.length; ++i)
{
yield [i, node.value];
node = node.next;
}
}
/**
* Returns an iterable of values in the list.
*/
public *values(): IterableIterator<T>
{ {
let node = this.head; let node = this.head;
while (node) while (node)
{ {
if (func(node.getValue())) yield node.value;
{ node = node.next;
return true;
}
node = node.getNextNode();
} }
return false;
}
public forEachNode(func: (t: LinkedListNode<T>) => void): void
{
let node = this.head;
while (node)
{
func(node);
node = node.getNextNode();
}
}
public forEachValue(func: (t: T) => void): void
{
let node = this.head;
while (node)
{
func(node.getValue());
node = node.getNextNode();
}
}
public findFirstNode(func: (t: LinkedListNode<T>) => boolean): LinkedListNode<T>
{
let node = this.head;
while (node)
{
if (func(node))
{
return node;
}
node = node.getNextNode();
}
return undefined;
}
public findFirstValue(func: (t: T) => boolean): T
{
let node = this.head;
while (node)
{
if (func(node.getValue()))
{
return node.getValue();
}
node = node.getNextNode();
}
return undefined;
}
public toList(): T[]
{
const elements: T[] = [];
let node = this.head;
while (node)
{
elements.push(node.getValue());
node = node.getNextNode();
}
return elements;
}
}
export class LinkedListNode<T>
{
private previous: LinkedListNode<T>;
private value: T;
private next: LinkedListNode<T>;
constructor(value: T, previous: LinkedListNode<T> = undefined, next: LinkedListNode<T> = undefined)
{
this.value = value;
this.previous = previous;
this.next = next;
}
public getValue(): T
{
return this.value;
}
public getNextNode(): LinkedListNode<T>
{
return this.next;
}
public setNextNode(node: LinkedListNode<T>): void
{
this.next = node;
}
public getPreviousNode(): LinkedListNode<T>
{
return this.previous;
}
public setPreviousNode(node: LinkedListNode<T>): void
{
this.previous = node;
} }
} }

View File

@ -0,0 +1,5 @@
export class LinkedListNode<T>
{
constructor(public value: T, public prev?: LinkedListNode<T>, public next?: LinkedListNode<T>)
{}
}

View File

@ -1,21 +1,30 @@
import { LinkedList } from "../lists/LinkedList";
export class Queue<T> export class Queue<T>
{ {
private elements: Record<number, T>; private list: LinkedList<T>;
private head: number;
private tail: number; public get length(): number
{
return this.list.length;
}
constructor() constructor()
{ {
this.elements = {}; this.list = new LinkedList<T>();
this.head = 0;
this.tail = 0;
} }
/**
* Adds an element to the end of the queue.
*/
public enqueue(element: T): void public enqueue(element: T): void
{ {
this.elements[this.tail] = element; this.list.append(element);
this.tail++;
} }
/**
* Iterates over the elements received and adds each one to the end of the queue.
*/
public enqueueAll(elements: T[]): void public enqueueAll(elements: T[]): void
{ {
for (const element of elements) for (const element of elements)
@ -24,25 +33,19 @@ export class Queue<T>
} }
} }
/**
* Removes the first element from the queue and returns it's value. If the queue is empty, undefined is returned and the queue is not modified.
*/
public dequeue(): T public dequeue(): T
{ {
const item = this.elements[this.head]; return this.list.shift();
delete this.elements[this.head];
this.head++;
return item;
} }
/**
* Returns the first element's value.
*/
public peek(): T public peek(): T
{ {
return this.elements[this.head]; return this.list.getHead();
}
public getLength(): number
{
return this.tail - this.head;
}
public isEmpty(): boolean
{
return this.getLength() === 0;
} }
} }

View File

@ -0,0 +1,263 @@
import "reflect-metadata";
import { describe, expect, it } from "vitest";
import { LinkedList } from "@spt-aki/utils/collections/lists/LinkedList";
describe("LinkedList", () =>
{
describe("prepend", () =>
{
const list = new LinkedList<number>();
list.prepend(420);
list.prepend(69);
list.prepend(8008135);
list.prepend(1337);
it("adds elements to the begining of the list", () =>
{
expect(list.getHead()).toEqual(1337);
expect(list.length).toEqual(4);
});
});
describe("append", () =>
{
const list = new LinkedList<number>();
list.append(420);
list.append(69);
list.append(8008135);
list.append(1337);
it("adds elements to the end of the list", () =>
{
expect(list.getHead()).toEqual(420);
expect(list.length).toEqual(4);
});
});
describe("insertAt", () =>
{
describe("empty list", () =>
{
const list = new LinkedList<number>();
it("should allow insertions at index 0 only", () =>
{
list.insertAt(420, 1);
expect(list.length).toEqual(0);
list.insertAt(420, 0);
expect(list.length).toEqual(1);
});
});
describe("filled list", () =>
{
const list = new LinkedList<number>();
list.append(420);
list.append(69);
list.append(8008135);
list.append(1337);
it("shouldn't insert if index is < 0 and > length", () =>
{
list.insertAt(10100111001, -1);
expect(list.length).toEqual(4);
list.insertAt(123, 5); // index 4 would work even though it's out of bounds because it's the next index, it's the same as doing an append
expect(list.length).toEqual(4);
});
it("should insert if index is between 0 and length", () =>
{
list.insertAt(10100111001, 0);
expect(list.length).toEqual(5);
list.insertAt(69420, 3);
expect(list.length).toEqual(6);
list.insertAt(123, 6);
expect(list.length).toEqual(7);
});
});
});
describe("getHead/getTail", () =>
{
it("should return undefined if the list is empty", () =>
{
const list = new LinkedList<number>();
expect(list.getHead()).toEqual(undefined);
expect(list.getTail()).toEqual(undefined);
});
it("should return the head and the tail values if the list has 1 or more elements", () =>
{
const list = new LinkedList<number>();
list.append(420);
list.append(69);
list.append(8008135);
expect(list.getHead()).toEqual(420);
expect(list.getTail()).toEqual(8008135);
});
});
describe("get", () =>
{
describe("empty list", () =>
{
const list = new LinkedList<number>();
it("should return undefined", () =>
{
expect(list.get(0)).toEqual(undefined);
expect(list.get(1)).toEqual(undefined);
});
});
describe("filled list", () =>
{
const list = new LinkedList<number>();
list.append(420);
list.append(69);
list.append(8008135);
list.append(1337);
it("should return undefined if index is < 0 or >= length", () =>
{
expect(list.get(-1)).toEqual(undefined);
expect(list.get(list.length)).toEqual(undefined);
});
it("should return the value if the index is between 0 and length - 1", () =>
{
expect(list.get(0)).toEqual(420);
expect(list.get(1)).toEqual(69);
expect(list.get(list.length - 1)).toEqual(1337);
});
});
});
describe("remove", () =>
{
const list = new LinkedList<number>();
list.append(420);
list.append(69);
list.append(8008135);
list.append(1337);
it("should return undefined if it doesn't find any element with the same value", () =>
{
expect(list.remove(10100111001)).toEqual(undefined);
expect(list.length).toEqual(4);
});
it("should remove an element and return it's value if one is found with the same value", () =>
{
expect(list.remove(420)).toEqual(420);
expect(list.length).toEqual(3);
expect(list.remove(8008135)).toEqual(8008135);
expect(list.length).toEqual(2);
expect(list.remove(1337)).toEqual(1337);
expect(list.length).toEqual(1);
expect(list.remove(69)).toEqual(69);
expect(list.length).toEqual(0);
});
});
describe("shift", () =>
{
describe("empty list", () =>
{
const list = new LinkedList<number>();
it("shouldn't change the list and should return undefined if list is empty", () =>
{
expect(list.shift()).toEqual(undefined);
expect(list.length).toEqual(0);
});
});
describe("filled list", () =>
{
const list = new LinkedList<number>();
list.append(420);
list.append(1337);
it("should remove the first element and return it's value", () =>
{
expect(list.shift()).toEqual(420);
expect(list.length).toEqual(1);
expect(list.shift()).toEqual(1337);
expect(list.length).toEqual(0);
});
});
});
describe("pop", () =>
{
describe("empty list", () =>
{
const list = new LinkedList<number>();
it("shouldn't change the list and should return undefined if list is empty", () =>
{
expect(list.pop()).toEqual(undefined);
expect(list.length).toEqual(0);
});
});
describe("filled list", () =>
{
const list = new LinkedList<number>();
list.append(420);
list.append(1337);
it("should remove the first element and return it's value", () =>
{
expect(list.pop()).toEqual(1337);
expect(list.length).toEqual(1);
expect(list.pop()).toEqual(420);
expect(list.length).toEqual(0);
});
});
});
describe("removeAt", () =>
{
const list = new LinkedList<number>();
list.append(420);
list.append(69);
list.append(8008135);
list.append(1337);
list.append(10100111001);
it("should return undefined if index is < 0 or >= length", () =>
{
expect(list.removeAt(-1)).toEqual(undefined);
expect(list.removeAt(list.length)).toEqual(undefined);
});
it("should remove an element and return it's value if index is between 0 and length - 1", () =>
{
expect(list.removeAt(0)).toEqual(420);
expect(list.length).toEqual(4);
expect(list.removeAt(2)).toEqual(1337);
expect(list.length).toEqual(3);
expect(list.removeAt(list.length - 1)).toEqual(10100111001);
expect(list.length).toEqual(2);
expect(list.removeAt(1)).toEqual(8008135);
expect(list.removeAt(0)).toEqual(69);
expect(list.length).toEqual(0);
});
});
});

View File

@ -0,0 +1,63 @@
import "reflect-metadata";
import { describe, expect, it } from "vitest";
import { Queue } from "@spt-aki/utils/collections/queue/Queue";
describe("LinkedList", () =>
{
describe("enqueue", () =>
{
const queue = new Queue<number>();
queue.enqueue(420);
queue.enqueue(69);
queue.enqueue(8008135);
queue.enqueue(1337);
it("adds elements to the end of the queue", () =>
{
expect(queue.peek()).toEqual(420);
expect(queue.length).toEqual(4);
});
});
describe("enqueueAll", () =>
{
const queue = new Queue<number>();
queue.enqueueAll([420, 69, 8008135, 1337]);
it("iterates the array and adds each element to the end of the queue", () =>
{
expect(queue.peek()).toEqual(420);
expect(queue.length).toEqual(4);
});
});
describe("dequeue", () =>
{
const queue = new Queue<number>();
queue.enqueueAll([420, 69, 8008135, 1337]);
it("removes the first element and return it's value", () =>
{
expect(queue.dequeue()).toEqual(420);
expect(queue.peek()).toEqual(69);
expect(queue.length).toEqual(3);
expect(queue.dequeue()).toEqual(69);
expect(queue.peek()).toEqual(8008135);
expect(queue.length).toEqual(2);
expect(queue.dequeue()).toEqual(8008135);
expect(queue.peek()).toEqual(1337);
expect(queue.length).toEqual(1);
expect(queue.dequeue()).toEqual(1337);
expect(queue.peek()).toEqual(undefined);
expect(queue.length).toEqual(0);
expect(queue.dequeue()).toEqual(undefined);
expect(queue.peek()).toEqual(undefined);
expect(queue.length).toEqual(0);
});
});
});