Skip to content

Commit

Permalink
Fixed bug with inverted on result for insert.
Browse files Browse the repository at this point in the history
  • Loading branch information
n8allan committed Feb 26, 2024
1 parent 945771b commit 2eda731
Show file tree
Hide file tree
Showing 12 changed files with 51 additions and 46 deletions.
58 changes: 30 additions & 28 deletions docs/classes/BTree.html

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions docs/classes/KeyBound.html

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions docs/classes/KeyRange.html

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions docs/classes/Path.html

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions docs/classes/PathBranch.html

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
</code><button>Copy</button></pre>
<a id="md:as-an-ordered-dictionary" class="tsd-anchor"></a><h4><a href="#md:as-an-ordered-dictionary">As an ordered dictionary</a></h4><pre><code class="language-ts"><span class="hl-0"> </span><span class="hl-2">import</span><span class="hl-0"> { </span><span class="hl-1">BTree</span><span class="hl-0"> } </span><span class="hl-2">from</span><span class="hl-0"> </span><span class="hl-3">&#39;digitree&#39;</span><span class="hl-0">;</span><br/><span class="hl-0"> ...</span><br/><span class="hl-0"> </span><span class="hl-4">interface</span><span class="hl-0"> </span><span class="hl-7">Widget</span><span class="hl-0"> { </span><span class="hl-1">id</span><span class="hl-0">: </span><span class="hl-7">number</span><span class="hl-0">, </span><span class="hl-1">shape</span><span class="hl-0">: </span><span class="hl-3">&quot;square&quot;</span><span class="hl-0"> | </span><span class="hl-3">&quot;circle&quot;</span><span class="hl-0"> };</span><br/><span class="hl-0"> </span><span class="hl-4">const</span><span class="hl-0"> </span><span class="hl-5">tree</span><span class="hl-0"> = </span><span class="hl-4">new</span><span class="hl-0"> </span><span class="hl-6">BTree</span><span class="hl-0">&lt;</span><span class="hl-7">number</span><span class="hl-0">, </span><span class="hl-7">Widget</span><span class="hl-0">&gt;(</span><span class="hl-1">e</span><span class="hl-0"> </span><span class="hl-4">=&gt;</span><span class="hl-0"> </span><span class="hl-1">e</span><span class="hl-0">.</span><span class="hl-1">id</span><span class="hl-0">);</span><br/><span class="hl-0"> </span><span class="hl-1">tree</span><span class="hl-0">.</span><span class="hl-6">insert</span><span class="hl-0">({ </span><span class="hl-1">id:</span><span class="hl-0"> </span><span class="hl-8">3</span><span class="hl-0">, </span><span class="hl-1">shape:</span><span class="hl-0"> </span><span class="hl-3">&quot;square&quot;</span><span class="hl-0"> });</span><br/><span class="hl-0"> </span><span class="hl-1">tree</span><span class="hl-0">.</span><span class="hl-6">insert</span><span class="hl-0">({ </span><span class="hl-1">id:</span><span class="hl-0"> </span><span class="hl-8">1</span><span class="hl-0">, </span><span class="hl-1">shape:</span><span class="hl-0"> </span><span class="hl-3">&quot;circle&quot;</span><span class="hl-0"> });</span><br/><span class="hl-0"> </span><span class="hl-1">tree</span><span class="hl-0">.</span><span class="hl-6">insert</span><span class="hl-0">({ </span><span class="hl-1">id:</span><span class="hl-0"> </span><span class="hl-8">2</span><span class="hl-0">, </span><span class="hl-1">shape:</span><span class="hl-0"> </span><span class="hl-3">&quot;square&quot;</span><span class="hl-0"> });</span><br/><span class="hl-0"> </span><span class="hl-2">for</span><span class="hl-0"> (</span><span class="hl-4">let</span><span class="hl-0"> </span><span class="hl-1">path</span><span class="hl-0"> </span><span class="hl-4">of</span><span class="hl-0"> </span><span class="hl-1">tree</span><span class="hl-0">.</span><span class="hl-6">ascending</span><span class="hl-0">(</span><span class="hl-1">tree</span><span class="hl-0">.</span><span class="hl-6">first</span><span class="hl-0">())) {</span><br/><span class="hl-0"> </span><span class="hl-1">console</span><span class="hl-0">.</span><span class="hl-6">log</span><span class="hl-0">(</span><span class="hl-1">tree</span><span class="hl-0">.</span><span class="hl-6">at</span><span class="hl-0">(</span><span class="hl-1">path</span><span class="hl-0">));</span><br/><span class="hl-0"> }</span><br/><span class="hl-0"> </span><span class="hl-1">console</span><span class="hl-0">.</span><span class="hl-6">log</span><span class="hl-0">(</span><span class="hl-1">tree</span><span class="hl-0">.</span><span class="hl-6">at</span><span class="hl-0">(</span><span class="hl-1">tree</span><span class="hl-0">.</span><span class="hl-6">find</span><span class="hl-0">(</span><span class="hl-8">2</span><span class="hl-0">)));</span>
</code><button>Copy</button></pre>
<a id="md:see-reference-documentation" class="tsd-anchor"></a><h4><a href="#md:see-reference-documentation">See <a href="docs/index.html">Reference Documentation</a></a></h4><a id="md:paths" class="tsd-anchor"></a><h4><a href="#md:paths">Paths</a></h4><p>Many methods take and return Path objects. All paths should considered invalid after any mutation operation, besides those returned by the mutation operator. None of the public methods will mutate the given path, except for <code>moveNext</code> and <code>movePrior</code>. In general, don&#39;t hold on the path operations, they are intended to be short-lived.</p>
<a id="md:see-reference-documentation" class="tsd-anchor"></a><h4><a href="#md:see-reference-documentation">See <a href="https://digithought.github.io/Digitree/">Reference Documentation</a></a></h4><a id="md:paths" class="tsd-anchor"></a><h4><a href="#md:paths">Paths</a></h4><p>Many methods take and return Path objects. All paths should considered invalid after any mutation operation, besides those returned by the mutation operator. None of the public methods will mutate the given path, except for <code>moveNext</code> and <code>movePrior</code>. In general, don&#39;t hold on the path operations, they are intended to be short-lived.</p>
<pre><code class="language-ts"><span class="hl-0"> </span><span class="hl-1">tree</span><span class="hl-0">.</span><span class="hl-6">updateAt</span><span class="hl-0">(</span><span class="hl-1">tree</span><span class="hl-0">.</span><span class="hl-6">last</span><span class="hl-0">().</span><span class="hl-6">prior</span><span class="hl-0">(), </span><span class="hl-8">7</span><span class="hl-0">); </span><span class="hl-9">// this is fine</span><br/><span class="hl-0"> </span><br/><span class="hl-0"> </span><span class="hl-4">const</span><span class="hl-0"> </span><span class="hl-5">path1</span><span class="hl-0"> = </span><span class="hl-1">tree</span><span class="hl-0">.</span><span class="hl-6">last</span><span class="hl-0">();</span><br/><span class="hl-0"> </span><span class="hl-4">const</span><span class="hl-0"> </span><span class="hl-5">ninePath</span><span class="hl-0"> = </span><span class="hl-1">tree</span><span class="hl-0">.</span><span class="hl-6">updateAt</span><span class="hl-0">(</span><span class="hl-1">tree</span><span class="hl-0">.</span><span class="hl-6">find</span><span class="hl-0">(</span><span class="hl-8">5</span><span class="hl-0">), </span><span class="hl-8">9</span><span class="hl-0">);</span><br/><span class="hl-0"> </span><span class="hl-1">tree</span><span class="hl-0">.</span><span class="hl-6">updateAt</span><span class="hl-0">(</span><span class="hl-1">ninePath</span><span class="hl-0">, </span><span class="hl-8">8</span><span class="hl-0">); </span><span class="hl-9">// Fine, ninePath came from mutation</span><br/><span class="hl-0"> </span><span class="hl-9">//tree.updateAt(path1, 7); // DON&#39;T USE path1 - invalid after mutation</span>
</code><button>Copy</button></pre>
<a id="md:background" class="tsd-anchor"></a><h3><a href="#md:background">Background</a></h3><p>At one point, a colleague and I set about finding the fastest possible data structure for in-memory storage of datasets, small and large. We experimented in C++ with various highly optimized data structures. We inserted, deleted, and read from millions of data rows in various benchmarks. We figured that structures like AVL trees or red-black trees would be the fastest due to simple design, but in the end, a B+Tree implementation, not dissimilar in design to this one (though much faster in C++) was the clear winner. For some tests, they were about the same, but the other structures had terrible worst cases, whereas the B+Tree was reliably and consistently fast for a variety of workloads. In studying this further, we realized that just as disk operations like to be performed in blocks, the same is true for memory and processor caches.</p>
Expand Down
2 changes: 1 addition & 1 deletion docs/variables/NodeCapacity.html

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "digitree",
"version": "1.0.0",
"version": "1.0.1",
"target": "module",
"description": "Lightweight B+Tree implementation in TypeScript",
"main": "./dist/index.js",
Expand Down
9 changes: 6 additions & 3 deletions src/b-tree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,14 +91,16 @@ export class BTree<TKey, TEntry> {
/**
* Adds a value to the tree. Be sure to check the result, as the tree does not allow duplicate keys.
* Added entries are frozen to ensure immutability
* @returns path to the existing (on = true) or new (on = false) row. */
* @returns path to the new (on = true) or conflicting (on = false) row. */
insert(entry: TEntry): Path<TKey, TEntry> {
Object.freeze(entry); // Ensure immutability
const path = this.find(this.keyFromEntry(entry));
if (path.on) {
path.on = false;
return path;
}
this.insertAt(path, entry);
path.on = true;
return path;
}

Expand All @@ -116,8 +118,7 @@ export class BTree<TKey, TEntry> {
const newKey = this.keyFromEntry(newEntry);
if (this.compareKeys(oldKey, newKey) !== 0) { // if key changed, delete and re-insert
let newPath = this.insert(newEntry)
newPath.on = !newPath.on;
if (newPath.on) { // Didn't exists - insert succeeded
if (newPath.on) { // insert succeeded
this.deleteAt(path);
newPath = this.find(newKey); // Re-find the new path - delete might have completely changed the tree
}
Expand All @@ -144,6 +145,8 @@ export class BTree<TKey, TEntry> {
}

/** Inserts or updates depending on the existence of the given key, using callbacks to generate the new value.
* @param newEntry the new entry to insert if the key doesn't exist.
* @param getUpdated a callback to generate an updated entry if the key does exist. WARNING: don't mutate the tree in this callback.
* @returns path to new entry and whether an update or insert attempted.
* If getUpdated callback returns a row that is already present, the resulting path will not be on. */
merge(newEntry: TEntry, getUpdated: (existing: TEntry) => TEntry): [path: Path<TKey, TEntry>, wasUpdate: boolean] {
Expand Down
4 changes: 2 additions & 2 deletions test/b-tree.branching.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ describe('Branching BTree', () => {
function addRange(starting: number, count: number) {
const s = Math.sign(count);
for (let i = 0; i !== count; i += s) {
if (!tree.insert(i + starting)) { // Expects here is slow
if (!tree.insert(i + starting).on) { // Expects here is slow
throw new Error("Failed to insert " + (i + starting));
}
}
Expand All @@ -151,7 +151,7 @@ describe('Branching BTree', () => {
const range = [...Array(end - start + 1).keys()];
while (range.length) {
const index = Math.floor(Math.random() * range.length);
if (!tree.insert(range.splice(index, 1)[0])) {
if (!tree.insert(range.splice(index, 1)[0]).on) {
throw new Error("Failed to insert " + index);
}
}
Expand Down
2 changes: 1 addition & 1 deletion test/b-tree.dict.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ describe('Dictionary BTree', () => {
while (range.length) {
const index = Math.floor(Math.random() * range.length);
const n = range.splice(index, 1)[0];
if (!tree.insert({ id: n, value: n.toString() })) {
if (!tree.insert({ id: n, value: n.toString() }).on) {
throw new Error("Failed to insert " + index);
}
}
Expand Down
2 changes: 1 addition & 1 deletion test/b-tree.one-leaf.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ describe('One leaf, key-only, B+Tree', () => {
});

it('should insert a single entry correctly', () => {
expect(tree.insert(5).on).toBe(false);
expect(tree.insert(5).on).toBe(true);
expect(tree.insert(5).on).toBe(false);
expect(tree.find(5).on).toBe(true);
expect(tree.find(4).on).toBe(false);
});
Expand Down

0 comments on commit 2eda731

Please sign in to comment.