UNPKG

@tylerbu/sorted-btree-es6

Version:

A sorted list of key-value pairs in a fast, typed in-memory B+ tree with a powerful API.

1 lines 17.5 kB
"use strict";function defaultComparator(e,t){if(Number.isFinite(e)&&Number.isFinite(t))return e-t;let r=typeof e,i=typeof t;if(r!==i)return r<i?-1:1;if("object"===r){if(null===e)return null===t?0:-1;if(null===t)return 1;if(r=typeof(e=e.valueOf()),i=typeof(t=t.valueOf()),r!==i)return r<i?-1:1}return e<t?-1:e>t?1:e===t?0:Number.isNaN(e)?Number.isNaN(t)?0:-1:Number.isNaN(t)?1:Array.isArray(e)?0:Number.NaN}function simpleComparator(e,t){return e>t?1:e<t?-1:0}Object.defineProperty(exports,"__esModule",{value:!0}),exports.EmptyBTree=exports.asSet=exports.BTree=exports.simpleComparator=exports.defaultComparator=void 0,exports.defaultComparator=defaultComparator,exports.simpleComparator=simpleComparator;class BTree{constructor(e,t,r){this._root=EmptyLeaf,this._size=0,this._maxNodeSize=r>=4?Math.min(r,256):32,this._compare=t||defaultComparator,e&&this.setPairs(e)}get size(){return this._size}get length(){return this._size}get isEmpty(){return 0===this._size}clear(){this._root=EmptyLeaf,this._size=0}forEach(e,t){return void 0!==t&&(e=e.bind(t)),this.forEachPair(((t,r)=>e(r,t,this)))}forEachPair(e,t){var r=this.minKey(),i=this.maxKey();return this.forRange(r,i,!0,e,t)}get(e,t){return this._root.get(e,t,this)}set(e,t,r){this._root.isShared&&(this._root=this._root.clone());var i=this._root.set(e,t,r,this);return!0===i||!1===i?i:(this._root=new BNodeInternal([this._root,i]),!0)}has(e){return 0!==this.forRange(e,e,!0,void 0)}delete(e){return 0!==this.editRange(e,e,!0,DeleteRange)}with(e,t,r){let i=this.clone();return i.set(e,t,r)||r?i:this}withPairs(e,t){let r=this.clone();return 0!==r.setPairs(e,t)||t?r:this}withKeys(e,t){let r=this.clone(),i=!1;for(var s=0;s<e.length;s++)i=r.set(e[s],void 0,!1)||i;return t&&!i?this:r}without(e,t){return this.withoutRange(e,e,!0,t)}withoutKeys(e,t){let r=this.clone();return r.deleteKeys(e)||!t?r:this}withoutRange(e,t,r,i){let s=this.clone();return 0===s.deleteRange(e,t,r)&&i?this:s}filter(e,t){var r,i=this.greedyClone();return i.editAll(((t,i,s)=>{if(!e(t,i,s))return r=Delete})),!r&&t?this:i}mapValues(e){var t={},r=this.greedyClone();return r.editAll(((r,i,s)=>(t.value=e(i,r,s),t))),r}reduce(e,t){let r=0,i=t;for(var s,n=this.entries(this.minKey(),ReusedArray);!(s=n.next()).done;)i=e(i,s.value,r++,this);return i}entries(e,t){var r=this.findPath(e);if(void 0===r)return iterator();var{nodequeue:i,nodeindex:s,leaf:n}=r,a=void 0!==t?1:0,h=void 0===e?-1:n.indexOf(e,0,this._compare)-1;return iterator((()=>{e:for(;;)switch(a){case 0:if(++h<n.keys.length)return{done:!1,value:[n.keys[h],n.values[h]]};a=2;continue;case 1:if(++h<n.keys.length)return t[0]=n.keys[h],t[1]=n.values[h],{done:!1,value:t};a=2;case 2:for(var e=-1;;){if(++e>=i.length){a=3;continue e}if(++s[e]<i[e].length)break}for(;e>0;e--)i[e-1]=i[e][s[e]].children,s[e-1]=0;n=i[0][s[0]],h=-1,a=void 0!==t?1:0;continue;case 3:return{done:!0,value:void 0}}}))}entriesReversed(e,t,r){if(void 0===e&&(r=void 0,void 0===(e=this.maxKey())))return iterator();var{nodequeue:i,nodeindex:s,leaf:n}=this.findPath(e)||this.findPath(this.maxKey());check(!i[0]||n===i[0][s[0]],"wat!");var a=n.indexOf(e,0,this._compare);!r&&a<n.keys.length&&this._compare(n.keys[a],e)<=0&&a++;var h=void 0!==t?1:0;return iterator((()=>{e:for(;;)switch(h){case 0:if(--a>=0)return{done:!1,value:[n.keys[a],n.values[a]]};h=2;continue;case 1:if(--a>=0)return t[0]=n.keys[a],t[1]=n.values[a],{done:!1,value:t};h=2;case 2:for(var e=-1;;){if(++e>=i.length){h=3;continue e}if(--s[e]>=0)break}for(;e>0;e--)i[e-1]=i[e][s[e]].children,s[e-1]=i[e-1].length-1;n=i[0][s[0]],a=n.keys.length,h=void 0!==t?1:0;continue;case 3:return{done:!0,value:void 0}}}))}findPath(e){var t,r,i=this._root;if(i.isLeaf)t=EmptyArray,r=EmptyArray;else{t=[],r=[];for(var s=0;!i.isLeaf;s++){if(t[s]=i.children,r[s]=void 0===e?0:i.indexOf(e,0,this._compare),r[s]>=t[s].length)return;i=t[s][r[s]]}t.reverse(),r.reverse()}return{nodequeue:t,nodeindex:r,leaf:i}}diffAgainst(e,t,r,i){if(e._compare!==this._compare)throw new Error("Tree comparators are not the same.");if(this.isEmpty||e.isEmpty){if(this.isEmpty&&e.isEmpty)return;return this.isEmpty?void 0===r?void 0:BTree.stepToEnd(BTree.makeDiffCursor(e),r):void 0===t?void 0:BTree.stepToEnd(BTree.makeDiffCursor(this),t)}const{_compare:s}=this,n=BTree.makeDiffCursor(this),a=BTree.makeDiffCursor(e);let h=!0,o=!0,l=BTree.compare(n,a,s);for(;h&&o;){const e=BTree.compare(n,a,s),{leaf:u,internalSpine:d,levelIndices:f}=n,{leaf:c,internalSpine:y,levelIndices:v}=a;if(u||c){if(0!==l)if(0===e){if(u&&c&&i){const e=u.values[f[f.length-1]],t=c.values[v[v.length-1]];if(!Object.is(e,t)){const r=i(n.currentKey,e,t);if(r&&r.break)return r.break}}}else if(e>0){if(c&&r){const e=c.values[v[v.length-1]],t=r(a.currentKey,e);if(t&&t.break)return t.break}}else if(t&&u&&0!==l){const e=u.values[f[f.length-1]],r=t(n.currentKey,e);if(r&&r.break)return r.break}}else if(!u&&!c&&0===e){const e=d.length-1,t=y.length-1,r=d[e][f[e]];if(y[t][v[t]]===r){l=0,h=BTree.step(n,!0),o=BTree.step(a,!0);continue}}l=e,e<0?h=BTree.step(n):o=BTree.step(a)}return h&&t?BTree.finishCursorWalk(n,a,s,t):o&&r?BTree.finishCursorWalk(a,n,s,r):void 0}static finishCursorWalk(e,t,r,i){const s=BTree.compare(e,t,r);if(0===s){if(!BTree.step(e))return}else s<0&&check(!1,"cursor walk terminated early");return BTree.stepToEnd(e,i)}static stepToEnd(e,t){let r=!0;for(;r;){const{leaf:i,levelIndices:s,currentKey:n}=e;if(i){const e=t(n,i.values[s[s.length-1]]);if(e&&e.break)return e.break}r=BTree.step(e)}}static makeDiffCursor(e){const{_root:t,height:r}=e;return{height:r,internalSpine:[[t]],levelIndices:[0],leaf:void 0,currentKey:t.maxKey()}}static step(e,t){const{internalSpine:r,levelIndices:i,leaf:s}=e;if(!0===t||s){const n=i.length;if(!0===t||0===i[n-1]){const t=r.length;if(0===t)return!1;const s=t-1;let a=s;for(;a>=0;){if(i[a]>0)return a<n-1&&(e.leaf=void 0,i.pop()),a<s&&(e.internalSpine=r.slice(0,a+1)),e.currentKey=r[a][--i[a]].maxKey(),!0;a--}return!1}{const t=--i[n-1];return e.currentKey=s.keys[t],!0}}{const t=r.length,s=t-1,n=r[s][i[s]];if(n.isLeaf){e.leaf=n;const r=i[t]=n.values.length-1;e.currentKey=n.keys[r]}else{const s=n.children;r[t]=s;const a=s.length-1;i[t]=a,e.currentKey=s[a].maxKey()}return!0}}static compare(e,t,r){const{height:i,currentKey:s,levelIndices:n}=e,{height:a,currentKey:h,levelIndices:o}=t,l=r(h,s);if(0!==l)return l;const u=i<a?i:a;return n.length-(i-u)-(o.length-(a-u))}keys(e){var t=this.entries(e,ReusedArray);return iterator((()=>{var e=t.next();return e.value&&(e.value=e.value[0]),e}))}values(e){var t=this.entries(e,ReusedArray);return iterator((()=>{var e=t.next();return e.value&&(e.value=e.value[1]),e}))}get maxNodeSize(){return this._maxNodeSize}minKey(){return this._root.minKey()}maxKey(){return this._root.maxKey()}clone(){this._root.isShared=!0;var e=new BTree(void 0,this._compare,this._maxNodeSize);return e._root=this._root,e._size=this._size,e}greedyClone(e){var t=new BTree(void 0,this._compare,this._maxNodeSize);return t._root=this._root.greedyClone(e),t._size=this._size,t}toArray(e=2147483647){let t=this.minKey(),r=this.maxKey();return void 0!==t?this.getRange(t,r,!0,e):[]}keysArray(){var e=[];return this._root.forRange(this.minKey(),this.maxKey(),!0,!1,this,0,((t,r)=>{e.push(t)})),e}valuesArray(){var e=[];return this._root.forRange(this.minKey(),this.maxKey(),!0,!1,this,0,((t,r)=>{e.push(r)})),e}toString(){return this.toArray().toString()}setIfNotPresent(e,t){return this.set(e,t,!1)}nextHigherPair(e,t){return t=t||[],void 0===e?this._root.minPair(t):this._root.getPairOrNextHigher(e,this._compare,!1,t)}nextHigherKey(e){var t=this.nextHigherPair(e,ReusedArray);return t&&t[0]}nextLowerPair(e,t){return t=t||[],void 0===e?this._root.maxPair(t):this._root.getPairOrNextLower(e,this._compare,!1,t)}nextLowerKey(e){var t=this.nextLowerPair(e,ReusedArray);return t&&t[0]}getPairOrNextLower(e,t){return this._root.getPairOrNextLower(e,this._compare,!0,t||[])}getPairOrNextHigher(e,t){return this._root.getPairOrNextHigher(e,this._compare,!0,t||[])}changeIfPresent(e,t){return 0!==this.editRange(e,e,!0,((e,r)=>({value:t})))}getRange(e,t,r,i=67108863){var s=[];return this._root.forRange(e,t,r,!1,this,0,((e,t)=>(s.push([e,t]),s.length>i?Break:void 0))),s}setPairs(e,t){for(var r=0,i=0;i<e.length;i++)this.set(e[i][0],e[i][1],t)&&r++;return r}forRange(e,t,r,i,s){var n=this._root.forRange(e,t,r,!1,this,s||0,i);return"number"==typeof n?n:n.break}editRange(e,t,r,i,s){var n=this._root;n.isShared&&(this._root=n=n.clone());try{var a=n.forRange(e,t,r,!0,this,s||0,i);return"number"==typeof a?a:a.break}finally{let e;for(;n.keys.length<=1&&!n.isLeaf;)e||(e=n.isShared),this._root=n=0===n.keys.length?EmptyLeaf:n.children[0];e&&(n.isShared=!0)}}editAll(e,t){return this.editRange(this.minKey(),this.maxKey(),!0,e,t)}deleteRange(e,t,r){return this.editRange(e,t,r,DeleteRange)}deleteKeys(e){for(var t=0,r=0;t<e.length;t++)this.delete(e[t])&&r++;return r}get height(){let e=this._root,t=-1;for(;e;)t++,e=e.isLeaf?void 0:e.children[0];return t}freeze(){var e=this;e.clear=e.set=e.editRange=function(){throw new Error("Attempted to modify a frozen BTree")}}unfreeze(){delete this.clear,delete this.set,delete this.editRange}get isFrozen(){return this.hasOwnProperty("editRange")}checkValid(){var e=this._root.checkValid(0,this,0);check(e===this.size,"size mismatch: counted ",e,"but stored",this.size)}}function asSet(e){return e}function iterator(e=(()=>({done:!0,value:void 0}))){var t={next:e};return Symbol&&Symbol.iterator&&(t[Symbol.iterator]=function(){return this}),t}exports.BTree=BTree,exports.asSet=asSet,Symbol&&Symbol.iterator&&(BTree.prototype[Symbol.iterator]=BTree.prototype.entries),BTree.prototype.where=BTree.prototype.filter,BTree.prototype.setRange=BTree.prototype.setPairs,BTree.prototype.add=BTree.prototype.set;class BNode{get isLeaf(){return void 0===this.children}constructor(e=[],t){this.keys=e,this.values=t||undefVals,this.isShared=void 0}maxKey(){return this.keys[this.keys.length-1]}indexOf(e,t,r){const i=this.keys;for(var s=0,n=i.length,a=n>>1;s<n;){var h=r(i[a],e);if(h<0)s=a+1;else{if(!(h>0)){if(0===h)return a;if(e==e)return i.length;throw new Error("BTree: NaN was used as a key")}n=a}a=s+n>>1}return a^t}minKey(){return this.keys[0]}minPair(e){if(0!==this.keys.length)return e[0]=this.keys[0],e[1]=this.values[0],e}maxPair(e){if(0===this.keys.length)return;const t=this.keys.length-1;return e[0]=this.keys[t],e[1]=this.values[t],e}clone(){var e=this.values;return new BNode(this.keys.slice(0),e===undefVals?e:e.slice(0))}greedyClone(e){return this.isShared&&!e?this:this.clone()}get(e,t,r){var i=this.indexOf(e,-1,r._compare);return i<0?t:this.values[i]}getPairOrNextLower(e,t,r,i){var s=this.indexOf(e,-1,t);const n=s<0?~s-1:r?s:s-1;if(n>=0)return i[0]=this.keys[n],i[1]=this.values[n],i}getPairOrNextHigher(e,t,r,i){var s=this.indexOf(e,-1,t);const n=s<0?~s:r?s:s+1,a=this.keys;if(n<a.length)return i[0]=a[n],i[1]=this.values[n],i}checkValid(e,t,r){var i=this.keys.length,s=this.values.length;return check(this.values===undefVals?i<=s:i===s,"keys/values length mismatch: depth",e,"with lengths",i,s,"and baseIndex",r),check(0==e||i>0,"empty leaf at depth",e,"and baseIndex",r),i}set(e,t,r,i){var s=this.indexOf(e,-1,i._compare);if(s<0){if(s=~s,i._size++,this.keys.length<i._maxNodeSize)return this.insertInLeaf(s,e,t,i);var n=this.splitOffRightSide(),a=this;return s>this.keys.length&&(s-=this.keys.length,a=n),a.insertInLeaf(s,e,t,i),n}return!1!==r&&(void 0!==t&&this.reifyValues(),this.keys[s]=e,this.values[s]=t),!1}reifyValues(){return this.values===undefVals?this.values=this.values.slice(0,this.keys.length):this.values}insertInLeaf(e,t,r,i){if(this.keys.splice(e,0,t),this.values===undefVals){for(;undefVals.length<i._maxNodeSize;)undefVals.push(void 0);if(void 0===r)return!0;this.values=undefVals.slice(0,this.keys.length-1)}return this.values.splice(e,0,r),!0}takeFromRight(e){var t=this.values;e.values===undefVals?t!==undefVals&&t.push(void 0):(t=this.reifyValues()).push(e.values.shift()),this.keys.push(e.keys.shift())}takeFromLeft(e){var t=this.values;e.values===undefVals?t!==undefVals&&t.unshift(void 0):(t=this.reifyValues()).unshift(e.values.pop()),this.keys.unshift(e.keys.pop())}splitOffRightSide(){var e=this.keys.length>>1,t=this.keys.splice(e),r=this.values===undefVals?undefVals:this.values.splice(e);return new BNode(t,r)}forRange(e,t,r,i,s,n,a){var h,o,l=s._compare;if(t===e){if(!r)return n;if(o=(h=this.indexOf(e,-1,l))+1,h<0)return n}else h=this.indexOf(e,0,l),(o=this.indexOf(t,-1,l))<0?o=~o:!0===r&&o++;var u=this.keys,d=this.values;if(void 0!==a)for(var f=h;f<o;f++){var c=u[f],y=a(c,d[f],n++);if(void 0!==y){if(!0===i){if(c!==u[f]||!0===this.isShared)throw new Error("BTree illegally changed or cloned in editRange");y.delete?(this.keys.splice(f,1),this.values!==undefVals&&this.values.splice(f,1),s._size--,f--,o--):y.hasOwnProperty("value")&&(d[f]=y.value)}if(void 0!==y.break)return y}}else n+=o-h;return n}mergeSibling(e,t){if(this.keys.push.apply(this.keys,e.keys),this.values===undefVals){if(e.values===undefVals)return;this.values=this.values.slice(0,this.keys.length)}this.values.push.apply(this.values,e.reifyValues())}}class BNodeInternal extends BNode{constructor(e,t){if(!t){t=[];for(var r=0;r<e.length;r++)t[r]=e[r].maxKey()}super(t),this.children=e}clone(){for(var e=this.children.slice(0),t=0;t<e.length;t++)e[t].isShared=!0;return new BNodeInternal(e,this.keys.slice(0))}greedyClone(e){if(this.isShared&&!e)return this;for(var t=new BNodeInternal(this.children.slice(0),this.keys.slice(0)),r=0;r<t.children.length;r++)t.children[r]=t.children[r].greedyClone(e);return t}minKey(){return this.children[0].minKey()}minPair(e){return this.children[0].minPair(e)}maxPair(e){return this.children[this.children.length-1].maxPair(e)}get(e,t,r){var i=this.indexOf(e,0,r._compare),s=this.children;return i<s.length?s[i].get(e,t,r):void 0}getPairOrNextLower(e,t,r,i){var s=this.indexOf(e,0,t),n=this.children;if(s>=n.length)return this.maxPair(i);const a=n[s].getPairOrNextLower(e,t,r,i);return void 0===a&&s>0?n[s-1].maxPair(i):a}getPairOrNextHigher(e,t,r,i){var s=this.indexOf(e,0,t),n=this.children,a=n.length;if(s>=a)return;const h=n[s].getPairOrNextHigher(e,t,r,i);return void 0===h&&s<a-1?n[s+1].minPair(i):h}checkValid(e,t,r){let i=this.keys.length,s=this.children.length;check(i===s,"keys/children length mismatch: depth",e,"lengths",i,s,"baseIndex",r),check(i>1||e>0,"internal node has length",i,"at depth",e,"baseIndex",r);let n=0,a=this.children,h=this.keys,o=0;for(var l=0;l<s;l++)n+=a[l].checkValid(e+1,t,r+n),o+=a[l].keys.length,check(n>=o,"wtf",r),check(0===l||a[l-1].constructor===a[l].constructor,"type mismatch, baseIndex:",r),a[l].maxKey()!=h[l]&&check(!1,"keys[",l,"] =",h[l],"is wrong, should be ",a[l].maxKey(),"at depth",e,"baseIndex",r),0===l||t._compare(h[l-1],h[l])<0||check(!1,"sort violation at depth",e,"index",l,"keys",h[l-1],h[l]);let u=0===o;return(u||o>t.maxNodeSize*s)&&check(!1,u?"too few":"too many","children (",o,n,") at depth",e,"maxNodeSize:",t.maxNodeSize,"children.length:",s,"baseIndex:",r),n}set(e,t,r,i){var s,n=this.children,a=i._maxNodeSize,h=i._compare,o=Math.min(this.indexOf(e,0,h),n.length-1),l=n[o];(l.isShared&&(n[o]=l=l.clone()),l.keys.length>=a)&&(o>0&&(s=n[o-1]).keys.length<a&&h(l.keys[0],e)<0?(s.isShared&&(n[o-1]=s=s.clone()),s.takeFromRight(l),this.keys[o-1]=s.maxKey()):void 0!==(s=n[o+1])&&s.keys.length<a&&h(l.maxKey(),e)<0&&(s.isShared&&(n[o+1]=s=s.clone()),s.takeFromLeft(l),this.keys[o]=n[o].maxKey()));var u=l.set(e,t,r,i);if(!1===u)return!1;if(this.keys[o]=l.maxKey(),!0===u)return!0;if(this.keys.length<a)return this.insert(o+1,u),!0;var d=this.splitOffRightSide(),f=this;return h(u.maxKey(),this.maxKey())>0&&(f=d,o-=this.keys.length),f.insert(o+1,u),d}insert(e,t){this.children.splice(e,0,t),this.keys.splice(e,0,t.maxKey())}splitOffRightSide(){var e=this.children.length>>1;return new BNodeInternal(this.children.splice(e),this.keys.splice(e))}takeFromRight(e){this.keys.push(e.keys.shift()),this.children.push(e.children.shift())}takeFromLeft(e){this.keys.unshift(e.keys.pop()),this.children.unshift(e.children.pop())}forRange(e,t,r,i,s,n,a){var h=s._compare,o=this.keys,l=this.children,u=this.indexOf(e,0,h),d=u,f=Math.min(t===e?u:this.indexOf(t,0,h),o.length-1);if(i){if(d<=f)try{for(;d<=f;d++){l[d].isShared&&(l[d]=l[d].clone());y=l[d].forRange(e,t,r,i,s,n,a);if(o[d]=l[d].maxKey(),"number"!=typeof y)return y;n=y}}finally{var c=s._maxNodeSize>>1;for(u>0&&u--,d=f;d>=u;d--)l[d].keys.length<=c&&(0!==l[d].keys.length?this.tryMerge(d,s._maxNodeSize):(o.splice(d,1),l.splice(d,1)));0!==l.length&&0===l[0].keys.length&&check(!1,"emptiness bug")}}else for(;d<=f;d++){var y;if("number"!=typeof(y=l[d].forRange(e,t,r,i,s,n,a)))return y;n=y}return n}tryMerge(e,t){var r=this.children;return e>=0&&e+1<r.length&&r[e].keys.length+r[e+1].keys.length<=t&&(r[e].isShared&&(r[e]=r[e].clone()),r[e].mergeSibling(r[e+1],t),r.splice(e+1,1),this.keys.splice(e+1,1),this.keys[e]=r[e].maxKey(),!0)}mergeSibling(e,t){var r=this.keys.length;this.keys.push.apply(this.keys,e.keys);const i=e.children;if(this.children.push.apply(this.children,i),e.isShared&&!this.isShared)for(var s=0;s<i.length;s++)i[s].isShared=!0;this.tryMerge(r-1,t)}}var undefVals=[];const Delete={delete:!0},DeleteRange=()=>Delete,Break={break:!0},EmptyLeaf=function(){var e=new BNode;return e.isShared=!0,e}(),EmptyArray=[],ReusedArray=[];function check(e,...t){if(!e)throw t.unshift("B+ tree"),new Error(t.join(" "))}exports.EmptyBTree=(()=>{let e=new BTree;return e.freeze(),e})();