mirror of
https://github.com/brendan-ch/project-inter-server.git
synced 2026-04-16 23:40:32 +00:00
Add CircularQueue.ts and test file
This commit is contained in:
@@ -2,6 +2,8 @@ export class CircularQueue<T> {
|
|||||||
private startIndex: number;
|
private startIndex: number;
|
||||||
private endIndex: number;
|
private endIndex: number;
|
||||||
private _data: T[];
|
private _data: T[];
|
||||||
|
private _size: number;
|
||||||
|
private _capacity: number;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
size: number,
|
size: number,
|
||||||
@@ -10,18 +12,103 @@ export class CircularQueue<T> {
|
|||||||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Indexed_collections#sparse_arrays
|
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Indexed_collections#sparse_arrays
|
||||||
this._data = new Array<T>(size);
|
this._data = new Array<T>(size);
|
||||||
this.startIndex = 0;
|
this.startIndex = 0;
|
||||||
this.endIndex = size - 1;
|
this.endIndex = 0;
|
||||||
|
this._size = 0;
|
||||||
|
this._capacity = size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
size = (): number => this._size;
|
||||||
|
|
||||||
|
get = (index: number): T | undefined => {
|
||||||
|
if (index < 0 || index >= this._size) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const actualIndex = (this.startIndex + index) % this._capacity;
|
||||||
|
return this._data[actualIndex];
|
||||||
|
};
|
||||||
|
|
||||||
appendWithSorting = (
|
appendWithSorting = (
|
||||||
data: T,
|
data: T,
|
||||||
sortingCallback: ((a: T, b: T) => number) | undefined
|
sortingCallback: (a: T, b: T) => number
|
||||||
) => {
|
) => {
|
||||||
// In case something is added that's not sorted, the sortingCallback
|
if (this._size === 0) {
|
||||||
// will be used to sort
|
this._data[this.startIndex] = data;
|
||||||
|
this._size = 1;
|
||||||
|
this.endIndex = this.startIndex;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._size < this._capacity) {
|
||||||
|
this.endIndex = (this.endIndex + 1) % this._capacity;
|
||||||
|
this._data[this.endIndex] = data;
|
||||||
|
this._size++;
|
||||||
|
} else {
|
||||||
|
this.startIndex = (this.startIndex + 1) % this._capacity;
|
||||||
|
this.endIndex = (this.endIndex + 1) % this._capacity;
|
||||||
|
this._data[this.endIndex] = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.sortData(sortingCallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
popFront = (data: T) => {
|
popFront = () => {
|
||||||
|
if (this._size === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._data[this.startIndex] = undefined as any;
|
||||||
|
if (this._size === 1) {
|
||||||
|
this._size = 0;
|
||||||
|
this.startIndex = 0;
|
||||||
|
this.endIndex = 0;
|
||||||
|
} else {
|
||||||
|
this.startIndex = (this.startIndex + 1) % this._capacity;
|
||||||
|
this._size--;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
binarySearch = <K>(
|
||||||
|
searchKey: K,
|
||||||
|
keyExtractor: (item: T) => K
|
||||||
|
): T | undefined => {
|
||||||
|
if (this._size === 0) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
let left = 0;
|
||||||
|
let right = this._size - 1;
|
||||||
|
|
||||||
|
while (left <= right) {
|
||||||
|
const mid = Math.floor((left + right) / 2);
|
||||||
|
const midItem = this.get(mid)!;
|
||||||
|
const midKey = keyExtractor(midItem);
|
||||||
|
|
||||||
|
if (midKey === searchKey) {
|
||||||
|
return midItem;
|
||||||
|
} else if (midKey < searchKey) {
|
||||||
|
left = mid + 1;
|
||||||
|
} else {
|
||||||
|
right = mid - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
private sortData = (sortingCallback: (a: T, b: T) => number) => {
|
||||||
|
const items: T[] = [];
|
||||||
|
for (let i = 0; i < this._size; i++) {
|
||||||
|
const item = this.get(i);
|
||||||
|
if (item !== undefined) {
|
||||||
|
items.push(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
items.sort(sortingCallback);
|
||||||
|
|
||||||
|
for (let i = 0; i < items.length; i++) {
|
||||||
|
const actualIndex = (this.startIndex + i) % this._capacity;
|
||||||
|
this._data[actualIndex] = items[i];
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
176
test/types/CircularQueue.test.ts
Normal file
176
test/types/CircularQueue.test.ts
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
import { describe, expect, it } from "@jest/globals";
|
||||||
|
import { CircularQueue } from "../../src/types/CircularQueue";
|
||||||
|
|
||||||
|
interface TestItem {
|
||||||
|
id: number;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("CircularQueue", () => {
|
||||||
|
const testItems = {
|
||||||
|
first: { id: 1, value: "first" },
|
||||||
|
second: { id: 2, value: "second" },
|
||||||
|
third: { id: 3, value: "third" },
|
||||||
|
fourth: { id: 4, value: "fourth" },
|
||||||
|
test: { id: 1, value: "test" },
|
||||||
|
apple: { id: 1, value: "apple" },
|
||||||
|
banana: { id: 2, value: "banana" },
|
||||||
|
cherry: { id: 3, value: "cherry" },
|
||||||
|
grape: { id: 5, value: "grape" },
|
||||||
|
orange: { id: 7, value: "orange" },
|
||||||
|
a: { id: 1, value: "a" },
|
||||||
|
b: { id: 2, value: "b" },
|
||||||
|
c: { id: 3, value: "c" },
|
||||||
|
d: { id: 4, value: "d" }
|
||||||
|
};
|
||||||
|
|
||||||
|
const sortingCallbacks = {
|
||||||
|
byId: (a: TestItem, b: TestItem) => a.id - b.id,
|
||||||
|
byValue: (a: TestItem, b: TestItem) => a.value.localeCompare(b.value)
|
||||||
|
};
|
||||||
|
|
||||||
|
const keyExtractors = {
|
||||||
|
id: (item: TestItem) => item.id,
|
||||||
|
value: (item: TestItem) => item.value
|
||||||
|
};
|
||||||
|
|
||||||
|
const createQueueWithItems = (size: number, items: TestItem[], sortingCallback: (a: TestItem, b: TestItem) => number) => {
|
||||||
|
const queue = new CircularQueue<TestItem>(size);
|
||||||
|
items.forEach(item => queue.appendWithSorting(item, sortingCallback));
|
||||||
|
return queue;
|
||||||
|
};
|
||||||
|
|
||||||
|
describe("constructor", () => {
|
||||||
|
it("creates queue with specified size", () => {
|
||||||
|
const queue = new CircularQueue<TestItem>(5);
|
||||||
|
expect(queue).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("appendWithSorting", () => {
|
||||||
|
it("adds items to the queue with sorting callback", () => {
|
||||||
|
const queue = createQueueWithItems(3, [testItems.third, testItems.first, testItems.second], sortingCallbacks.byId);
|
||||||
|
|
||||||
|
expect(queue.size()).toBe(3);
|
||||||
|
expect(queue.get(0)).toEqual(testItems.first);
|
||||||
|
expect(queue.get(1)).toEqual(testItems.second);
|
||||||
|
expect(queue.get(2)).toEqual(testItems.third);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("overwrites oldest items when queue is full", () => {
|
||||||
|
const queue = createQueueWithItems(2, [testItems.first, testItems.second, testItems.third], sortingCallbacks.byId);
|
||||||
|
|
||||||
|
expect(queue.size()).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("handles appending to empty queue", () => {
|
||||||
|
const queue = createQueueWithItems(3, [testItems.test], sortingCallbacks.byId);
|
||||||
|
|
||||||
|
expect(queue.size()).toBe(1);
|
||||||
|
expect(queue.get(0)).toEqual(testItems.test);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("popFront", () => {
|
||||||
|
it("removes the oldest item from queue", () => {
|
||||||
|
const queue = createQueueWithItems(3, [testItems.first, testItems.second], sortingCallbacks.byId);
|
||||||
|
|
||||||
|
expect(queue.size()).toBe(2);
|
||||||
|
queue.popFront();
|
||||||
|
expect(queue.size()).toBe(1);
|
||||||
|
expect(queue.get(0)).toEqual(testItems.second);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("handles popping from empty queue", () => {
|
||||||
|
const queue = new CircularQueue<TestItem>(3);
|
||||||
|
|
||||||
|
expect(() => queue.popFront()).not.toThrow();
|
||||||
|
expect(queue.size()).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("handles popping until empty", () => {
|
||||||
|
const queue = createQueueWithItems(2, [testItems.first, testItems.second], sortingCallbacks.byId);
|
||||||
|
|
||||||
|
queue.popFront();
|
||||||
|
expect(queue.size()).toBe(1);
|
||||||
|
queue.popFront();
|
||||||
|
expect(queue.size()).toBe(0);
|
||||||
|
queue.popFront();
|
||||||
|
expect(queue.size()).toBe(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("binarySearch", () => {
|
||||||
|
it("finds item using key extractor function", () => {
|
||||||
|
const queue = createQueueWithItems(5, [testItems.apple, testItems.cherry, testItems.grape, testItems.orange], sortingCallbacks.byId);
|
||||||
|
|
||||||
|
const result = queue.binarySearch(5, keyExtractors.id);
|
||||||
|
|
||||||
|
expect(result).toEqual(testItems.grape);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns undefined when item not found", () => {
|
||||||
|
const queue = createQueueWithItems(5, [testItems.apple, testItems.cherry, testItems.orange], sortingCallbacks.byId);
|
||||||
|
|
||||||
|
const result = queue.binarySearch(5, keyExtractors.id);
|
||||||
|
|
||||||
|
expect(result).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("finds first item", () => {
|
||||||
|
const queue = createQueueWithItems(5, [testItems.apple, testItems.cherry, testItems.orange], sortingCallbacks.byId);
|
||||||
|
|
||||||
|
const result = queue.binarySearch(1, keyExtractors.id);
|
||||||
|
|
||||||
|
expect(result).toEqual(testItems.apple);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("finds last item", () => {
|
||||||
|
const queue = createQueueWithItems(5, [testItems.apple, testItems.cherry, testItems.orange], sortingCallbacks.byId);
|
||||||
|
|
||||||
|
const result = queue.binarySearch(7, keyExtractors.id);
|
||||||
|
|
||||||
|
expect(result).toEqual(testItems.orange);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns undefined for empty queue", () => {
|
||||||
|
const queue = new CircularQueue<TestItem>(5);
|
||||||
|
const result = queue.binarySearch(1, keyExtractors.id);
|
||||||
|
|
||||||
|
expect(result).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("works with string keys", () => {
|
||||||
|
const queue = createQueueWithItems(5, [testItems.apple, testItems.banana, testItems.cherry], sortingCallbacks.byValue);
|
||||||
|
|
||||||
|
const result = queue.binarySearch("banana", keyExtractors.value);
|
||||||
|
|
||||||
|
expect(result).toEqual(testItems.banana);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("maintains sorted order assumption", () => {
|
||||||
|
const queue = createQueueWithItems(5, [testItems.d, testItems.a, testItems.c, testItems.b], sortingCallbacks.byValue);
|
||||||
|
|
||||||
|
expect(queue.binarySearch("a", keyExtractors.value)).toEqual(testItems.a);
|
||||||
|
expect(queue.binarySearch("b", keyExtractors.value)).toEqual(testItems.b);
|
||||||
|
expect(queue.binarySearch("c", keyExtractors.value)).toEqual(testItems.c);
|
||||||
|
expect(queue.binarySearch("d", keyExtractors.value)).toEqual(testItems.d);
|
||||||
|
expect(queue.binarySearch("z", keyExtractors.value)).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("integration", () => {
|
||||||
|
it("handles appendWithSorting, popFront, and binarySearch together", () => {
|
||||||
|
const queue = createQueueWithItems(3, [testItems.third, testItems.first, testItems.second], sortingCallbacks.byId);
|
||||||
|
|
||||||
|
expect(queue.binarySearch(2, keyExtractors.id)).toEqual(testItems.second);
|
||||||
|
|
||||||
|
queue.popFront();
|
||||||
|
expect(queue.binarySearch(1, keyExtractors.id)).toBeUndefined();
|
||||||
|
expect(queue.binarySearch(2, keyExtractors.id)).toEqual(testItems.second);
|
||||||
|
|
||||||
|
queue.appendWithSorting(testItems.fourth, sortingCallbacks.byId);
|
||||||
|
expect(queue.binarySearch(4, keyExtractors.id)).toEqual(testItems.fourth);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user