From 2c885d5f8027123e8c4b155156d2b9877f40c9c7 Mon Sep 17 00:00:00 2001 From: HamsterCoder Date: Mon, 4 Dec 2023 16:44:01 +0100 Subject: [PATCH] Code basic BST. Add some tests. --- README.md | 6 ++ src/BinarySearchTree.test.ts | 61 ++++++++++++++++++ src/BinarySearchTree.ts | 122 +++++++++++++++++++++++++++++++++++ tsconfig.json | 2 +- 4 files changed, 190 insertions(+), 1 deletion(-) create mode 100644 src/BinarySearchTree.test.ts create mode 100644 src/BinarySearchTree.ts diff --git a/README.md b/README.md index 9299c13..703d329 100644 --- a/README.md +++ b/README.md @@ -4,12 +4,18 @@ This repository contains implementations of some data structures and algorithms. ### What do you have? +#### Sorts and shuffles + * Knuth Shuffle - returns a shuffled copy of the array * Shell Sort - returns a sorted copy of the array * Quick Sort - returns a sorted copy of the array * Insertion Sort - returns a sorted copy of the array + +#### Data structures * MaxHeap - allows for `add` and `removeMax` operations (with heapify) * MinHeap - allows for `add` and `removeMin` operations (with heapify) +* Heap - allows for `add` and `removeRoot` operations (with heapify) +* BinarySearchTree - allows for `set`, `get`, `min`, `max`, iteration (no deletion) ## Development diff --git a/src/BinarySearchTree.test.ts b/src/BinarySearchTree.test.ts new file mode 100644 index 0000000..8e5b0fe --- /dev/null +++ b/src/BinarySearchTree.test.ts @@ -0,0 +1,61 @@ +import { BinarySearchTree } from "./BinarySearchTree"; + +describe('BinarySearchTree', () => { + let bst: BinarySearchTree; + + beforeEach(() => { + bst = new BinarySearchTree((a, b) => a > b ? 1 : (a === b ? 0 : -1)); + }); + + test('Sets and gets one key-value', () => { + bst.set('a', 0); + expect(bst.get('a')).toEqual(0); + expect(bst.root?.count).toEqual(1); + }); + + test('Sets and gets a pair of key-values', () => { + bst.set('a', 0); + bst.set('b', 1); + + for (let node of bst) { + console.log(node?.key, node?.value); + } + + expect(bst.get('a')).toEqual(0); + expect(bst.get('b')).toEqual(1); + expect(bst.root?.count).toEqual(2); + }); + + test('Provides in order iteration', () => { + bst.set('a', 0); + bst.set('b', 1); + bst.set('c', 2); + bst.set('d', 3); + bst.set('e', 4); + bst.set('f', 5); + + expect(bst.root?.count).toEqual(6); + + let prev; + + for (let node of bst) { + if (!prev) { + prev = node; + } else { + expect(bst.compare(node?.key, prev?.key) === 1).toBeTruthy(); + } + } + }); + + test('Returns min and max', () => { + bst.set('a', 0); + bst.set('b', 1); + bst.set('c', 2); + bst.set('d', 3); + bst.set('e', 4); + bst.set('f', 5); + + expect(bst.min()?.key).toEqual('a'); + expect(bst.max()?.key).toEqual('f'); + }); +}); \ No newline at end of file diff --git a/src/BinarySearchTree.ts b/src/BinarySearchTree.ts new file mode 100644 index 0000000..c71e88b --- /dev/null +++ b/src/BinarySearchTree.ts @@ -0,0 +1,122 @@ +export interface TreeNode { + key: K; + value: V; + count: number; + left: TreeNode | null; + right: TreeNode | null; +} + +// TODO +// Add this method description to docs +// Rank: how many keys are smaller than k, bigger than k + +export class BinarySearchTree { + root: TreeNode | null = null; + compare: (a: K, b: K) => number; + + constructor(compare: (a: K, b: K) => number) { + this.compare = compare; + } + + [Symbol.iterator](this: BinarySearchTree): Iterator> { + const items = this.getSortedNodes(this.root); + let i = 0; + return { + next() { + if (i === items.length) { + return { done: true, value: undefined }; + } + + return { value: items[i++]}; + } + }; + } + + private getSortedNodes(node: TreeNode | null): TreeNode[] { + if (node === null) { + return []; + } + + return [ + ...this.getSortedNodes(node.left), + node, + ...this.getSortedNodes(node.right) + ]; + } + + get(key: K): V | null { + let root = this.root; + + while (root !== null) { + let cmp = this.compare(key, root.key); + + if (cmp < 0) { + root = root.left; + } else if (cmp > 0) { + root = root.right; + } else { + return root.value; + } + } + + return null; + } + + size(node: TreeNode | null): number { + if (node === null) { + return 0; + } + + return node.count; + } + + private _set(node: TreeNode | null, key: K, value: V): TreeNode { + if (node === null) { + return { + key, + value, + count: 1, + left: null, + right: null, + }; + } + + let cmp = this.compare(key, node.key); + + if (cmp < 0) { + node.left = this._set(node.left, key, value); + } else if (cmp > 0) { + node.right = this._set(node.right, key, value); + } else { + node.value = value; + } + + node.count = 1 + this.size(node.left) + this.size(node.right); + + return node; + } + + set(key: K, value: V): void { + this.root = this._set(this.root, key, value); + } + + min(): TreeNode | null { + let node = this.root; + + while (node?.left) { + node = node.left; + } + + return node; + } + + max(): TreeNode | null { + let node = this.root; + + while (node?.right) { + node = node.right; + } + + return node; + } +} diff --git a/tsconfig.json b/tsconfig.json index 258070b..fe0756d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,6 @@ { "compilerOptions": { - "target": "ES5", + "target": "es2015", "module": "CommonJS", "rootDir": "src", "outDir": "dist",