rabbitmq-client
Version:
Robust, typed, RabbitMQ (0-9-1) client library
369 lines (368 loc) • 12.3 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
class Node {
key;
value;
parent;
left;
right;
size;
/** The nil node (see the Null Object Pattern), used for leaf nodes or for
* the parent of the root node. NIL nodes are immutable. This simplifies the
* rebalancing algorithms. This will throw a TypeError if modifications are
* attempted. */
static NIL = Object.freeze(new class extends Node {
toString() { return '·'; }
constructor() {
super(Symbol('nil'), Symbol('nil'));
this.size = 0;
this.parent = this.left = this.right = this;
}
}());
constructor(key, value) {
this.key = key;
this.value = value;
this.parent = this.left = this.right = Node.NIL;
this.size = 1;
}
}
/**
* Hirai, Y.; Yamamoto, K. (2011). "Balancing weight-balanced trees" (PDF).
* Journal of Functional Programming. 21 (3): 287.
* doi:10.1017/S0956796811000104.
* https://yoichihirai.com/bst.pdf
*
* Delta decides whether any rotation is made at all
*/
const Δ = 3;
/** Gamma chooses a single rotation or a double rotation. */
const Γ = 2;
/**
* Weight-Balanced Tree.
* Insertion, deletion and iteration have O(log n) time complexity where n is
* the number of entries in the tree. Items must have an absolute ordering.
*
* @internal
*/
class SortedMap {
_root;
_compare;
/**
* @param source An inital list of values
* @param compare Sorting criterum: Should run in O(1) time
*/
constructor(source, compare = (a, b) => a === b ? 0 : a < b ? -1 : 1) {
this._compare = compare;
this._root = Node.NIL;
if (source)
for (const [key, val] of source)
this._insertNode(new Node(key, val));
}
get size() { return this._root.size; }
get [Symbol.toStringTag]() { return 'SortedMap'; }
/** Traverse keys, breadth-first */
*bfs() {
if (this._root === Node.NIL)
return;
const queue = [this._root];
while (queue.length) {
const next = queue.shift();
yield next.key;
if (next.left !== Node.NIL)
queue.push(next.left);
if (next.right !== Node.NIL)
queue.push(next.right);
}
}
toString() {
return `[${this[Symbol.toStringTag]} size:${this.size}]`;
}
has(key) {
return this._findNode(key) !== Node.NIL;
}
get(key) {
const node = this._findNode(key);
return node === Node.NIL ? undefined : node.value;
}
set(key, value) {
this._insertNode(new Node(key, value));
return this;
}
delete(key) {
const node = this._findNode(key);
const result = this._deleteNode(node);
return result;
}
clear() {
this._root = Node.NIL;
}
forEach(cb, self) {
for (const [key, val] of this.entries())
cb.call(self, val, key, this);
}
[Symbol.iterator]() {
return this.entries();
}
values() {
return this._iterator(node => node.value);
}
keys() {
return this._iterator(node => node.key);
}
entries() {
return this._iterator(node => [node.key, node.value]);
}
/** Return a missing value from the set. If {4} then result = 3 so it's not
* always the LOWEST missing number but that's not necessary here. */
pick(min = 1) {
// n.b.
// to find the LOWEST missing number in [min ... STORED_VALUES] (inclusive)
// requires checking the value at rank 0 at the cost of an extra
// ~O(log n) steps, or extra book-keeping to track the stored min value.
if (this._root === Node.NIL)
return min;
let dir = 0;
// FIXME Kind of a rude typecast, but this only works when K is a number.
// Maybe a subclass makes more sense.
let node = this._root;
let parent = node;
do {
dir = (node.left.size + min) - node.key;
parent = node;
if (dir < 0) {
node = node.left;
}
else {
min += node.left.size + 1;
node = node.right;
}
} while (node !== Node.NIL);
return dir < 0 ? parent.key - 1 : parent.key + 1;
}
_iterator(get) {
// eslint-disable-next-line @typescript-eslint/no-this-alias
const tree = this;
let node = Node.NIL;
let started = false;
return {
[Symbol.iterator]() { return this; },
next() {
if (node === Node.NIL)
node = tree._firstNode();
if (started)
node = tree._nextNode(node);
started = true;
const done = node === Node.NIL;
const value = done ? undefined : get(node);
return { done, value };
// ^^^^^^^^^^^^^^^^^^^^
// See https://github.com/Microsoft/TypeScript/issues/11375
}
};
}
_firstNode(node = this._root) {
while (node.left !== Node.NIL)
node = node.left;
return node;
}
_nextNode(node) {
if (node === Node.NIL)
return node;
if (node.right !== Node.NIL)
return this._firstNode(node.right);
let parent = node.parent;
while (parent !== Node.NIL && node === parent.right) {
node = parent;
parent = parent.parent;
}
return parent;
}
_findNode(key, node = this._root) {
let dir;
while (node !== Node.NIL && (dir = this._compare(key, node.key))) {
node = dir < 0 ? node.left : node.right;
}
return node;
}
_leftRotate(node) {
// assert node !== Node.NIL
// assert node.right !== Node.NIL
const child = node.right;
node.right = child.left;
// don't attempt to modify NIL
if (child.left !== Node.NIL)
child.left.parent = node;
child.parent = node.parent;
if (node === this._root)
this._root = child;
else if (node === node.parent.left)
node.parent.left = child;
else
node.parent.right = child;
node.parent = child;
child.left = node;
child.size = node.size;
node.size = node.left.size + node.right.size + 1;
return child;
}
_rightRotate(node) {
// assert node !== Node.NIL
// assert node.left !== Node.NIL
const child = node.left;
node.left = child.right;
// don't attempt to modify NIL
if (child.right !== Node.NIL)
child.right.parent = node;
child.parent = node.parent;
if (node === this._root)
this._root = child;
else if (node === node.parent.left)
node.parent.left = child;
else
node.parent.right = child;
node.parent = child;
child.right = node;
child.size = node.size;
node.size = node.left.size + node.right.size + 1;
return child;
}
_isUnbalanced(a, b) {
return Δ * (a.size + 1) < b.size + 1;
}
_isSingle(a, b) {
return a.size + 1 < Γ * (b.size + 1);
}
_insertNode(node) {
// assert node !== Node.NIL
if (this._root === Node.NIL) {
this._root = node;
return;
}
let parent = Node.NIL;
let root = this._root;
let dir;
// find insertion point
do {
dir = this._compare(node.key, root.key);
if (dir === 0) {
return; // element is already in the tree!
}
parent = root;
root = dir < 0 ? root.left : root.right;
} while (root !== Node.NIL);
// replace leaf
node.parent = parent;
if (dir < 0)
parent.left = node;
else
parent.right = node;
// walk back down the tree and rebalance
do {
parent.size += 1;
if (parent.right === node) {
if (this._isUnbalanced(parent.left, parent.right)) {
if (this._isSingle(parent.right.left, parent.right.right)) {
parent = this._leftRotate(parent);
}
else {
this._rightRotate(parent.right);
parent = this._leftRotate(parent);
}
}
}
else {
if (this._isUnbalanced(parent.right, parent.left)) {
if (this._isSingle(parent.left.right, parent.left.left)) {
parent = this._rightRotate(parent);
}
else {
this._leftRotate(parent.left);
parent = this._rightRotate(parent);
}
}
}
node = parent;
parent = parent.parent;
} while (parent !== Node.NIL);
// in case the root node was rotated away
this._root = node;
}
_deleteNode(node) {
if (node === Node.NIL)
return false;
let child;
let parent;
if (node.left !== Node.NIL && node.right !== Node.NIL) {
// find the first child with a NIL left-pointer (in the right-hand tree)
// this will replace the deleted node
let next = node.right;
while (next.left !== Node.NIL)
next = next.left;
next.left = node.left;
next.left.parent = next;
if (node === this._root)
this._root = next;
else if (node === node.parent.left)
node.parent.left = next;
else
node.parent.right = next;
child = next.right; // may be NIL
parent = next.parent;
if (node === parent)
parent = next;
else {
if (child !== Node.NIL)
child.parent = parent;
parent.left = child;
next.right = node.right;
node.right.parent = next;
}
next.parent = node.parent;
}
else {
child = node.left === Node.NIL ? node.right : node.left; // may be NIL
parent = node.parent; // may be NIL
if (child !== Node.NIL)
child.parent = parent;
if (node === this._root)
this._root = child;
else if (parent.left === node)
parent.left = child;
else
parent.right = child;
}
node = child;
while (parent !== Node.NIL) {
parent.size = parent.left.size + parent.right.size + 1;
// left rotation is performed when an element is deleted from the left subtree
if (parent.left === node) {
if (this._isUnbalanced(parent.left, parent.right)) {
if (this._isSingle(parent.right.left, parent.right.right)) {
parent = this._leftRotate(parent);
}
else {
this._rightRotate(parent.right);
parent = this._leftRotate(parent);
}
}
}
else {
if (this._isUnbalanced(parent.right, parent.left)) {
if (this._isSingle(parent.left.right, parent.left.left)) {
parent = this._rightRotate(parent);
}
else {
this._leftRotate(parent.left);
parent = this._rightRotate(parent);
}
}
}
node = parent;
parent = parent.parent;
}
// in case the root node was rotated away
this._root = node;
return true;
}
}
exports.default = SortedMap;