UNPKG

sorted-map

Version:

a sorted map based heavily upon redis' skip list implementation

417 lines (372 loc) 11 kB
var intersect = require('./intersect'); var slice = Array.prototype.slice; var P = 1 / Math.E; function randomLevel() { var level = 1; while (Math.random() < P) level++; return level < 32 ? level : 32; } function Level(next, span) { this.next = next; this.span = span; } // value is score, sorted // key is obj, unique function Node(level, key, value) { this.key = key; this.value = value; this.next = new Array(level); this.prev = null; } function Entry(key, value) { this.key = key; this.value = value; } function Map(options) { if (!(this instanceof Map)) return new Map(); options || (options = {}); this._unique = !!options.unique; this.empty(); } Map.intersect = function() { return intersect.call(Map, slice.call(arguments)); }; Map.prototype.set = function(key, value) { var current; if (key === '__proto__') throw new Error('invalid key __proto__'); if (value == null) return this.del(key); current = this._map[key]; if (current !== undefined) { if (value === current) return current; this._remove(key, current); } var node = this._insert(key, value); if (!node) { current === undefined || this._insert(key, current); // TODO: can we defer _remove until after insert? throw new Error('unique constraint violated'); } this._map[key] = value; return current === undefined ? null : current; }; Map.prototype.has = function(key) { return this._map[key] !== undefined; }; Map.prototype.get = function(key) { var value = this._map[key]; return value === undefined ? null : value; }; Map.prototype.del = function(key) { var value = this._map[key]; if (value !== undefined) { this._remove(key, value); delete this._map[key]; return value; } return null; }; Map.prototype.rank = function(key) { var value = this._map[key]; if (value === undefined) return -1; var i = this._level - 1, node = this._head, next = null, rank = -1; for (; i >= 0; i--) { while ((next = node.next[i].next) && (next.value < value || (next.value === value && next.key <= key))) { rank += node.next[i].span; node = next; } if (node.key && node.key === key) { return rank; } } return -1; }; Map.prototype.count = function(min, max) { if (!this.length) return 0; min == null && (min = -Infinity); max == null && (max = Infinity); if (min <= this._head.next[0].next.value && max >= this._tail.value) return this.length; if (max < min || min > this._tail.value || max < this._head.next[0].next.value) return 0; var node = this._first(min), count = 0; if (!node) return 0; for (var i = node.next.length - 1; i >= 0; i--) { while (node.next[i].next && node.next[i].next.value <= max) { count += node.next[i].span; node = node.next[i].next; } } // feels hacky and error prone return count && count + 1; }; Map.prototype.range = function(min, max) { if (!this.length) return []; min == null && (min = -Infinity); max == null && (max = Infinity); if (min <= this._head.next[0].next.value && max >= this._tail.value) return this.toArray(); if (max < min || min > this._tail.value || max < this._head.next[0].next.value) return []; var node = this._first(min), result = []; // if (!node) // return result; for (; node && node.value <= max; node = node.next[0].next) result.push(new Entry(node.key, node.value)); return result; }; Map.prototype.slice = function(start, end) { if (start == null) start = 0; else if (start < 0) start = Math.max(this.length + start, 0); if (end == null) end = this.length; else if (end < 0) end = this.length + end; if (start > end || start >= this.length) return []; if (end > this.length) end = this.length; if (start === 0 && end === this.length) return this.toArray(); var i = 0, length = end - start, result = new Array(length); var node = start > 0 ? this._get(start) : this._head.next[0].next; for (; length--; node = node.next[0].next) result[i++] = new Entry(node.key, node.value); return result; }; Map.prototype.gut = function(min, max) { var result, removed = 0; if (!this.length) return 0; min == null && (min = -Infinity); max == null && (max = Infinity); if (min <= this._head.next[0].next.value && max >= this._tail.value) return removed = this.length, this.empty(), removed; var next, node = this._head, update = new Array(32), i = this._level - 1; for (; i >= 0; i--) { while ((next = node.next[i].next) && next.value < min) node = next; update[i] = node; } node = node.next[0].next; while (node && node.value <= max) { next = node.next[0].next; this._removeNode(node, update); delete this._map[node.key]; removed++; node = next; } this.length -= removed; return removed; }; Map.prototype.gutSlice = function(start, end) { var len = this.length; if (!len) return 0; if (start == null) start = 0; else if (start < 0) start = Math.max(len + start, 0); if (end == null) end = len; else if (end < 0) end = len + end; if (start > end || start >= len) return 0; if (end > len) end = len; if (start === 0 && end === len) return this.empty(), len; var node = this._head, update = new Array(32), result, traversed = -1, next; for (var i = this._level - 1; i >= 0; i--) { while ((next = node.next[i].next) && (traversed + node.next[i].span) < start) { traversed += node.next[i].span; node = next; } update[i] = node; } var removed = 0; traversed++; node = node.next[0].next; while (node && traversed < end) { next = node.next[0].next; this._removeNode(node, update); delete this._map[node.key]; removed++; traversed++; node = next; } this.length -= removed; return removed; }; // intersect values Map.prototype.intersect = function() { var maps = slice.call(arguments); maps.unshift(this); return intersect.call(this, maps); }; Map.prototype.intersectKeys = function() { var maps = slice.call(arguments); maps.unshift(this); return intersectKeys.call(this, maps); }; Map.prototype.values = function() { if (!this.length) return []; var i = 0, array = new Array(this.length), node = this._head.next[0].next; for (; node; node = node.next[0].next) array[i++] = node.value; return array; }; Map.prototype.keys = function() { if (!this.length) return []; var i = 0, array = new Array(this.length), node = this._head.next[0].next; for (; node; node = node.next[0].next) array[i++] = node.key; return array; }; Map.prototype.toArray = function() { if (!this.length) return []; var i = 0, array = new Array(this.length), node = this._head.next[0].next; for (; node; node = node.next[0].next) array[i++] = new Entry(node.key, node.value); return array; }; Map.prototype.empty = function() { this.length = 0; this._level = 1; this._map = Object.create(null); this._head = new Node(32, null, 0); this._tail = null; for (var i = 0; i < 32; i++) { // hrm this._head.next[i] = new Level(null, 0); } }; // precondition: does not already have key // in unique mode, returns null if the value already exists Map.prototype._insert = function(key, value) { var update = new Array(32), rank = new Array(32), node = this._head, next = null, i = this._level - 1; for (; i >= 0; i--) { rank[i] = i === (this._level - 1) ? 0 : rank[i + 1]; // TODO: optimize some more? while ((next = node.next[i].next) && next.value <= value) { if (next.value === value) { if (this._unique) return null; if (next.key >= key) break; } rank[i] += node.next[i].span; node = next; } if (this._unique && node.value === value) return null; update[i] = node; } if (this._unique && node.value === value) return null; var level = randomLevel(); if (level > this._level) { // TODO: optimize for (i = this._level; i < level; i++) { rank[i] = 0; update[i] = this._head; update[i].next[i].span = this.length; } this._level = level; } node = new Node(level, key, value); for (i = 0; i < level; i++) { node.next[i] = new Level(update[i].next[i].next, update[i].next[i].span - (rank[0] - rank[i])); update[i].next[i].next = node; update[i].next[i].span = (rank[0] - rank[i]) + 1; } for (i = level; i < this._level; i++) update[i].next[i].span++; node.prev = (update[0] === this._head) ? null : update[0]; if (node.next[0].next) node.next[0].next.prev = node; else this._tail = node; this.length++; return node; }; // TODO: optimize when index is less than log(N) from the end Map.prototype._get = function(index) { var node = this._head, distance = -1; for (var i = this._level - 1; i >= 0; i--) { while (node.next[i].next && (distance + node.next[i].span) <= index) { distance += node.next[i].span; node = node.next[i].next; } if (distance === index) return node; } return null; }; Map.prototype._first = function(min) { var node = this._tail; if (!node || node.value < min) return null; node = this._head; for (var next = null, i = this._level - 1; i >= 0; i--) while ((next = node.next[i].next) && next.value < min) node = next; return node.next[0].next; }; // find node after node when value >= specified value Map.prototype._next = function(value, node) { if (!this._tail || this._tail.value < value) return null; // search upwards for (var next = null; (next = node.next[node.next.length - 1].next) && next.value < value; ) node = next; if (node.value === value) return node; // search downwards for (var i = node.next.length - 1; i >= 0; i--) { while ((next = node.next[i].next) && next.value < value) node = next; if (node.value === value) return node; } return node.next[0].next; }; Map.prototype._remove = function(key, value) { var update = new Array(32), node = this._head, next; for (var i = this._level - 1; i >= 0; i--) { while ((next = node.next[i].next) && (next.value < value || (next.value === value && next.key < key))) { node = next; } update[i] = node; } node = node.next[0].next; if (!node || value !== node.value || node.key !== key) return false; // delete this._removeNode(node, update); this.length--; }; Map.prototype._removeNode = function(node, update) { var next = null, i = 0, n = this._level; for (; i < n; i++) { if (update[i].next[i].next === node) { update[i].next[i].span += node.next[i].span - 1; update[i].next[i].next = node.next[i].next; } else { update[i].next[i].span--; } } if (next = node.next[0].next) next.prev = node.prev; else this._tail = node.prev; while (this._level > 1 && !this._head.next[this._level - 1].next) this._level--; }; module.exports = Map;