From d5ddfd14e81585d904457880742224452165196c Mon Sep 17 00:00:00 2001 From: KanHar Date: Thu, 28 Mar 2024 16:35:23 +0200 Subject: [PATCH 1/4] Add the find at or neighbor function --- src/index.ts | 42 ++++++++++++++++++++++--------- tests/find.test.ts | 61 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+), 12 deletions(-) diff --git a/src/index.ts b/src/index.ts index 4c04c1d..a513849 100644 --- a/src/index.ts +++ b/src/index.ts @@ -260,31 +260,49 @@ export default class Tree { return null; } + public findAtOrNeighborStatic (key:Key) : Node|null { + let current = this._root; + const compare = this._comparator; + let next:Node; + while (current) { + const cmp = compare(key, current.key); + if (cmp === 0) return current; + else if (cmp < 0) next = current.left; + else next = current.right; + + if (next == undefined) return current; + + current = next; + } + } + /** * Find without splaying */ public findStatic (key:Key) : Node|null { - let current = this._root; - const compare = this._comparator; - while (current) { - const cmp = compare(key, current.key); - if (cmp === 0) return current; - else if (cmp < 0) current = current.left; - else current = current.right; + const result_candidate = this.findAtOrNeighborStatic(key); + if (result_candidate && this._comparator(result_candidate.key, key) === 0) { + return result_candidate; } return null; } - - public find (key:Key) : Node|null { + public findAtOrNeighbor (key:Key) : Node|null { if (this._root) { this._root = splay(key, this._root, this._comparator); - if (this._comparator(key, this._root.key) !== 0) return null; } return this._root; } + public find (key:Key) : Node|null { + const result_candidate = this.findAtOrNeighbor(key); + if (result_candidate && this._comparator(result_candidate.key, key) === 0) { + return result_candidate; + } + return null; + } + public contains (key:Key) : boolean { let current = this._root; @@ -530,9 +548,9 @@ export default class Tree { return split(key, this._root, this._comparator); } - *[Symbol.iterator]() { + public *[Symbol.iterator]() { let current = this._root; - const Q: Node[] = []; /* Initialize stack s */ + const Q:Node[] = []; /* Initialize stack s */ let done = false; while (!done) { diff --git a/tests/find.test.ts b/tests/find.test.ts index eccd9dd..663132f 100644 --- a/tests/find.test.ts +++ b/tests/find.test.ts @@ -56,4 +56,65 @@ describe ('find', () => { assert.strictEqual(tree.find(2), tree.root); }); + + it('should return previous key as result of search', () => { + const tree = new Tree(); + assert.equal(tree.findAtOrNeighbor(10), null); + assert.equal(tree.findAtOrNeighbor(20), null); + assert.equal(tree.findAtOrNeighbor(30), null); + tree.insert(10, 40); + tree.insert(20, 50); + tree.insert(30, 60); + + let root = tree.root; + assert.equal(tree.findAtOrNeighbor(10).data, 40); + assert.notStrictEqual(root, tree.root); + root = tree.root; + + const found_around_15 = tree.findAtOrNeighbor(15); + assert.equal(found_around_15.data === 40 || found_around_15.data === 50, true); + + assert.equal(tree.findAtOrNeighbor(20).data, 50); + assert.notStrictEqual(root, tree.root); + root = tree.root; + + assert.equal(tree.findAtOrNeighbor(30).data, 60); + assert.notStrictEqual(root, tree.root); + root = tree.root; + + assert.equal(tree.findAtOrNeighbor(80).data, 60); + assert.strictEqual(root, tree.root); + }); + + it ('should allow finding node by approximate key without splaying', () => { + const tree = new Tree(); + assert.equal(tree.findAtOrNeighborStatic(10), null); + assert.equal(tree.findAtOrNeighborStatic(20), null); + assert.equal(tree.findAtOrNeighborStatic(30), null); + tree.insert(-20, 80); + tree.insert(10, 40); + tree.insert(20, 50); + tree.insert(30, 60); + + tree.find(20); + const root = tree.root; + assert.equal(tree.findAtOrNeighborStatic(10).data, 40); + assert.strictEqual(root, tree.root); + + assert.equal(tree.findAtOrNeighborStatic(15).data === 40 || tree.findAtOrNeighborStatic(15).data === 50, true); + assert.strictEqual(root, tree.root); + + assert.equal(tree.findAtOrNeighborStatic(20).data, 50); + assert.strictEqual(root, tree.root); + + assert.equal(tree.findAtOrNeighborStatic(30).data, 60); + assert.strictEqual(root, tree.root); + + assert.equal(tree.findAtOrNeighborStatic(80).data, 60); + + assert.equal(tree.findAtOrNeighborStatic(-20).data, 80); + assert.equal(tree.findAtOrNeighborStatic(-30).data, 80); + + assert.strictEqual(tree.find(20), tree.root); + }); }); From f9b0d7ccc1cef067a00a6b62f64e7e635df2db43 Mon Sep 17 00:00:00 2001 From: KanHar Date: Thu, 28 Mar 2024 16:37:12 +0200 Subject: [PATCH 2/4] Revert lint --- src/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/index.ts b/src/index.ts index a513849..2f61dcd 100644 --- a/src/index.ts +++ b/src/index.ts @@ -548,9 +548,9 @@ export default class Tree { return split(key, this._root, this._comparator); } - public *[Symbol.iterator]() { + *[Symbol.iterator]() { let current = this._root; - const Q:Node[] = []; /* Initialize stack s */ + const Q: Node[] = []; /* Initialize stack s */ let done = false; while (!done) { From 131c2c7768c648f86e8e973751259955be870413 Mon Sep 17 00:00:00 2001 From: KanHar Date: Thu, 28 Mar 2024 16:37:56 +0200 Subject: [PATCH 3/4] Cleaner testing --- tests/find.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/find.test.ts b/tests/find.test.ts index 663132f..f62e810 100644 --- a/tests/find.test.ts +++ b/tests/find.test.ts @@ -101,7 +101,8 @@ describe ('find', () => { assert.equal(tree.findAtOrNeighborStatic(10).data, 40); assert.strictEqual(root, tree.root); - assert.equal(tree.findAtOrNeighborStatic(15).data === 40 || tree.findAtOrNeighborStatic(15).data === 50, true); + const result_around_15 = tree.findAtOrNeighborStatic(15); + assert.equal(result_around_15.data === 40 || result_around_15.data === 50, true); assert.strictEqual(root, tree.root); assert.equal(tree.findAtOrNeighborStatic(20).data, 50); From 83fd44694b7c0dbe9edb03f20e0df8dcd95f9d1d Mon Sep 17 00:00:00 2001 From: KanHar Date: Sun, 31 Mar 2024 10:59:12 +0300 Subject: [PATCH 4/4] Update readme --- Readme.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Readme.md b/Readme.md index 2e51d6d..f894299 100644 --- a/Readme.md +++ b/Readme.md @@ -53,6 +53,8 @@ Or use the compiled version 'dist/splay.js'. - `tree.remove(key:any)` - Remove item - `tree.find(key):Node|Null` - Return node by its key - `tree.findStatic(key):Node|Null` - Return node by its key (doesn't re-balance the tree) +- `tree.findAtOrNeighbor(key):Node|Null` - Return node by its key or a node with a key right above or below. Useful for floating point keys, etc. +- `tree.findAtOrNeighborStatic(key):Node|Null` - Same as above, but doesn't re-balance the tree - `tree.at(index:Number):Node|Null` - Return node by its index in sorted order of keys - `tree.contains(key):Boolean` - Whether a node with the given key is in the tree - `tree.forEach(function(node) {...}):Tree` In-order traversal