UNPKG

tiny-tree

Version:

Efficient, no-dependency b-tree and binary search tree for node or the browser

309 lines (247 loc) 7.93 kB
import BTreeNode from "./b-tree-node.js"; export default class BTree { constructor(options) { options = options || {}; this._degree = options.degree >= 3 ? options.degree : 15; this.clear(); } clear() { this._root = new BTreeNode(this._degree); } get(key) { let current = this._root; while (current) { let i; for (i = 0; i < current.keys.length; i++) { if (current.keys[i] == null || current.keys[i] > key) { break; } if (current.keys[i] === key) { return current.values[i]; } } current = current.children ? current.children[i] : null; } return undefined; } getByIndex(index) { let current = this._root, iThis = 0; if (index < 0) return undefined; if (index >= this.size) return undefined; while (true) { let i; if (current.isLeafNode) { return current.values[index - iThis]; } for (i = 0; i < current.keys.length; i++) { if (current.keys[i] == null) break; if (index < iThis + current.children[i].size) { break; } else { iThis += current.children[i].size; } if (iThis === index) { return current.values[i]; } else { iThis++; } } current = current.children[i]; } } _indexAtOrAboveKey(key) { let current = this._root; let index = 0; while (current) { let i; for (i = 0; i < current.keys.length; i++) { if (current.keys[i] == null) { break; } else if (current.keys[i] < key) { if (current.children) { index += current.children[i].size; } index += 1; } else if (current.keys[i] === key) { if (current.children) { index += current.children[i].size; } return {index: index, key: current.keys[i]}; } else { if (current.children) { break; } else { return {index: index, key: current.keys[i] }; } } } current = current.children ? current.children[i] : null; } return null; } _indexAtOrBelowKey(key) { let current = this._root; let index = this.size - 1; while (current) { let i; for (i = current.keys.length - 1; i >= 0; i--) { if (current.keys[i] == null) { // ignore } else if (current.keys[i] > key) { if (current.children) { index -= current.children[i + 1].size; } index -= 1; } else if (current.keys[i] === key) { if (current.children) { index -= current.children[i + 1].size; } return {index: index, key: current.keys[i]}; } else { if (current.children) { break; } else { return {index: index, key: current.keys[i] }; } } } current = current.children ? current.children[i + 1] : null; } return null; } toArray(bounds, valuesOnly) { let indexInfo = this._boundsToIndex(bounds); return this.toArrayByIndex(indexInfo[0], indexInfo[1] - indexInfo[0] + 1, valuesOnly); } _boundsToIndex(bounds) { let start = 0, end = this.size - 1; if (bounds) { if (bounds.min != null) { let indexInfo = this._indexAtOrAboveKey(bounds.min); start = indexInfo.index; } else if (bounds.minInclusive != null) { let indexInfo = this._indexAtOrAboveKey(bounds.minInclusive); start = indexInfo.index; } else if (bounds.minExclusive != null) { let indexInfo = this._indexAtOrAboveKey(bounds.minExclusive); start = indexInfo.index; if (indexInfo.key === bounds.minExclusive) start += 1; } if (bounds.max != null) { let indexInfo = this._indexAtOrBelowKey(bounds.max); end = indexInfo.index; if (indexInfo.key === bounds.max) end -= 1; } else if (bounds.maxExclusive != null) { let indexInfo = this._indexAtOrBelowKey(bounds.maxExclusive); end = indexInfo.index; if (indexInfo.key === bounds.maxExclusive) end -= 1; } else if (bounds.maxInclusive != null) { let indexInfo = this._indexAtOrBelowKey(bounds.maxInclusive); end = indexInfo.index; } } return [start, end] } toArrayByIndex(start, count, valuesOnly) { let stack = [], ctx, index, result = [], end = start + count - 1; if (start < 0) { count += start; start = 0; } if (count < 1) return []; ctx = {node: this._root, iKey: 0, nKeys: this._root.filledKeys, isLeafNode: this._root.isLeafNode, phase: 0}; index = 0; while (ctx) { if (index > end) return result; if (ctx.isLeafNode) { if (index + ctx.node.size - 1 < start) { // skip this node index += ctx.node.size; } else { for (let i = 0; i < ctx.nKeys; i++) { if (index < start) { // skip this key index += 1; } else { if (index >= start && index <= end) { result.push(valuesOnly ? ctx.node.values[i] : [ctx.node.keys[i], ctx.node.values[i]]); } index++; } } } ctx = stack.pop(); } else if (ctx.phase === 0) { // Child segment let child = ctx.node.children[ctx.iKey]; ctx.phase = 1; if (index + child.size - 1 < start) { // skip this child index += child.size; } else { // traverse into child stack.push(ctx); ctx = {node: child, iKey: 0, nKeys: child.filledKeys, isLeafNode: child.isLeafNode, phase: 0}; } } else { // Key segment if (ctx.iKey === ctx.nKeys) { ctx = stack.pop(); } else { if (index < start) { // skip this key index += 1; } else { if (index >= start && index <= end) { result.push(valuesOnly ? ctx.node.values[ctx.iKey] : [ctx.node.keys[ctx.iKey], ctx.node.values[ctx.iKey]]); } index++; } ctx.iKey++; ctx.phase = 0; } } } return result; } set(key, value) { let result = this._root.set(key, value, false); if (result) this._root = result; } bulkLoad(data) { if (this.size) throw new Error("Bulk load not allowed on non-empty trees"); if (!data.length) return; let lastKey = data[0][0]; for (let item of data) { if (!(item[0] >= lastKey)) throw new Error("Keys must be sorted in ascending order"); lastKey = item[0]; let result = this._root.set(item[0], item[1], true); if (result) this._root = result; } this._root.completeBulkLoad(); } delete(key) { this._root.delete(key); if (!this._root.isLeafNode && this._root.keys[0] == null) { this._root = this._root.children[0]; } } getStats() { let result = { degree: this._degree, size: this.size, depth: 0, fillFactor: null, nodes: 0, saturationFactor: null, keySlots: 0, filledKeySlots: 0, saturatedNodes: 0, }; this._root._updateStats(result, 0); result.fillFactor = result.filledKeySlots / result.keySlots; result.saturationFactor = result.saturatedNodes / result.nodes; return result; } get size() {return this._root.size} }