Skip to content

Commit 63d84d9

Browse files
authored
Merge pull request #23 from qwertie/taylorsw04/OptimizeNextLowerPair
fix: fix bug in entriesReversed, optimize nextLowerPair
2 parents fc1a4bf + 3fb0df5 commit 63d84d9

File tree

6 files changed

+8221
-39
lines changed

6 files changed

+8221
-39
lines changed

b+tree.d.ts

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -327,17 +327,30 @@ export default class BTree<K = any, V = any> implements ISortedMapF<K, V>, ISort
327327
*/
328328
setIfNotPresent(key: K, value: V): boolean;
329329
/** Returns the next pair whose key is larger than the specified key (or undefined if there is none).
330-
* If key === undefined, this function returns the lowest pair.
330+
* If key === undefined, this function returns the lowest pair.
331+
* @param key The key to search for.
332+
* @param reusedArray Optional array used repeatedly to store key-value pairs, to
333+
* avoid creating a new array on every iteration.
331334
*/
332-
nextHigherPair(key: K | undefined): [K, V] | undefined;
335+
nextHigherPair(key: K | undefined, reusedArray?: [K, V]): [K, V] | undefined;
333336
/** Returns the next key larger than the specified key (or undefined if there is none) */
334337
nextHigherKey(key: K | undefined): K | undefined;
335338
/** Returns the next pair whose key is smaller than the specified key (or undefined if there is none).
336339
* If key === undefined, this function returns the highest pair.
340+
* @param key The key to search for.
341+
* @param reusedArray Optional array used repeatedly to store key-value pairs, to
342+
* avoid creating a new array each time you call this method.
337343
*/
338-
nextLowerPair(key: K | undefined): [K, V] | undefined;
344+
nextLowerPair(key: K | undefined, reusedArray?: [K, V]): [K, V] | undefined;
339345
/** Returns the next key smaller than the specified key (or undefined if there is none) */
340346
nextLowerKey(key: K | undefined): K | undefined;
347+
/** Returns the key-value pair associated with the supplied key if it exists
348+
* and the next lower pair otherwise (or undefined if there is none)
349+
* @param key The key to search for.
350+
* @param reusedArray Optional array used repeatedly to store key-value pairs, to
351+
* avoid creating a new array each time you call this method.
352+
* */
353+
getPairOrNextLower(key: K, reusedArray?: [K, V]): [K, V] | undefined;
341354
/** Edits the value associated with a key in the tree, if it already exists.
342355
* @returns true if the key existed, false if not.
343356
*/

b+tree.js

