@tylerbu/sorted-btree-es6
Version:
A sorted list of key-value pairs in a fast, typed in-memory B+ tree with a powerful API.
2 lines (1 loc) • 16.6 kB
JavaScript
function defaultComparator(e,t){if(Number.isFinite(e)&&Number.isFinite(t))return e-t;var i=typeof e,r=typeof t;if(i!=r)return i<r?-1:1;if("object"==i){if(null===e)return null===t?0:-1;if(null===t)return 1;if((i=typeof(e=e.valueOf()))!=(r=typeof(t=t.valueOf())))return i<r?-1:1}return e<t?-1:t<e?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 t<e?1:e<t?-1:0}function fixMaxSize(e){return 4<=e?Math.min(0|e,256):32}Object.defineProperty(exports,"__esModule",{value:!0}),exports.EmptyBTree=exports.BNodeInternal=exports.BNode=exports.BTree=void 0,exports.defaultComparator=defaultComparator,exports.simpleComparator=simpleComparator,exports.fixMaxSize=fixMaxSize,exports.asSet=asSet,exports.sumChildSizes=sumChildSizes,exports.areOverlapping=areOverlapping,exports.check=check;class BTree{constructor(e,t,i){this._root=EmptyLeaf,this._maxNodeSize=fixMaxSize(i),this._compare=t||defaultComparator,e&&this.setPairs(e)}get size(){return this._root.size()}get length(){return this.size}get isEmpty(){return 0===this._root.size()}clear(){this._root=EmptyLeaf}forEach(i,e){return void 0!==e&&(i=i.bind(e)),this.forEachPair((e,t)=>i(t,e,this))}forEachPair(e,t){var i=this.minKey(),r=this.maxKey();return this.forRange(i,r,!0,e,t)}get(e,t){return this._root.get(e,t,this)}set(e,t,i){this._root.isShared&&(this._root=this._root.clone());e=this._root.set(e,t,i,this);return!0===e||!1===e?e:(t=[this._root,e],this._root=new BNodeInternal(t,sumChildSizes(t)),!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,i){var r=this.clone();return r.set(e,t,i)||i?r:this}withPairs(e,t){var i=this.clone();return 0!==i.setPairs(e,t)||t?i:this}withKeys(e,t){let i=this.clone(),r=!1;for(var s=0;s<e.length;s++)r=i.set(e[s],void 0,!1)||r;return t&&!r?this:i}without(e,t){return this.withoutRange(e,e,!0,t)}withoutKeys(e,t){var i=this.clone();return i.deleteKeys(e)||!t?i:this}withoutRange(e,t,i,r){var s=this.clone();return 0===s.deleteRange(e,t,i)&&r?this:s}filter(r,e){var s,t=this.greedyClone();return t.editAll((e,t,i)=>{if(!r(e,t,i))return s=Delete}),!s&&e?this:t}mapValues(r){var s={},e=this.greedyClone();return e.editAll((e,t,i)=>(s.value=r(t,e,i),s)),e}reduce(e,t){let i=0,r=t,s=this.entries(this.minKey(),ReusedArray),h;for(;!(h=s.next()).done;)r=e(r,h.value,i++,this);return r}entries(e,t){var i,r,s,h,n,a=this.findPath(e);return void 0===a?iterator():({nodequeue:i,nodeindex:r,leaf:s}=a,h=void 0!==t?1:0,n=void 0===e?-1:s.indexOf(e,0,this._compare)-1,iterator(()=>{e:for(;;)switch(h){case 0:if(++n<s.keys.length)return{done:!1,value:[s.keys[n],s.values[n]]};h=2;continue;case 1:if(++n<s.keys.length)return t[0]=s.keys[n],t[1]=s.values[n],{done:!1,value:t};h=2;case 2:for(var e=-1;;){if(++e>=i.length){h=3;continue e}if(++r[e]<i[e].length)break}for(;0<e;e--)i[e-1]=i[e][r[e]].children,r[e-1]=0;s=i[0][r[0]],n=-1,h=void 0!==t?1:0;continue;case 3:return{done:!0,value:void 0}}}))}entriesReversed(e,t,i){var r,s,h,n,a;return void 0===e&&(i=void 0)===(e=this.maxKey())?iterator():({nodequeue:r,nodeindex:s,leaf:h}=this.findPath(e)||this.findPath(this.maxKey()),check(!r[0]||h===r[0][s[0]],"wat!"),n=h.indexOf(e,0,this._compare),!i&&n<h.keys.length&&this._compare(h.keys[n],e)<=0&&n++,a=void 0!==t?1:0,iterator(()=>{e:for(;;)switch(a){case 0:if(0<=--n)return{done:!1,value:[h.keys[n],h.values[n]]};a=2;continue;case 1:if(0<=--n)return t[0]=h.keys[n],t[1]=h.values[n],{done:!1,value:t};a=2;case 2:for(var e=-1;;){if(++e>=r.length){a=3;continue e}if(0<=--s[e])break}for(;0<e;e--)r[e-1]=r[e][s[e]].children,s[e-1]=r[e-1].length-1;h=r[0][s[0]],n=h.keys.length,a=void 0!==t?1:0;continue;case 3:return{done:!0,value:void 0}}}))}findPath(e){var t=this._root;if(t.isLeaf)r=i=EmptyArray;else{for(var i=[],r=[],s=0;!t.isLeaf;s++){if(i[s]=t.children,r[s]=void 0===e?0:t.indexOf(e,0,this._compare),r[s]>=i[s].length)return;t=i[s][r[s]]}i.reverse(),r.reverse()}return{nodequeue:i,nodeindex:r,leaf:t}}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}greedyClone(e){var t=new BTree(void 0,this._compare,this._maxNodeSize);return t._root=this._root.greedyClone(e),t}toArray(e=2147483647){var t=this.minKey(),i=this.maxKey();return void 0!==t?this.getRange(t,i,!0,e):[]}keysArray(){var i=[];return this._root.forRange(this.minKey(),this.maxKey(),!0,!1,this,0,(e,t)=>{i.push(e)}),i}valuesArray(){var i=[];return this._root.forRange(this.minKey(),this.maxKey(),!0,!1,this,0,(e,t)=>{i.push(t)}),i}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){e=this.nextHigherPair(e,ReusedArray);return e&&e[0]}nextLowerPair(e,t){return t=t||[],void 0===e?this._root.maxPair(t):this._root.getPairOrNextLower(e,this._compare,!1,t)}nextLowerKey(e){e=this.nextLowerPair(e,ReusedArray);return e&&e[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,i){return 0!==this.editRange(e,e,!0,(e,t)=>({value:i}))}getRange(e,t,i,r=67108863){var s=[];return this._root.forRange(e,t,i,!1,this,0,(e,t)=>(s.push([e,t]),r<s.length?Break:void 0)),s}setPairs(e,t){for(var i=0,r=0;r<e.length;r++)this.set(e[r][0],e[r][1],t)&&i++;return i}forRange(e,t,i,r,s){e=this._root.forRange(e,t,i,!1,this,s||0,r);return"number"==typeof e?e:e.break}editRange(e,t,i,r,s){var h=this._root;h.isShared&&(this._root=h=h.clone());try{var n=h.forRange(e,t,i,!0,this,s||0,r);return"number"==typeof n?n:n.break}finally{let e;for(;h.keys.length<=1&&!h.isLeaf;)e=e||h.isShared,this._root=h=0===h.keys.length?EmptyLeaf:h.children[0];e&&(h.isShared=!0)}}editAll(e,t){return this.editRange(this.minKey(),this.maxKey(),!0,e,t)}deleteRange(e,t,i){return this.editRange(e,t,i,DeleteRange)}deleteKeys(e){for(var t=0,i=0;t<e.length;t++)this.delete(e[t])&&i++;return i}get height(){let e=this._root,t=-1;for(;e;)t++,e=e.isLeaf?void 0:e.children[0];return t}freeze(){this.clear=this.set=this.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(e=!1){var[e]=this._root.checkValid(0,this,0,e);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})){e={next:e};return Symbol&&Symbol.iterator&&(e[Symbol.iterator]=function(){return this}),e}exports.BTree=BTree,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}size(){return this.keys.length}maxKey(){return this.keys[this.keys.length-1]}indexOf(e,t,i){for(var r=this.keys,s=0,h=r.length,n=h>>1;s<h;){var a=i(r[n],e);if(a<0)s=n+1;else{if(!(0<a)){if(0===a)return n;if(e==e)return r.length;throw new Error("BTree: NaN was used as a key")}h=n}n=s+h>>1}return n^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){var t;if(0!==this.keys.length)return t=this.keys.length-1,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,i){e=this.indexOf(e,-1,i._compare);return e<0?t:this.values[e]}getPairOrNextLower(e,t,i,r){e=this.indexOf(e,-1,t),t=e<0?~e-1:i?e:e-1;if(0<=t)return r[0]=this.keys[t],r[1]=this.values[t],r}getPairOrNextHigher(e,t,i,r){e=this.indexOf(e,-1,t),t=e<0?~e:i?e:e+1,i=this.keys;if(t<i.length)return r[0]=i[t],r[1]=this.values[t],r}checkValid(e,t,i,r){var s=this.keys.length,h=this.values.length;if(check(this.values===undefVals?s<=h:s===h,"keys/values length mismatch: depth",e,"with lengths",s,h,"and baseIndex",i),check(0==e||0<s,"empty leaf at depth",e,"and baseIndex",i),!0===r)for(var n=1;n<s;n++)check(t._compare(this.keys[n-1],this.keys[n])<0,"keys out of order at depth",e,"and baseIndex",i+n-1,": ",this.keys[n-1]," !< ",this.keys[n]);return[s,this.keys[0],this.keys[s-1]]}set(e,t,i,r){var s,h,n=this.indexOf(e,-1,r._compare);return n<0?(n=~n,this.keys.length<r._maxNodeSize?this.insertInLeaf(n,e,t,r):(s=this.splitOffRightSide(),n>(h=this).keys.length&&(n-=this.keys.length,h=s),h.insertInLeaf(n,e,t,r),s)):(!1!==i&&(void 0!==t&&this.reifyValues(),this.keys[n]=e,this.values[n]=t),!1)}reifyValues(){return this.values===undefVals?this.values=this.values.slice(0,this.keys.length):this.values}insertInLeaf(e,t,i,r){if(this.keys.splice(e,0,t),this.values===undefVals){for(;undefVals.length<r._maxNodeSize;)undefVals.push(void 0);if(void 0===i)return!0;this.values=undefVals.slice(0,this.keys.length-1)}return this.values.splice(e,0,i),!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),e=this.values===undefVals?undefVals:this.values.splice(e);return new BNode(t,e)}forRange(e,t,i,r,s,h,n){var a,o,s=s._compare;if(t===e){if(!i)return h;if(o=(a=this.indexOf(e,-1,s))+1,a<0)return h}else a=this.indexOf(e,0,s),(o=this.indexOf(t,-1,s))<0?o=~o:!0===i&&o++;var l=this.keys,d=this.values;if(void 0!==n)for(var u=a;u<o;u++){var c=l[u],f=n(c,d[u],h++);if(void 0!==f){if(!0===r){if(c!==l[u]||!0===this.isShared)throw new Error("BTree illegally changed or cloned in editRange");f.delete?(this.keys.splice(u,1),this.values!==undefVals&&this.values.splice(u,1),u--,o--):f.hasOwnProperty("value")&&(d[u]=f.value)}if(void 0!==f.break)return f}}else h+=o-a;return h}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())}}exports.BNode=BNode;class BNodeInternal extends BNode{constructor(e,t,i){if(!i){i=[];for(var r=0;r<e.length;r++)i[r]=e[r].maxKey()}super(i),this.children=e,this._size=t}clone(){for(var e=this.children.slice(0),t=0;t<e.length;t++)e[t].isShared=!0;return new BNodeInternal(e,this._size,this.keys.slice(0))}size(){return this._size}greedyClone(e){if(this.isShared&&!e)return this;for(var t=new BNodeInternal(this.children.slice(0),this._size,this.keys.slice(0)),i=0;i<t.children.length;i++)t.children[i]=t.children[i].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,i){var r=this.indexOf(e,0,i._compare),s=this.children;return r<s.length?s[r].get(e,t,i):void 0}getPairOrNextLower(e,t,i,r){var s=this.indexOf(e,0,t),h=this.children;return s>=h.length?this.maxPair(r):void 0===(e=h[s].getPairOrNextLower(e,t,i,r))&&0<s?h[s-1].maxPair(r):e}getPairOrNextHigher(e,t,i,r){var s=this.indexOf(e,0,t),h=this.children,n=h.length;if(!(n<=s))return void 0===(e=h[s].getPairOrNextHigher(e,t,i,r))&&s<n-1?h[s+1].minPair(r):e}checkValid(e,t,i,r){var s=this.keys.length,h=this.children.length;check(s===h,"keys/children length mismatch: depth",e,"lengths",s,h,"baseIndex",i),check(1<s||0<e,"internal node has length",s,"at depth",e,"baseIndex",i);let n=0,a=this.children,o=this.keys,l=0,d=void 0,u=void 0;for(var c=0;c<h;c++){var f=a[c],[y,v,g]=f.checkValid(e+1,t,i+n,r);check(y===f.size(),"cached size mismatch at depth",e,"index",c,"baseIndex",i),check(1===y||t._compare(v,g)<0,"child node keys not sorted at depth",e,"index",c,"baseIndex",i),void 0!==d&&void 0!==u&&r&&(check(!areOverlapping(d,u,v,g,t._compare),"children keys not sorted at depth",e,"index",c,"baseIndex",i,": ",u," !< ",v),check(t._compare(u,v)<0,"children keys not sorted at depth",e,"index",c,"baseIndex",i,": ",u," !< ",v)),d=v,u=g,n+=y,l+=f.keys.length,check(n>=l,"wtf",i),check(0===c||a[c-1].constructor===f.constructor,"type mismatch, baseIndex:",i),f.maxKey()!=o[c]&&check(!1,"keys[",c,"] =",o[c],"is wrong, should be ",f.maxKey(),"at depth",e,"baseIndex",i),0===c||t._compare(o[c-1],o[c])<0||check(!1,"sort violation at depth",e,"index",c,"keys",o[c-1],o[c])}check(this._size===n,"internal node cached size mismatch at depth",e,"baseIndex",i,"cached",this._size,"actual",n);s=0===l;return(s||l>t.maxNodeSize*h)&&check(!1,s?"too few":"too many","children (",l,n,") at depth",e,"maxNodeSize:",t.maxNodeSize,"children.length:",h,"baseIndex:",i),[n,this.minKey(),this.maxKey()]}set(e,t,i,r){var s=this.children,h=r._maxNodeSize,n=r._compare,a=Math.min(this.indexOf(e,0,n),s.length-1),o=s[a],l=(o.isShared&&(s[a]=o=o.clone()),o.keys.length>=h&&(0<a&&(l=s[a-1]).keys.length<h&&n(o.keys[0],e)<0?(l.isShared&&(s[a-1]=l=l.clone()),l.takeFromRight(o),this.keys[a-1]=l.maxKey()):void 0!==(l=s[a+1])&&l.keys.length<h&&n(o.maxKey(),e)<0&&(l.isShared&&(s[a+1]=l=l.clone()),l.takeFromLeft(o),this.keys[a]=s[a].maxKey())),o.size()),s=o.set(e,t,i,r);return this._size+=o.size()-l,!1!==s&&(this.keys[a]=o.maxKey(),!0===s||(this.keys.length<h?(this.insert(a+1,s),!0):(e=this.splitOffRightSide(),t=this,0<n(s.maxKey(),this.maxKey())&&(t=e,a-=this.keys.length),t.insert(a+1,s),e)))}insert(e,t){this.children.splice(e,0,t),this.keys.splice(e,0,t.maxKey()),this._size+=t.size()}splitOffRightSide(){var e=this.children.length>>1,t=this.children.splice(e),e=this.keys.splice(e),i=this._size,t=(this._size=sumChildSizes(this.children),new BNodeInternal(t,i-this._size,e));return t}splitOffLeftSide(){var e=this.children.length>>1,t=this.children.splice(0,e),e=this.keys.splice(0,e),i=this._size,t=(this._size=sumChildSizes(this.children),new BNodeInternal(t,i-this._size,e));return t}takeFromRight(e){var t=e,e=(this.keys.push(e.keys.shift()),t.children.shift()),e=(this.children.push(e),e.size());t._size-=e,this._size+=e}takeFromLeft(e){var t=e,i=t.children.pop(),e=(this.keys.unshift(e.keys.pop()),this.children.unshift(i),i.size());t._size-=e,this._size+=e}forRange(i,r,s,h,n,a,o){var e=n._compare,l=this.keys,d=this.children,t=this.indexOf(i,0,e),u=t,c=Math.min(r===i?t:this.indexOf(r,0,e),l.length-1);if(h){if(u<=c)try{for(;u<=c;u++){let e=d[u];e.isShared&&(d[u]=e=e.clone());var f=e.size();let t=e.forRange(i,r,s,h,n,a,o);if(l[u]=e.maxKey(),this._size+=e.size()-f,"number"!=typeof t)return t;a=t}}finally{var y=n._maxNodeSize>>1;for(0<t&&t--,u=c;t<=u;u--)d[u].keys.length<=y&&(0!==d[u].keys.length?this.tryMerge(u,n._maxNodeSize):(l.splice(u,1),check(0===d.splice(u,1)[0].size(),"emptiness cleanup")));0!==d.length&&0===d[0].keys.length&&check(!1,"emptiness bug")}}else for(;u<=c;u++){var v=d[u].forRange(i,r,s,h,n,a,o);if("number"!=typeof v)return v;a=v}return a}tryMerge(e,t){var i=this.children;return 0<=e&&e+1<i.length&&i[e].keys.length+i[e+1].keys.length<=t&&(i[e].isShared&&(i[e]=i[e].clone()),i[e].mergeSibling(i[e+1],t),i.splice(e+1,1),this.keys.splice(e+1,1),this.keys[e]=i[e].maxKey(),!0)}mergeSibling(e,t){var i=this.keys.length,r=(this.keys.push.apply(this.keys,e.keys),e.children);if(this.children.push.apply(this.children,r),this._size+=e.size(),e.isShared&&!this.isShared)for(var s=0;s<r.length;s++)r[s].isShared=!0;this.tryMerge(i-1,t)}}exports.BNodeInternal=BNodeInternal;var undefVals=[];function sumChildSizes(e){for(var t=0,i=0;i<e.length;i++)t+=e[i].size();return t}function areOverlapping(e,t,i,r,s){return s(e,r)<=0&&0<=s(t,i)}let Delete={delete:!0},DeleteRange=()=>Delete,Break={break:!0},EmptyLeaf=(()=>{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=(()=>{var e=new BTree;return e.freeze(),e})();