diff --git a/docs/classes/BTree.html b/docs/classes/BTree.html index bd9321b..b355d28 100644 --- a/docs/classes/BTree.html +++ b/docs/classes/BTree.html @@ -2,7 +2,7 @@ Allows for efficient storage and retrieval of data in a sorted manner.

Type Parameters

Constructors

Constructors

Properties

_root compare keyFromEntry @@ -40,42 +40,44 @@ upsert

Constructors

  • Type Parameters

    • TKey

    • TEntry

    Parameters

    • Optional keyFromEntry: ((entry) => TKey) = ...

      a function to extract the key from an entry. The default assumes the key is the entry itself.

    • Optional compare: ((a, b) => 0 | 1 | -1) = ...

      a comparison function for keys. The default uses < and > operators.

      -
        • (a, b): 0 | 1 | -1
        • Parameters

          Returns 0 | 1 | -1

    Returns BTree<TKey, TEntry>

Properties

_root: ITreeNode
compare: ((a, b) => 0 | 1 | -1) = ...

a comparison function for keys. The default uses < and > operators.

+
    • (a, b): 0 | 1 | -1
    • Parameters

      Returns 0 | 1 | -1

Returns BTree<TKey, TEntry>

Properties

_root: ITreeNode
compare: ((a, b) => 0 | 1 | -1) = ...

a comparison function for keys. The default uses < and > operators.

Type declaration

    • (a, b): 0 | 1 | -1
    • a comparison function for keys. The default uses < and > operators.

      -

      Parameters

      Returns 0 | 1 | -1

keyFromEntry: ((entry) => TKey) = ...

a function to extract the key from an entry. The default assumes the key is the entry itself.

+

Parameters

Returns 0 | 1 | -1

keyFromEntry: ((entry) => TKey) = ...

a function to extract the key from an entry. The default assumes the key is the entry itself.

Type declaration

    • (entry): TKey
    • a function to extract the key from an entry. The default assumes the key is the entry itself.

      -

      Parameters

      Returns TKey

Methods

Methods

Returns number

Generated using TypeDoc

\ No newline at end of file +

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/classes/KeyBound.html b/docs/classes/KeyBound.html index 0187ea9..8ad710c 100644 --- a/docs/classes/KeyBound.html +++ b/docs/classes/KeyBound.html @@ -1,4 +1,4 @@ -KeyBound | digitree

Class KeyBound<TKey>

Type Parameters

  • TKey

Constructors

constructor +KeyBound | digitree

Class KeyBound<TKey>

Type Parameters

  • TKey

Constructors

Properties

Constructors

Properties

inclusive: boolean = true
key: TKey

Generated using TypeDoc

\ No newline at end of file +

Constructors

Properties

inclusive: boolean = true
key: TKey

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/classes/KeyRange.html b/docs/classes/KeyRange.html index 860921e..44b14dd 100644 --- a/docs/classes/KeyRange.html +++ b/docs/classes/KeyRange.html @@ -1,6 +1,6 @@ KeyRange | digitree

Class KeyRange<TKey>

Used for range scans. Omitting first or last implies the end of the tree.

-

Type Parameters

  • TKey

Constructors

Type Parameters

  • TKey

Constructors

Properties

Constructors

Properties

first?: KeyBound<TKey>
isAscending: boolean = true
last?: KeyBound<TKey>

Generated using TypeDoc

\ No newline at end of file +

Constructors

Properties

first?: KeyBound<TKey>
isAscending: boolean = true
last?: KeyBound<TKey>

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/classes/Path.html b/docs/classes/Path.html index 093a8ae..3091bdf 100644 --- a/docs/classes/Path.html +++ b/docs/classes/Path.html @@ -1,11 +1,11 @@ Path | digitree

Class Path<TKey, TEntry>

Represents a cursor in a BTree. Invalid once mutation has occurred (unless it is the results of a mutation method). Do not change the properties of this object directly. Use the methods of the BTree class to manipulate it.

Member

on - true if the cursor is on an entry, false if it is between entries.

-

Type Parameters

  • TKey

  • TEntry

Constructors

Type Parameters

  • TKey

  • TEntry