Lines changed: 109 additions & 16 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) {
@@ -722,6 +726,8 @@ var BTree = /** @class */ (function () {
722726
var depthBNormalized = levelIndicesB.length - (heightB - heightMin);
723727
return depthANormalized - depthBNormalized;
724728
};
729+
// End of helper methods for diffAgainst //////////////////////////////////
730+
///////////////////////////////////////////////////////////////////////////
725731
/** Returns a new iterator for iterating the keys of each pair in ascending order.
726732
* @param firstKey: Minimum key to include in the output. */
727733
BTree.prototype.keys = function (firstKey) {
@@ -745,7 +751,8 @@ var BTree = /** @class */ (function () {
745751
});
746752
};
747753
Object.defineProperty(BTree.prototype, "maxNodeSize", {
748-
// Additional methods /////////////////////////////////////////////////////
754+
/////////////////////////////////////////////////////////////////////////////
755+
// Additional methods ///////////////////////////////////////////////////////
749756
/** Returns the maximum number of children/values before nodes will split. */
750757
get: function () {
751758
return this._maxNodeSize;
@@ -811,32 +818,50 @@ var BTree = /** @class */ (function () {
811818
return this.set(key, value, false);
812819
};
813820
/** Returns the next pair whose key is larger than the specified key (or undefined if there is none).
814-
* If key === undefined, this function returns the lowest pair.
821+
* If key === undefined, this function returns the lowest pair.
822+
* @param key The key to search for.
823+
* @param reusedArray Optional array used repeatedly to store key-value pairs, to
824+
* avoid creating a new array on every iteration.
815825
*/
816-
BTree.prototype.nextHigherPair = function (key) {
817-
var it = this.entries(key, ReusedArray);
818-
var r = it.next();
819-
if (!r.done && key !== undefined && this._compare(r.value[0], key) <= 0)
820-
r = it.next();
821-
return r.value;
826+
BTree.prototype.nextHigherPair = function (key, reusedArray) {
827+
reusedArray = reusedArray || [];
828+
if (key === undefined) {
829+
return this._root.minPair(reusedArray);
830+
}
831+
return this._root.getPairOrNextHigher(key, this, false, reusedArray);
822832
};
823833
/** Returns the next key larger than the specified key (or undefined if there is none) */
824834
BTree.prototype.nextHigherKey = function (key) {
825-
var p = this.nextHigherPair(key);
835+
var p = this.nextHigherPair(key, ReusedArray);
826836
return p ? p[0] : p;
827837
};
828838
/** Returns the next pair whose key is smaller than the specified key (or undefined if there is none).
829839
* If key === undefined, this function returns the highest pair.
840+
* @param key The key to search for.
841+
* @param reusedArray Optional array used repeatedly to store key-value pairs, to
842+
* avoid creating a new array each time you call this method.
830843
*/
831-
BTree.prototype.nextLowerPair = function (key) {
832-
var it = this.entriesReversed(key, ReusedArray, true);
833-
return it.next().value;
844+
BTree.prototype.nextLowerPair = function (key, reusedArray) {
845+
reusedArray = reusedArray || [];
846+
if (key === undefined) {
847+
return this._root.maxPair(reusedArray);
848+
}
849+
return this._root.getPairOrNextLower(key, this, false, reusedArray);
834850
};
835851
/** Returns the next key smaller than the specified key (or undefined if there is none) */
836852
BTree.prototype.nextLowerKey = function (key) {
837-
var p = this.nextLowerPair(key);
853+
var p = this.nextLowerPair(key, ReusedArray);
838854
return p ? p[0] : p;
839855
};
856+
/** Returns the key-value pair associated with the supplied key if it exists
857+
* and the next lower pair otherwise (or undefined if there is none)
858+
* @param key The key to search for.
859+
* @param reusedArray Optional array used repeatedly to store key-value pairs, to
860+
* avoid creating a new array each time you call this method.
861+
* */
862+
BTree.prototype.getPairOrNextLower = function (key, reusedArray) {
863+
return this._root.getPairOrNextLower(key, this, true, reusedArray || []);
864+
};
840865
/** Edits the value associated with a key in the tree, if it already exists.
841866
* @returns true if the key existed, false if not.
842867
*/
@@ -1050,6 +1075,7 @@ var BNode = /** @class */ (function () {
10501075
enumerable: false,
10511076
configurable: true
10521077
});
1078+
///////////////////////////////////////////////////////////////////////////
10531079
// Shared methods /////////////////////////////////////////////////////////
10541080
BNode.prototype.maxKey = function () {
10551081
return this.keys[this.keys.length - 1];
@@ -1122,10 +1148,26 @@ var BNode = /** @class */ (function () {
11221148
}
11231149
return c === 0 ? i : i ^ failXor;*/
11241150
};
1151+
/////////////////////////////////////////////////////////////////////////////
11251152
// Leaf Node: misc //////////////////////////////////////////////////////////
11261153
BNode.prototype.minKey = function () {
11271154
return this.keys[0];
11281155
};
1156+
BNode.prototype.minPair = function (reusedArray) {
1157+
if (this.keys.length === 0)
1158+
return undefined;
1159+
reusedArray[0] = this.keys[0];
1160+
reusedArray[1] = this.values[0];
1161+
return reusedArray;
1162+
};
1163+
BNode.prototype.maxPair = function (reusedArray) {
1164+
if (this.keys.length === 0)
1165+
return undefined;
1166+
var lastIndex = this.keys.length - 1;
1167+
reusedArray[0] = this.keys[lastIndex];
1168+
reusedArray[1] = this.values[lastIndex];
1169+
return reusedArray;
1170+
};
11291171
BNode.prototype.clone = function () {
11301172
var v = this.values;
11311173
return new BNode(this.keys.slice(0), v === undefVals ? v : v.slice(0));
@@ -1137,6 +1179,27 @@ var BNode = /** @class */ (function () {
11371179
var i = this.indexOf(key, -1, tree._compare);
11381180
return i < 0 ? defaultValue : this.values[i];
11391181
};
1182+
BNode.prototype.getPairOrNextLower = function (key, tree, inclusive, reusedArray) {
1183+
var i = this.indexOf(key, -1, tree._compare);
1184+
var indexOrLower = i < 0 ? ~i - 1 : (inclusive ? i : i - 1);
1185+
if (indexOrLower >= 0) {
1186+
reusedArray[0] = this.keys[indexOrLower];
1187+
reusedArray[1] = this.values[indexOrLower];
1188+
return reusedArray;
1189+
}
1190+
return undefined;
1191+
};
1192+
BNode.prototype.getPairOrNextHigher = function (key, tree, inclusive, reusedArray) {
1193+
var i = this.indexOf(key, -1, tree._compare);
1194+
var indexOrLower = i < 0 ? ~i : (inclusive ? i : i + 1);
1195+
var keys = this.keys;
1196+
if (indexOrLower < keys.length) {
1197+
reusedArray[0] = keys[indexOrLower];
1198+
reusedArray[1] = this.values[indexOrLower];
1199+
return reusedArray;
1200+
}
1201+
return undefined;
1202+
};
11401203
BNode.prototype.checkValid = function (depth, tree, baseIndex) {
11411204
var kL = this.keys.length, vL = this.values.length;
11421205
check(this.values === undefVals ? kL <= vL : kL === vL, "keys/values length mismatch: depth", depth, "with lengths", kL, vL, "and baseIndex", baseIndex);
@@ -1148,6 +1211,7 @@ var BNode = /** @class */ (function () {
11481211
check(depth == 0 || kL > 0, "empty leaf at depth", depth, "and baseIndex", baseIndex);
11491212
return kL;
11501213
};
1214+
/////////////////////////////////////////////////////////////////////////////
11511215
// Leaf Node: set & node splitting //////////////////////////////////////////
11521216
BNode.prototype.set = function (key, value, overwrite, tree) {
11531217
var i = this.indexOf(key, -1, tree._compare);
@@ -1237,6 +1301,7 @@ var BNode = /** @class */ (function () {
12371301
var values = this.values === undefVals ? undefVals : this.values.splice(half);
12381302
return new BNode(keys, values);
12391303
};
1304+
/////////////////////////////////////////////////////////////////////////////
12401305
// Leaf Node: scanning & deletions //////////////////////////////////////////
12411306
BNode.prototype.forRange = function (low, high, includeHigh, editMode, tree, count, onFound) {
12421307
var cmp = tree._compare;
@@ -1329,10 +1394,36 @@ var BNodeInternal = /** @class */ (function (_super) {
13291394
BNodeInternal.prototype.minKey = function () {
13301395
return this.children[0].minKey();
13311396
};
1397+
BNodeInternal.prototype.minPair = function (reusedArray) {
1398+
return this.children[0].minPair(reusedArray);
1399+
};
1400+
BNodeInternal.prototype.maxPair = function (reusedArray) {
1401+
return this.children[this.children.length - 1].maxPair(reusedArray);
1402+
};
13321403
BNodeInternal.prototype.get = function (key, defaultValue, tree) {
13331404
var i = this.indexOf(key, 0, tree._compare), children = this.children;
13341405
return i < children.length ? children[i].get(key, defaultValue, tree) : undefined;
13351406
};
1407+
BNodeInternal.prototype.getPairOrNextLower = function (key, tree, inclusive, reusedArray) {
1408+
var i = this.indexOf(key, 0, tree._compare), children = this.children;
1409+
if (i >= children.length)
1410+
return undefined;
1411+
var result = children[i].getPairOrNextLower(key, tree, inclusive, reusedArray);
1412+
if (result === undefined && i > 0) {
1413+
return children[i - 1].maxPair(reusedArray);
1414+
}
1415+
return result;
1416+
};
1417+
BNodeInternal.prototype.getPairOrNextHigher = function (key, tree, inclusive, reusedArray) {
1418+
var i = this.indexOf(key, 0, tree._compare), children = this.children, length = children.length;
1419+
if (i >= length)
1420+
return undefined;
1421+
var result = children[i].getPairOrNextHigher(key, tree, inclusive, reusedArray);
1422+
if (result === undefined && i < length - 1) {
1423+
return children[i + 1].minPair(reusedArray);
1424+
}
1425+
return result;
1426+
};
13361427
BNodeInternal.prototype.checkValid = function (depth, tree, baseIndex) {
13371428
var kL = this.keys.length, cL = this.children.length;
13381429
check(kL === cL, "keys/children length mismatch: depth", depth, "lengths", kL, cL, "baseIndex", baseIndex);
@@ -1355,6 +1446,7 @@ var BNodeInternal = /** @class */ (function (_super) {
13551446
check(false, toofew ? "too few" : "too many", "children (", childSize, size, ") at depth", depth, "maxNodeSize:", tree.maxNodeSize, "children.length:", cL, "baseIndex:", baseIndex);
13561447
return size;
13571448
};
1449+
/////////////////////////////////////////////////////////////////////////////
13581450
// Internal Node: set & node splitting //////////////////////////////////////
13591451
BNodeInternal.prototype.set = function (key, value, overwrite, tree) {
13601452
var c = this.children, max = tree._maxNodeSize, cmp = tree._compare;
@@ -1423,6 +1515,7 @@ var BNodeInternal = /** @class */ (function (_super) {
14231515
this.keys.unshift(lhs.keys.pop());
14241516
this.children.unshift(lhs.children.pop());
14251517
};
1518+
/////////////////////////////////////////////////////////////////////////////
14261519
// Internal Node: scanning & deletions //////////////////////////////////////
14271520
// Note: `count` is the next value of the third argument to `onFound`.
14281521
// A leaf node's `forRange` function returns a new value for this counter,

b+tree.test.ts

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -421,6 +421,28 @@ 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+
expect(tree.nextHigherPair(undefined)).toEqual([tree.minKey()!, tree.get(tree.minKey()!)]);
434+
for (let i = 0; i < size; i++) {
435+
if (i > 0) {
436+
expect(tree.nextLowerPair(i)).toEqual(pairs[i - 1]);
437+
}
438+
if (i < size - 1) {
439+
expect(tree.nextHigherPair(i)).toEqual(pairs[i + 1]);
440+
}
441+
}
442+
expect(tree.nextLowerPair(undefined)).toEqual([tree.maxKey()!, tree.get(tree.maxKey()!)]);
443+
})
444+
}
445+
424446
for (let size of [6, 36, 216]) {
425447
test(`setPairs & deleteRange [size ${size}]`, () => {
426448
// Store numbers in descending order
@@ -758,6 +780,12 @@ function testBTree(maxNodeSize: number)
758780
}
759781
});
760782

783+
test("entriesReversed when highest key does not exist", () => {
784+
const entries: [{ key: number}, number][] = [[{ key: 10 }, 0], [{ key: 20 }, 0], [{ key: 30 }, 0]];
785+
const tree = new BTree<{ key: number }, number>(entries, (a, b) => a.key - b.key);
786+
expect(Array.from(tree.entriesReversed({ key: 40 }))).toEqual(entries.reverse());
787+
});
788+
761789
test("nextLowerPair/nextHigherPair and issue #9: nextLowerPair returns highest pair if key is 0", () => {
762790
const tree = new BTree<number,number>(undefined, undefined, maxNodeSize);
763791
tree.set(-2, 123);
@@ -776,7 +804,7 @@ function testBTree(maxNodeSize: number)
776804
expect(tree.nextLowerPair(undefined)).toEqual([2, 12345]);
777805
expect(tree.nextHigherPair(undefined)).toEqual([-2, 123]);
778806

779-
for (var i = -10; i <= 300; i++) // embiggen the tree
807+
for (let i = -10; i <= 300; i++) // embiggen the tree
780808
tree.set(i, i*2);
781809
expect(tree.nextLowerPair(-1)).toEqual([-2, -4]);
782810
expect(tree.nextLowerPair(0)).toEqual([-1, -2]);

0 commit comments

Comments
 (0)