Skip to content

Commit ad8e1f0

Browse files
committed
add optimized nextLowerPair, fix entriesReversed
1 parent fb31d70 commit ad8e1f0

File tree

4 files changed

+103
-10
lines changed

4 files changed

+103
-10
lines changed

b+tree.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,7 @@ export default class BTree<K = any, V = any> implements ISortedMapF<K, V>, ISort
338338
nextLowerPair(key: K | undefined): [K, V] | undefined;
339339
/** Returns the next key smaller than the specified key (or undefined if there is none) */
340340
nextLowerKey(key: K | undefined): K | undefined;
341+
getOrNextLower(key: K): [K, V] | undefined;
341342
/** Edits the value associated with a key in the tree, if it already exists.
342343
* @returns true if the key existed, false if not.
343344
*/

b+tree.js

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,8 @@ var BTree = /** @class */ (function () {
160160
this.setPairs(entries);
161161
}
162162
Object.defineProperty(BTree.prototype, "size", {
163-
// ES6 Map<K,V> methods ///////////////////////////////////////////////////
163+
/////////////////////////////////////////////////////////////////////////////
164+
// ES6 Map<K,V> methods /////////////////////////////////////////////////////
164165
/** Gets the number of key-value pairs in the tree. */
165166
get: function () { return this._size; },
166167
enumerable: false,
@@ -345,7 +346,8 @@ var BTree = /** @class */ (function () {
345346
p = callback(p, next.value, i++, this);
346347
return p;
347348
};
348-
// Iterator methods ///////////////////////////////////////////////////////
349+
/////////////////////////////////////////////////////////////////////////////
350+
// Iterator methods /////////////////////////////////////////////////////////
349351
/** Returns an iterator that provides items in order (ascending order if
350352
* the collection's comparator uses ascending order, as is the default.)
351353
* @param lowestKey First key to be iterated, or undefined to start at
@@ -418,7 +420,7 @@ var BTree = /** @class */ (function () {
418420
var _a = this.findPath(highestKey) || this.findPath(this.maxKey()), nodequeue = _a.nodequeue, nodeindex = _a.nodeindex, leaf = _a.leaf;
419421
check(!nodequeue[0] || leaf === nodequeue[0][nodeindex[0]], "wat!");
420422
var i = leaf.indexOf(highestKey, 0, this._compare);
421-
if (!(skipHighest || this._compare(leaf.keys[i], highestKey) > 0))
423+
if (!skipHighest && i < leaf.keys.length && this._compare(leaf.keys[i], highestKey) <= 0)
422424
i++;
423425
var state = reusedArray !== undefined ? 1 : 0;
424426
return iterator(function () {
@@ -603,6 +605,8 @@ var BTree = /** @class */ (function () {
603605
if (otherSuccess && onlyOther)
604606
return BTree.finishCursorWalk(otherCursor, thisCursor, _compare, onlyOther);
605607
};
608+
///////////////////////////////////////////////////////////////////////////
609+
// Helper methods for diffAgainst /////////////////////////////////////////
606610
BTree.finishCursorWalk = function (cursor, cursorFinished, compareKeys, callback) {
607611
var compared = BTree.compare(cursor, cursorFinished, compareKeys);
608612
if (compared === 0) {
@@ -725,6 +729,8 @@ var BTree = /** @class */ (function () {
725729
var depthBNormalized = levelIndicesB.length - (heightB - heightMin);
726730
return depthANormalized - depthBNormalized;
727731
};
732+
// End of helper methods for diffAgainst //////////////////////////////////
733+
///////////////////////////////////////////////////////////////////////////
728734
/** Returns a new iterator for iterating the keys of each pair in ascending order.
729735
* @param firstKey: Minimum key to include in the output. */
730736
BTree.prototype.keys = function (firstKey) {
@@ -748,7 +754,8 @@ var BTree = /** @class */ (function () {
748754
});
749755
};
750756
Object.defineProperty(BTree.prototype, "maxNodeSize", {
751-
// Additional methods /////////////////////////////////////////////////////
757+
/////////////////////////////////////////////////////////////////////////////
758+
// Additional methods ///////////////////////////////////////////////////////
752759
/** Returns the maximum number of children/values before nodes will split. */
753760
get: function () {
754761
return this._maxNodeSize;
@@ -832,14 +839,22 @@ var BTree = /** @class */ (function () {
832839
* If key === undefined, this function returns the highest pair.
833840
*/
834841
BTree.prototype.nextLowerPair = function (key) {
835-
var it = this.entriesReversed(key, ReusedArray, true);
836-
return it.next().value;
842+
if (key === undefined) {
843+
var maxKey = this.maxKey();
844+
if (maxKey === undefined)
845+
return undefined;
846+
return [maxKey, this.get(maxKey)];
847+
}
848+
return this._root.getOrNextLower(key, this, false);
837849
};
838850
/** Returns the next key smaller than the specified key (or undefined if there is none) */
839851
BTree.prototype.nextLowerKey = function (key) {
840852
var p = this.nextLowerPair(key);
841853
return p ? p[0] : p;
842854
};
855+
BTree.prototype.getOrNextLower = function (key) {
856+
return this._root.getOrNextLower(key, this, true);
857+
};
843858
/** Edits the value associated with a key in the tree, if it already exists.
844859
* @returns true if the key existed, false if not.
845860
*/
@@ -1053,6 +1068,7 @@ var BNode = /** @class */ (function () {
10531068
enumerable: false,
10541069
configurable: true
10551070
});
1071+
///////////////////////////////////////////////////////////////////////////
10561072
// Shared methods /////////////////////////////////////////////////////////
10571073
BNode.prototype.maxKey = function () {
10581074
return this.keys[this.keys.length - 1];
@@ -1125,6 +1141,7 @@ var BNode = /** @class */ (function () {
11251141
}
11261142
return c === 0 ? i : i ^ failXor;*/
11271143
};
1144+
/////////////////////////////////////////////////////////////////////////////
11281145
// Leaf Node: misc //////////////////////////////////////////////////////////
11291146
BNode.prototype.minKey = function () {
11301147
return this.keys[0];
@@ -1140,6 +1157,11 @@ var BNode = /** @class */ (function () {
11401157
var i = this.indexOf(key, -1, tree._compare);
11411158
return i < 0 ? defaultValue : this.values[i];
11421159
};
1160+
BNode.prototype.getOrNextLower = function (key, tree, inclusive) {
1161+
var i = this.indexOf(key, -1, tree._compare);
1162+
var indexOrLower = i < 0 ? (i ^ -1) - 1 : (inclusive ? i : i - 1);
1163+
return indexOrLower >= 0 ? [this.keys[indexOrLower], this.values[indexOrLower]] : undefined;
1164+
};
11431165
BNode.prototype.checkValid = function (depth, tree, baseIndex) {
11441166
var kL = this.keys.length, vL = this.values.length;
11451167
check(this.values === undefVals ? kL <= vL : kL === vL, "keys/values length mismatch: depth", depth, "with lengths", kL, vL, "and baseIndex", baseIndex);
@@ -1151,6 +1173,7 @@ var BNode = /** @class */ (function () {
11511173
check(depth == 0 || kL > 0, "empty leaf at depth", depth, "and baseIndex", baseIndex);
11521174
return kL;
11531175
};
1176+
/////////////////////////////////////////////////////////////////////////////
11541177
// Leaf Node: set & node splitting //////////////////////////////////////////
11551178
BNode.prototype.set = function (key, value, overwrite, tree) {
11561179
var i = this.indexOf(key, -1, tree._compare);
@@ -1240,6 +1263,7 @@ var BNode = /** @class */ (function () {
12401263
var values = this.values === undefVals ? undefVals : this.values.splice(half);
12411264
return new BNode(keys, values);
12421265
};
1266+
/////////////////////////////////////////////////////////////////////////////
12431267
// Leaf Node: scanning & deletions //////////////////////////////////////////
12441268
BNode.prototype.forRange = function (low, high, includeHigh, editMode, tree, count, onFound) {
12451269
var cmp = tree._compare;
@@ -1336,6 +1360,18 @@ var BNodeInternal = /** @class */ (function (_super) {
13361360
var i = this.indexOf(key, 0, tree._compare), children = this.children;
13371361
return i < children.length ? children[i].get(key, defaultValue, tree) : undefined;
13381362
};
1363+
BNodeInternal.prototype.getOrNextLower = function (key, tree, inclusive) {
1364+
var i = this.indexOf(key, 0, tree._compare), children = this.children;
1365+
if (i > children.length)
1366+
return undefined;
1367+
var result = children[i].getOrNextLower(key, tree, inclusive);
1368+
if (result === undefined && i > 0) {
1369+
var child = children[i - 1];
1370+
var maxKey = child.maxKey();
1371+
return [maxKey, child.get(maxKey, undefined, tree)];
1372+
}
1373+
return result;
1374+
};
13391375
BNodeInternal.prototype.checkValid = function (depth, tree, baseIndex) {
13401376
var kL = this.keys.length, cL = this.children.length;
13411377
check(kL === cL, "keys/children length mismatch: depth", depth, "lengths", kL, cL, "baseIndex", baseIndex);
@@ -1358,6 +1394,7 @@ var BNodeInternal = /** @class */ (function (_super) {
13581394
check(false, toofew ? "too few" : "too many", "children (", childSize, size, ") at depth", depth, "maxNodeSize:", tree.maxNodeSize, "children.length:", cL, "baseIndex:", baseIndex);
13591395
return size;
13601396
};
1397+
/////////////////////////////////////////////////////////////////////////////
13611398
// Internal Node: set & node splitting //////////////////////////////////////
13621399
BNodeInternal.prototype.set = function (key, value, overwrite, tree) {
13631400
var c = this.children, max = tree._maxNodeSize, cmp = tree._compare;
@@ -1426,6 +1463,7 @@ var BNodeInternal = /** @class */ (function (_super) {
14261463
this.keys.unshift(lhs.keys.pop());
14271464
this.children.unshift(lhs.children.pop());
14281465
};
1466+
/////////////////////////////////////////////////////////////////////////////
14291467
// Internal Node: scanning & deletions //////////////////////////////////////
14301468
// Note: `count` is the next value of the third argument to `onFound`.
14311469
// A leaf node's `forRange` function returns a new value for this counter,

b+tree.test.ts

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -421,6 +421,26 @@ function testBTree(maxNodeSize: number)
421421
});
422422
}
423423

424+
for (let size of [5, 10, 300]) {
425+
const tree = new BTree<number,number>(undefined, undefined, maxNodeSize);
426+
const pairs: [number,number][] = [];
427+
for (let i = 0; i < size; i++) {
428+
const value = randInt(size * 2);
429+
tree.set(i, value);
430+
pairs.push([i, value]);
431+
}
432+
test(`nextLowerPair/nextHigherPair for tree of size ${size}`, () => {
433+
for (let i = 0; i < size; i++) {
434+
if (i > 0) {
435+
expect(tree.nextLowerPair(i)).toEqual(pairs[i - 1]);
436+
}
437+
if (i < size - 1) {
438+
expect(tree.nextHigherPair(i)).toEqual(pairs[i + 1]);
439+
}
440+
}
441+
})
442+
}
443+
424444
for (let size of [6, 36, 216]) {
425445
test(`setPairs & deleteRange [size ${size}]`, () => {
426446
// Store numbers in descending order
@@ -758,6 +778,12 @@ function testBTree(maxNodeSize: number)
758778
}
759779
});
760780

781+
test("entriesReversed when highest key does not exist", () => {
782+
const entries: [{ key: number}, number][] = [[{ key: 10 }, 0], [{ key: 20 }, 0], [{ key: 30 }, 0]];
783+
const tree = new BTree<{ key: number }, number>(entries, (a, b) => a.key - b.key);
784+
expect(Array.from(tree.entriesReversed({ key: 40 }))).toEqual(entries.reverse());
785+
});
786+
761787
test("nextLowerPair/nextHigherPair and issue #9: nextLowerPair returns highest pair if key is 0", () => {
762788
const tree = new BTree<number,number>(undefined, undefined, maxNodeSize);
763789
tree.set(-2, 123);
@@ -776,7 +802,7 @@ function testBTree(maxNodeSize: number)
776802
expect(tree.nextLowerPair(undefined)).toEqual([2, 12345]);
777803
expect(tree.nextHigherPair(undefined)).toEqual([-2, 123]);
778804

779-
for (var i = -10; i <= 300; i++) // embiggen the tree
805+
for (let i = -10; i <= 300; i++) // embiggen the tree
780806
tree.set(i, i*2);
781807
expect(tree.nextLowerPair(-1)).toEqual([-2, -4]);
782808
expect(tree.nextLowerPair(0)).toEqual([-1, -2]);

b+tree.ts

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -497,7 +497,7 @@ export default class BTree<K=any, V=any> implements ISortedMapF<K,V>, ISortedMap
497497
var {nodequeue,nodeindex,leaf} = this.findPath(highestKey) || this.findPath(this.maxKey())!;
498498
check(!nodequeue[0] || leaf === nodequeue[0][nodeindex[0]], "wat!");
499499
var i = leaf.indexOf(highestKey, 0, this._compare);
500-
if (!(skipHighest || this._compare(leaf.keys[i], highestKey) > 0))
500+
if (!skipHighest && i < leaf.keys.length && this._compare(leaf.keys[i], highestKey) <= 0)
501501
i++;
502502
var state = reusedArray !== undefined ? 1 : 0;
503503

@@ -945,8 +945,13 @@ export default class BTree<K=any, V=any> implements ISortedMapF<K,V>, ISortedMap
945945
* If key === undefined, this function returns the highest pair.
946946
*/
947947
nextLowerPair(key: K|undefined): [K,V]|undefined {
948-
var it = this.entriesReversed(key, ReusedArray, true);
949-
return it.next().value;
948+
if (key === undefined) {
949+
const maxKey = this.maxKey();
950+
if (maxKey === undefined)
951+
return undefined;
952+
return [maxKey, this.get(maxKey) as V];
953+
}
954+
return this._root.getOrNextLower(key, this, false);
950955
}
951956

952957
/** Returns the next key smaller than the specified key (or undefined if there is none) */
@@ -955,6 +960,10 @@ export default class BTree<K=any, V=any> implements ISortedMapF<K,V>, ISortedMap
955960
return p ? p[0] : p;
956961
}
957962

963+
getOrNextLower(key: K): [K,V]|undefined {
964+
return this._root.getOrNextLower(key, this, true);
965+
}
966+
958967
/** Edits the value associated with a key in the tree, if it already exists.
959968
* @returns true if the key existed, false if not.
960969
*/
@@ -1273,6 +1282,12 @@ class BNode<K,V> {
12731282
return i < 0 ? defaultValue : this.values[i];
12741283
}
12751284

1285+
getOrNextLower(key: K, tree: BTree<K,V>, inclusive: boolean): [K,V]|undefined {
1286+
var i = this.indexOf(key, -1, tree._compare);
1287+
const indexOrLower = i < 0 ? (i ^ -1) - 1 : (inclusive ? i : i - 1);
1288+
return indexOrLower >= 0 ? [this.keys[indexOrLower], this.values[indexOrLower]] : undefined;
1289+
}
1290+
12761291
checkValid(depth: number, tree: BTree<K,V>, baseIndex: number): number {
12771292
var kL = this.keys.length, vL = this.values.length;
12781293
check(this.values === undefVals ? kL <= vL : kL === vL,
@@ -1483,6 +1498,19 @@ class BNodeInternal<K,V> extends BNode<K,V> {
14831498
return i < children.length ? children[i].get(key, defaultValue, tree) : undefined;
14841499
}
14851500

1501+
getOrNextLower(key: K, tree: BTree<K,V>, inclusive: boolean): [K,V]|undefined {
1502+
var i = this.indexOf(key, 0, tree._compare), children = this.children;
1503+
if (i > children.length)
1504+
return undefined;
1505+
const result = children[i].getOrNextLower(key, tree, inclusive);
1506+
if (result === undefined && i > 0) {
1507+
const child = children[i - 1];
1508+
const maxKey = child.maxKey();
1509+
return [maxKey, child.get(maxKey, undefined, tree) as V];
1510+
}
1511+
return result;
1512+
}
1513+
14861514
checkValid(depth: number, tree: BTree<K,V>, baseIndex: number): number {
14871515
let kL = this.keys.length, cL = this.children.length;
14881516
check(kL === cL, "keys/children length mismatch: depth", depth, "lengths", kL, cL, "baseIndex", baseIndex);

0 commit comments

Comments
 (0)