Constructors

Properties

Methods

Constructors

Properties

branches: PathBranch<TKey>[]
leafIndex: number
leafNode: LeafNode<TEntry>
on: boolean

Methods

Generated using TypeDoc

\ No newline at end of file +

Constructors

Properties

branches: PathBranch<TKey>[]
leafIndex: number
leafNode: LeafNode<TEntry>
on: boolean

Methods

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/classes/PathBranch.html b/docs/classes/PathBranch.html index fc7bb21..10752b1 100644 --- a/docs/classes/PathBranch.html +++ b/docs/classes/PathBranch.html @@ -1,5 +1,5 @@ -PathBranch | digitree

Class PathBranch<TKey>

Type Parameters

  • TKey

Constructors

constructor +PathBranch | digitree

Class PathBranch<TKey>

Type Parameters

  • TKey

Constructors

Properties

Methods

Constructors

Properties

index: number
node: BranchNode<TKey>

Methods

Generated using TypeDoc

\ No newline at end of file +

Constructors

Properties

index: number
node: BranchNode<TKey>

Methods

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/index.html b/docs/index.html index 27cd01b..7a7daf5 100644 --- a/docs/index.html +++ b/docs/index.html @@ -30,7 +30,7 @@

As an ordered dictionary

  import { BTree } from 'digitree';
...
interface Widget { id: number, shape: "square" | "circle" };
const tree = new BTree<number, Widget>(e => e.id);
tree.insert({ id: 3, shape: "square" });
tree.insert({ id: 1, shape: "circle" });
tree.insert({ id: 2, shape: "square" });
for (let path of tree.ascending(tree.first())) {
console.log(tree.at(path));
}
console.log(tree.at(tree.find(2)));
-

See Reference Documentation

Paths

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 moveNext and movePrior. In general, don't hold on the path operations, they are intended to be short-lived.

+

See Reference Documentation

Paths

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 moveNext and movePrior. In general, don't hold on the path operations, they are intended to be short-lived.

  tree.updateAt(tree.last().prior(), 7);  // this is fine

const path1 = tree.last();
const ninePath = tree.updateAt(tree.find(5), 9);
tree.updateAt(ninePath, 8); // Fine, ninePath came from mutation
//tree.updateAt(path1, 7); // DON'T USE path1 - invalid after mutation

Background

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.

diff --git a/docs/variables/NodeCapacity.html b/docs/variables/NodeCapacity.html index c8f3218..803abca 100644 --- a/docs/variables/NodeCapacity.html +++ b/docs/variables/NodeCapacity.html @@ -1,2 +1,2 @@ NodeCapacity | digitree

Variable NodeCapacityConst

NodeCapacity: 64 = 64

Node capacity. Not configurable - not worth the runtime memory, when almost nobody will touch this

-

Generated using TypeDoc

\ No newline at end of file +

Generated using TypeDoc

\ No newline at end of file diff --git a/package.json b/package.json index 4e0e9e5..de1955f 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/b-tree.ts b/src/b-tree.ts index 5e7c35d..024d094 100644 --- a/src/b-tree.ts +++ b/src/b-tree.ts @@ -91,14 +91,16 @@ export class BTree { /** * 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 { 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; } @@ -116,8 +118,7 @@ export class BTree { 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 } @@ -144,6 +145,8 @@ export class BTree { } /** 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, wasUpdate: boolean] { diff --git a/test/b-tree.branching.test.ts b/test/b-tree.branching.test.ts index c65025e..3314be3 100644 --- a/test/b-tree.branching.test.ts +++ b/test/b-tree.branching.test.ts @@ -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)); } } @@ -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); } } diff --git a/test/b-tree.dict.test.ts b/test/b-tree.dict.test.ts index b9bf620..f3bf875 100644 --- a/test/b-tree.dict.test.ts +++ b/test/b-tree.dict.test.ts @@ -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); } } diff --git a/test/b-tree.one-leaf.test.ts b/test/b-tree.one-leaf.test.ts index 263b416..a3d9c48 100644 --- a/test/b-tree.one-leaf.test.ts +++ b/test/b-tree.one-leaf.test.ts @@ -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); });