@oresoftware/linked-queue
Version:
Synchronous queue implementation with constant/linear time operations.
390 lines (389 loc) • 10.4 kB
JavaScript
'use strict';
Object.defineProperty(exports, "__esModule", { value: true });
exports.LinkedQueue = exports.IsVoid = exports.r2gSmokeTest = void 0;
const util = require("util");
const chalk_1 = require("chalk");
const r2gSmokeTest = function () {
return true;
};
exports.r2gSmokeTest = r2gSmokeTest;
const flattenDeep = (arr) => {
return Array.isArray(arr) ? arr.reduce((a, b) => [...flattenDeep(a), ...flattenDeep(b)], []) : [arr];
};
const IsVoidVal = Symbol('null result');
exports.IsVoid = {
check: (v) => v === IsVoidVal
};
class LinkedQueue {
constructor() {
this.lookup = new Map();
this.head = null;
this.tail = null;
}
getComputedProperties() {
return {
size: this.lookup.size
};
}
toJSON() {
return {
size: this.lookup.size
};
}
[util.inspect.custom]() {
return {
size: this.lookup.size
};
}
get size() {
return this.lookup.size;
}
get length() {
return this.lookup.size;
}
getLength() {
return this.lookup.size;
}
getSize() {
return this.lookup.size;
}
iterator() {
return this;
}
getIterator() {
return this;
}
[Symbol.iterator]() {
let v = this.head;
return {
next() {
if (!v) {
return { value: null, done: true };
}
const [key, value] = [v.key, v.value];
v = v.after;
return {
value: [key, value],
done: false
};
}
};
}
reverseIterator() {
return {
[Symbol.iterator]: () => {
let v = this.tail;
return {
next() {
const r = v ? {
value: [v.key, v.value],
done: false
} : {
done: true,
value: null
};
if (v) {
v = v.before;
}
return r;
}
};
}
};
}
dequeueIterator() {
const self = this;
return {
[Symbol.iterator]() {
return {
next() {
const d = self.dequeue();
return {
value: d,
done: exports.IsVoid.check(d[0])
};
}
};
}
};
}
getRandomKey() {
const size = this.lookup.size;
if (size < 1) {
throw new Error('Cannot get random key from empty queue.');
}
const r = Math.floor(Math.random() * size);
let i = 0;
for (var k of this.lookup.keys()) {
if (i === r) {
break;
}
i++;
}
return k;
}
getRandomItem() {
try {
var v = this.lookup.get(this.getRandomKey());
}
catch (err) {
return [IsVoidVal];
}
return [v.key, v.value];
}
remove(k) {
const v = this.lookup.get(k);
this.lookup.delete(k);
if (!v) {
return [IsVoidVal];
}
let before = v.before;
let after = v.after;
if (before) {
before.after = after || null;
}
if (after) {
after.before = before || null;
}
if (this.head === v) {
this.head = v.after || null;
}
if (this.tail === v) {
this.tail = v.before || null;
}
return [v.key, v.value];
}
contains(k) {
return Boolean(this.lookup.get(k));
}
get(k) {
const v = this.lookup.get(k);
return v ? [v.key, v.value] : [IsVoidVal];
}
peek() {
return !this.head ? [IsVoidVal] : [
this.head.key,
this.head.value
];
}
getOrderedList() {
const ret = [];
let v = this.head;
while (v) {
ret.push([v.key, v.value]);
v = v.after;
}
return ret;
}
map(fn, ctx) {
let v = this.head;
let index = 0;
ctx = ctx || null;
const ret = [];
while (v) {
ret.push(fn.call(ctx, [v.key, v.value], index++));
v = v.after;
}
return ret;
}
filter(fn, ctx) {
let v = this.head;
let index = 0;
ctx = ctx || null;
const ret = [];
while (v) {
if (fn.call(ctx, [v.key, v.value], index++)) {
ret.push([v.key, v.value]);
}
v = v.after;
}
return ret;
}
insertInFrontOf() {
throw new Error('not yet implemented.');
}
insertBehind() {
throw new Error('not yet implemented.');
}
insertAtIndex() {
throw new Error('not yet implemented.');
}
first() {
return !this.head ? [IsVoidVal] : [
this.head.key,
this.head.value
];
}
last() {
return !this.tail ? [IsVoidVal] : [
this.tail.key,
this.tail.value
];
}
getReverseOrderedList() {
const ret = [];
let v = this.tail;
while (v) {
ret.push([v.key, v.value]);
v = v.before;
}
return ret;
}
removeAll() {
this.head = null;
this.tail = null;
this.lookup.clear();
}
addToFront(k, obj) {
if (arguments.length < 1) {
throw new Error(`Please pass an argument to '${this.addToFront.name}'()`);
}
if (!k) {
throw new Error(`Please pass a truthy value as the first argument to '${this.addToFront.name}'()`);
}
if (arguments.length === 1) {
obj = k;
}
if (this.lookup.get(k)) {
throw new Error(chalk_1.default.magenta(`The following object/value already exists in the queue. ${util.inspect(this.lookup.get(k).key).slice(0, 100)}`) +
chalk_1.default.magenta.bold(`Either remove the already enqueued item, or pass a unique value as the first argument to '${this.addToFront.name || 'unknown'}()'.`));
}
const v = {
value: obj,
key: k,
};
this.lookup.set(k, v);
const h = this.head;
if (h) {
if (h.before) {
throw new Error('The queue head should not have an "before" pointer.');
}
h.before = v;
}
v.after = h || null;
this.head = v;
if (!this.tail) {
this.tail = v;
}
}
enq(...args) {
for (let v of args) {
this.enqueue(v[0], v[1]);
}
}
enqueue(k, val) {
if (arguments.length < 1) {
throw new Error(`Please pass an argument to '${this.enqueue.name}()'.`);
}
if (arguments.length === 1) {
val = k;
}
if (this.lookup.get(k)) {
throw new Error(chalk_1.default.magenta(`The following object/value already exists in the queue. ${util.inspect(this.lookup.get(k).key).slice(0, 100)}. `) +
chalk_1.default.magenta.bold(`Either remove the already enqueued item, or pass a unique value as the first argument to '${this.enq.name || 'unknown'}()'.`));
}
const v = {
key: k,
value: val,
};
this.lookup.set(k, v);
const t = this.tail;
if (t) {
if (t.after) {
throw new Error('The queue tail should not have an "after" pointer.');
}
t.after = v;
}
v.before = t || null;
this.tail = v;
if (!this.head) {
this.head = v;
}
}
forEach(fn, ctx) {
let v = this.head;
let index = 0;
ctx = ctx || null;
while (v) {
fn.call(ctx, [v.key, v.value], index++);
v = v.after;
}
return this;
}
dequeueEach(fn, ctx) {
let index = 0;
ctx = ctx || null;
while (this.head) {
const h = this.head;
this.lookup.delete(this.head.key);
this.head = this.head.after || null;
if (this.head) {
this.head.before = null;
}
else {
this.tail = null;
}
fn.call(ctx, [h.key, h.value], index++);
}
return this;
}
deq(n) {
if (!Number.isInteger(n)) {
throw new Error('Must provide an integer as an argument to deq().');
}
if (n < 1) {
throw new Error('Must provide a positive integer as an argument to deq().');
}
const items = [];
let v = true;
while (v && items.length < n) {
if ((v = this.dequeue())) {
items.push(v);
}
}
return items;
}
dequeue() {
const h = this.head;
if (!h) {
if (this.tail) {
throw new Error('tail should not be defined if there is no head.');
}
return [IsVoidVal];
}
this.lookup.delete(h.key);
this.head = h.after || null;
if (this.head) {
this.head.before = null;
}
else {
this.tail = null;
}
return [
h.key,
h.value
];
}
removeLast() {
const t = this.tail;
if (!t) {
if (this.head) {
throw new Error('head should not be defined if there is no tail.');
}
return [IsVoidVal];
}
this.lookup.delete(t.key);
this.tail = t.before || null;
if (this.tail) {
this.tail.after = null;
}
else {
this.head = null;
}
return [
t.key,
t.value
];
}
}
exports.LinkedQueue = LinkedQueue;