UNPKG

@oresoftware/linked-queue

Version:

Synchronous queue implementation with constant/linear time operations.

390 lines (389 loc) 10.4 kB
'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;