From 55b61597e045215fd819ead9970c80b930592b21 Mon Sep 17 00:00:00 2001 From: zFlxw Date: Sun, 5 Mar 2023 12:46:16 +0100 Subject: [PATCH 1/5] feat(data-struct): binary search tree --- data_structures/binary_search_tree.ts | 199 ++++++++++++++++++ .../test/binary_search_tree.test.ts | 53 +++++ 2 files changed, 252 insertions(+) create mode 100644 data_structures/binary_search_tree.ts create mode 100644 data_structures/test/binary_search_tree.test.ts diff --git a/data_structures/binary_search_tree.ts b/data_structures/binary_search_tree.ts new file mode 100644 index 00000000..19e04ca3 --- /dev/null +++ b/data_structures/binary_search_tree.ts @@ -0,0 +1,199 @@ +/** + * Represents a node of a binary search tree. + * + * @template T The type of the value stored in the node. + */ +class TreeNode { + constructor( + public data: T, + public leftChild?: TreeNode, + public rightChild?: TreeNode, + ) {} +} + +/** + * An implementation of a binary search tree. + * + * A binary tree is a tree with only two children per node. A binary search tree on top sorts the children according + * to following rules: + * - left child < parent node + * - right child > parent node + * - all children on the left side < root node + * - all children on the right side > root node + * + * For profound information about trees + * @see https://www.geeksforgeeks.org/introduction-to-tree-data-structure-and-algorithm-tutorials/ + * + * @template T The data type of the values in the binary tree. + */ +export class BinarySearchTree { + rootNode?: TreeNode; + + /** + * Instantiates the binary search tree. + * + * @param rootNode The root node. + */ + constructor() { + this.rootNode = undefined; + } + + /** + * Checks, if the binary search tree is empty, i. e. has no root node. + * + * @returns Whether the binary search tree is empty. + */ + isEmpty(): boolean { + return this.rootNode === undefined; + } + + /** + * Searches for data in the current binary search tree. + * + * @param data The data to search for. + */ + search(data: T): TreeNode { + if (!this.rootNode) { + throw new Error('No root node defined.'); + } + + let currentNode = this.rootNode; + while (currentNode.data !== data) { + if (data > currentNode.data) { + if (!currentNode.rightChild) { + throw new Error('No such element found.'); + } + + currentNode = currentNode.rightChild; + } else { + if (!currentNode.leftChild) { + throw new Error('No such element found.'); + } + + currentNode = currentNode.leftChild; + } + } + + return currentNode; + } + + /** + * Inserts the given data into the binary search tree. + * + * @param data The data to be stored in the binary search tree. + * @returns + */ + insert(data: T): TreeNode { + if (!this.rootNode) { + this.rootNode = new TreeNode(data); + return this.rootNode; + } + + let currentNode: TreeNode = this.rootNode; + while (true) { + if (data > currentNode.data) { + if (currentNode.rightChild) { + currentNode = currentNode.rightChild; + } else { + currentNode.rightChild = new TreeNode(data); + return currentNode.rightChild; + } + } else { + if (currentNode.leftChild) { + currentNode = currentNode.leftChild; + } else { + currentNode.leftChild = new TreeNode(data); + return currentNode.leftChild; + } + } + } + } + + /** + * Finds the minimum value of the binary search tree. + * + * @returns The minimum value of the binary search tree + */ + findMin(): T { + if (!this.rootNode) { + throw new Error('No root node defined'); + } + + const traverse = (node: TreeNode): T => { + return !node.leftChild ? node.data : traverse(node.leftChild); + }; + + return traverse(this.rootNode); + } + + /** + * Finds the maximum value of the binary search tree. + * + * @returns The maximum value of the binary search tree + */ + findMax(): T { + if (!this.rootNode) { + throw new Error('No root node defined'); + } + + const traverse = (node: TreeNode): T => { + return !node.rightChild ? node.data : traverse(node.rightChild); + }; + + return traverse(this.rootNode); + } + + /** + * Traverses to the binary search tree in in-order, i. e. it follow the schema of: + * Left Node -> Root Node -> Right Node + * + * @param node The node to start from. + * @param array The already found node data for recursive access. + * @returns + */ + inOrderTraversal(node?: TreeNode, array: T[] = []): T[] { + if (node) { + this.inOrderTraversal(node.leftChild, array); + array.push(node.data); + this.inOrderTraversal(node.rightChild, array); + } + + return array; + } + + /** + * Traverses to the binary search tree in pre-order, i. e. it follow the schema of: + * Root Node -> Left Node -> Right Node + * + * @param node The node to start from. + * @param array The already found node data for recursive access. + * @returns + */ + preOrderTraversal(node?: TreeNode, array: T[] = []): T[] { + if (node) { + array.push(node.data); + this.inOrderTraversal(node.leftChild, array); + this.inOrderTraversal(node.rightChild, array); + } + + return array; + } + + /** + * Traverses to the binary search tree in post-order, i. e. it follow the schema of: + * Left Node -> Right Node -> Root Node + * + * @param node The node to start from. + * @param array The already found node data for recursive access. + * @returns + */ + postOrderTraversal(node?: TreeNode, array: T[] = []): T[] { + if (node) { + this.inOrderTraversal(node.leftChild, array); + this.inOrderTraversal(node.rightChild, array); + array.push(node.data); + } + + return array; + } +} diff --git a/data_structures/test/binary_search_tree.test.ts b/data_structures/test/binary_search_tree.test.ts new file mode 100644 index 00000000..b377dd0d --- /dev/null +++ b/data_structures/test/binary_search_tree.test.ts @@ -0,0 +1,53 @@ +import { BinarySearchTree } from '../binary_search_tree'; + +describe('BinarySearchTree', () => { + describe('with filled binary search tree (insert)', () => { + let binarySearchTree: BinarySearchTree; + + beforeEach(() => { + binarySearchTree = new BinarySearchTree(); + binarySearchTree.insert(25); + binarySearchTree.insert(80); + binarySearchTree.insert(12); + binarySearchTree.insert(5); + binarySearchTree.insert(64); + }); + + it('should return false for isEmpty when binary search tree is not empty', () => { + expect(binarySearchTree.isEmpty()).toBeFalsy(); + }); + + it('should return correct root node for search', () => { + expect(binarySearchTree.rootNode?.data).toBe(25); + }); + + it('should insert a new leaf node and return the correct child of the previous one', () => { + binarySearchTree.insert(1); + expect(binarySearchTree.search(5).leftChild?.data).toBe(1); + }); + + it('should traverse in in-order through the tree', () => { + expect(binarySearchTree.inOrderTraversal(binarySearchTree.rootNode)).toStrictEqual([5, 12, 25, 64, 80]); + }); + + it('should traverse in pre-order through the tree', () => { + expect( + binarySearchTree.preOrderTraversal(binarySearchTree.rootNode), + ).toStrictEqual([25, 5, 12, 64, 80]); + }); + + it('should traverse in post-order through the tree', () => { + expect( + binarySearchTree.postOrderTraversal(binarySearchTree.rootNode), + ).toStrictEqual([5, 12, 64, 80, 25]); + }); + + it('should return the minimum value of the binary search tree', () => { + expect(binarySearchTree.findMin()).toBe(5); + }); + + it('should return the maximum value of the binary search tree', () => { + expect(binarySearchTree.findMax()).toBe(80); + }); + }); +}); From 84f0657d17cd0d6c7b66c404b33be1fea9985859 Mon Sep 17 00:00:00 2001 From: zFlxw Date: Sun, 5 Mar 2023 14:06:08 +0100 Subject: [PATCH 2/5] fix: improvements part 1 --- data_structures/binary_search_tree.ts | 86 ++++++++++++------- .../test/binary_search_tree.test.ts | 15 ++-- 2 files changed, 64 insertions(+), 37 deletions(-) diff --git a/data_structures/binary_search_tree.ts b/data_structures/binary_search_tree.ts index 19e04ca3..02f35d78 100644 --- a/data_structures/binary_search_tree.ts +++ b/data_structures/binary_search_tree.ts @@ -52,9 +52,9 @@ export class BinarySearchTree { * * @param data The data to search for. */ - search(data: T): TreeNode { + search(data: T): T | undefined { if (!this.rootNode) { - throw new Error('No root node defined.'); + return undefined; } let currentNode = this.rootNode; @@ -74,7 +74,7 @@ export class BinarySearchTree { } } - return currentNode; + return currentNode.data; } /** @@ -83,10 +83,10 @@ export class BinarySearchTree { * @param data The data to be stored in the binary search tree. * @returns */ - insert(data: T): TreeNode { + insert(data: T): void { if (!this.rootNode) { this.rootNode = new TreeNode(data); - return this.rootNode; + return; } let currentNode: TreeNode = this.rootNode; @@ -96,14 +96,14 @@ export class BinarySearchTree { currentNode = currentNode.rightChild; } else { currentNode.rightChild = new TreeNode(data); - return currentNode.rightChild; + return; } } else { if (currentNode.leftChild) { currentNode = currentNode.leftChild; } else { currentNode.leftChild = new TreeNode(data); - return currentNode.leftChild; + return; } } } @@ -116,7 +116,7 @@ export class BinarySearchTree { */ findMin(): T { if (!this.rootNode) { - throw new Error('No root node defined'); + throw new Error('Empty tree.'); } const traverse = (node: TreeNode): T => { @@ -133,7 +133,7 @@ export class BinarySearchTree { */ findMax(): T { if (!this.rootNode) { - throw new Error('No root node defined'); + throw new Error('Empty tree.'); } const traverse = (node: TreeNode): T => { @@ -147,53 +147,79 @@ export class BinarySearchTree { * Traverses to the binary search tree in in-order, i. e. it follow the schema of: * Left Node -> Root Node -> Right Node * - * @param node The node to start from. * @param array The already found node data for recursive access. * @returns */ - inOrderTraversal(node?: TreeNode, array: T[] = []): T[] { - if (node) { - this.inOrderTraversal(node.leftChild, array); - array.push(node.data); - this.inOrderTraversal(node.rightChild, array); + inOrderTraversal(array: T[] = []): T[] { + if (!this.rootNode) { + return array; } - return array; + const traverse = (node?: TreeNode, array: T[] = []): T[] => { + if (!node) { + return array; + } + + traverse(node.leftChild, array); + array.push(node.data); + traverse(node.rightChild, array); + return array; + }; + + return traverse(this.rootNode); } /** * Traverses to the binary search tree in pre-order, i. e. it follow the schema of: * Root Node -> Left Node -> Right Node * - * @param node The node to start from. * @param array The already found node data for recursive access. * @returns */ - preOrderTraversal(node?: TreeNode, array: T[] = []): T[] { - if (node) { - array.push(node.data); - this.inOrderTraversal(node.leftChild, array); - this.inOrderTraversal(node.rightChild, array); + preOrderTraversal(array: T[] = []): T[] { + if (!this.rootNode) { + return array; } - return array; + const traverse = (node?: TreeNode, array: T[] = []): T[] => { + if (!node) { + return array; + } + + array.push(node.data); + traverse(node.leftChild, array); + traverse(node.rightChild, array); + + return array; + }; + + return traverse(this.rootNode); } /** * Traverses to the binary search tree in post-order, i. e. it follow the schema of: * Left Node -> Right Node -> Root Node * - * @param node The node to start from. * @param array The already found node data for recursive access. * @returns */ - postOrderTraversal(node?: TreeNode, array: T[] = []): T[] { - if (node) { - this.inOrderTraversal(node.leftChild, array); - this.inOrderTraversal(node.rightChild, array); - array.push(node.data); + postOrderTraversal(array: T[] = []): T[] { + if (!this.rootNode) { + return array; } - return array; + const traverse = (node?: TreeNode, array: T[] = []): T[] => { + if (!node) { + return array; + } + + traverse(node.leftChild, array); + traverse(node.rightChild, array); + array.push(node.data); + + return array; + }; + + return traverse(this.rootNode); } } diff --git a/data_structures/test/binary_search_tree.test.ts b/data_structures/test/binary_search_tree.test.ts index b377dd0d..cf353e96 100644 --- a/data_structures/test/binary_search_tree.test.ts +++ b/data_structures/test/binary_search_tree.test.ts @@ -21,24 +21,25 @@ describe('BinarySearchTree', () => { expect(binarySearchTree.rootNode?.data).toBe(25); }); - it('should insert a new leaf node and return the correct child of the previous one', () => { - binarySearchTree.insert(1); - expect(binarySearchTree.search(5).leftChild?.data).toBe(1); + it('should search for data and return it.', () => { + expect(binarySearchTree.search(5)).toBe(5); }); it('should traverse in in-order through the tree', () => { - expect(binarySearchTree.inOrderTraversal(binarySearchTree.rootNode)).toStrictEqual([5, 12, 25, 64, 80]); + expect(binarySearchTree.inOrderTraversal()).toStrictEqual([5, 12, 25, 64, 80]); }); it('should traverse in pre-order through the tree', () => { + console.log(binarySearchTree.preOrderTraversal()); + expect( - binarySearchTree.preOrderTraversal(binarySearchTree.rootNode), - ).toStrictEqual([25, 5, 12, 64, 80]); + binarySearchTree.preOrderTraversal(), + ).toStrictEqual([25, 12, 5, 80, 64]); }); it('should traverse in post-order through the tree', () => { expect( - binarySearchTree.postOrderTraversal(binarySearchTree.rootNode), + binarySearchTree.postOrderTraversal(), ).toStrictEqual([5, 12, 64, 80, 25]); }); From ac6869e5e14ad689bc74d18ffb8f9396dc67bdeb Mon Sep 17 00:00:00 2001 From: zFlxw Date: Sun, 5 Mar 2023 15:30:39 +0100 Subject: [PATCH 3/5] fix: improvements part 2 --- data_structures/binary_search_tree.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/data_structures/binary_search_tree.ts b/data_structures/binary_search_tree.ts index 02f35d78..f9e4afdc 100644 --- a/data_structures/binary_search_tree.ts +++ b/data_structures/binary_search_tree.ts @@ -48,33 +48,33 @@ export class BinarySearchTree { } /** - * Searches for data in the current binary search tree. + * Checks whether the tree has the given data or not. * - * @param data The data to search for. + * @param data The data to check for. */ - search(data: T): T | undefined { + has(data: T): boolean { if (!this.rootNode) { - return undefined; + return false; } let currentNode = this.rootNode; while (currentNode.data !== data) { if (data > currentNode.data) { if (!currentNode.rightChild) { - throw new Error('No such element found.'); + return false; } currentNode = currentNode.rightChild; } else { if (!currentNode.leftChild) { - throw new Error('No such element found.'); + return false; } currentNode = currentNode.leftChild; } } - return currentNode.data; + return true; } /** From 06aa4c5b9fb571ad9f52300fab5cebe5a390c354 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20M=C3=BCller?= <34514239+appgurueu@users.noreply.github.com> Date: Thu, 9 Mar 2023 15:21:02 +0100 Subject: [PATCH 4/5] Fix failing tests --- data_structures/test/binary_search_tree.test.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/data_structures/test/binary_search_tree.test.ts b/data_structures/test/binary_search_tree.test.ts index cf353e96..69121985 100644 --- a/data_structures/test/binary_search_tree.test.ts +++ b/data_structures/test/binary_search_tree.test.ts @@ -21,8 +21,9 @@ describe('BinarySearchTree', () => { expect(binarySearchTree.rootNode?.data).toBe(25); }); - it('should search for data and return it.', () => { - expect(binarySearchTree.search(5)).toBe(5); + it('should return whether an element is in the set', () => { + expect(binarySearchTree.has(5)).toBe(true); + expect(binarySearchTree.has(42)).toBe(false); }); it('should traverse in in-order through the tree', () => { From 1d23d49781c7dffd85d088eafa23e3edbd7204a5 Mon Sep 17 00:00:00 2001 From: zFlxw Date: Fri, 31 Mar 2023 09:57:59 +0200 Subject: [PATCH 5/5] feat: pascals triangle --- maths/pascals_triangle.ts | 40 +++++++++++++++++++++++++++++ maths/test/pascals_triangle.test.ts | 11 ++++++++ 2 files changed, 51 insertions(+) create mode 100644 maths/pascals_triangle.ts create mode 100644 maths/test/pascals_triangle.test.ts diff --git a/maths/pascals_triangle.ts b/maths/pascals_triangle.ts new file mode 100644 index 00000000..cce4bfd1 --- /dev/null +++ b/maths/pascals_triangle.ts @@ -0,0 +1,40 @@ +/** + * Pascal's Triangle is an array of binomial coefficients. It can be used for unwrapping terms like + * (a + b)^5. + * To construct Pascal's Triangle you add the numbers above the child entry together. Here are the first five rows: + * 1 + * 1 1 + * 1 2 1 + * 1 3 3 1 + * 1 4 6 4 1 + * + * Time Complexity: quadratic (O(n^2)). + * + * @param n The exponent / The index of the searched row. + * @returns The nth row of Pascal's Triangle + * @see https://en.wikipedia.org/wiki/Pascal's_triangle + */ +export const pascalsTriangle = (n: number): number[] => { + let arr: number[][] = []; + for (let i: number = 0; i < n; i++) { + if (i === 0) { + arr.push([1]); + continue; + } + + let lastRow: number[] = arr[i - 1]; + let temp: number[] = []; + for (let j: number = 0; j < lastRow.length + 1; j++) { + if (j === 0 || j === lastRow.length) { + temp.push(1); + continue; + } + + temp.push(lastRow[j - 1] + lastRow[j]); + } + + arr.push(temp); + } + + return arr[arr.length - 1]; +}; diff --git a/maths/test/pascals_triangle.test.ts b/maths/test/pascals_triangle.test.ts new file mode 100644 index 00000000..7091ce13 --- /dev/null +++ b/maths/test/pascals_triangle.test.ts @@ -0,0 +1,11 @@ +import { pascalsTriangle } from "../pascals_triangle"; + +describe('pascalsTriangle', () => { + it.each([ + [2, [1, 1]], + [4, [1, 3, 3, 1]], + [6, [1, 5, 10, 10, 5, 1]], + ])('The %i th row should equal to %i', (n, expectation) => { + expect(pascalsTriangle(n)).toEqual(expectation); + }); +});