Going with @SomeCallMeTim 's answer, which I think is on the right track, I have this:
export class Queue {
lookup = new Map<number, any>();
first = 0;
last = 0;
length = 0;
elementExists = false; // when first === last, and item exists there
peek() {
return this.lookup.get(this.first);
}
getByIndex(v: number) {
return this.lookup.get(v);
}
getLength() {
return this.length;
}
pop() {
const last = this.last;
if (this.elementExists && this.first === this.last) {
this.length--;
this.elementExists = false;
}
else if (this.last > this.first) {
this.length--;
this.last--;
}
const v = this.lookup.get(last);
this.lookup.delete(last);
return v;
}
shift() {
const first = this.first;
if (this.elementExists && this.first === this.last) {
this.length--;
this.elementExists = false;
}
else if (this.first < this.last) {
this.length--;
this.first++;
}
const v = this.lookup.get(first);
this.lookup.delete(first);
return v;
}
push(v: any) {
this.length++;
if (this.elementExists && this.first === this.last) {
this.last++;
}
else if (this.first === this.last) {
this.elementExists = true;
}
else {
this.last++;
}
return this.lookup.set(this.last, v);
}
enq(v: any) {
return this.push.apply(this, arguments);
}
enqueue(v: any) {
return this.push.apply(this, arguments);
}
deq() {
return this.shift.apply(this, arguments);
}
dequeue() {
return this.shift.apply(this, arguments);
}
unshift(v: any) {
this.length++;
if (this.elementExists && this.first === this.last) {
this.first--;
}
else if (this.first === this.last) {
this.elementExists = true;
}
else {
this.first--;
}
return this.lookup.set(this.first, v);
}
addToFront(v: any){
return this.unshift.apply(this,arguments);
}
removeAll() {
return this.clear.apply(this, arguments);
}
clear(): void {
this.length = 0;
this.elementExists = false;
this.first = 0;
this.last = 0;
this.lookup.clear();
}
}
takeaways:
it turns out, you can call getByIndex()
, as Tim's suggestion points out.
Using Map is surprisingly ~10% faster than POJSO, possibly only because with a POJSO the integers need to get converted to strings for lookup.
The Map implementation is about 20% faster than doubly-linked list, so a doubly-linked list is not that much slower. It's probably slower mostly because we must create a container object with next/prev pointers for each item in the queue, whereas with the non-linked list implementation, we can insert primitives in the queue, etc.
The doubly-linked list allows us to remove/insert items from the middle of the queue in constant time; we cannot do the same with the non-linked list implementation as is.
All of the above are orders of magnitude more performant than a plain array when operating on an array with more than 10,000 elements or so.
I have some constant time queue implementations here:
https://github.com/ORESoftware/linked-queue
Tim had a good suggestion, to make getByIndex() easier to use - we can do this:
getByIndex(v: number) {
if(!Number.isInteger(v)){
throw new Error('Argument must be an integer.');
}
return this.lookup.get(v + this.first);
}
that way to get the 5th element in the queue, all we need to do is:
getByIndex(4);