From b4062be2abcea61f989cabbb971ea82ad5edff5e Mon Sep 17 00:00:00 2001 From: SOZEL <80200848+TruongNhanNguyen@users.noreply.github.com> Date: Mon, 8 Apr 2024 16:38:06 +0700 Subject: [PATCH 01/10] Refactor 0-1 Knapsack Implementation (#236) * ref: improve code readability, maintainabiity and edge case handling - Correctly throws an error if the length of the `weights` and `values` arrays are not equal - Use camelCase consistently through the codebase - Add type annotations to the function params and return types - Adding comments within the loops to clarify the logic - Ensure the function handles edge cases appropriately, such as when capacity is 0 or when weights and values arrays are empty * chore(docs): rewrite function docstring * style: format code using prettier --- dynamic_programming/knapsack.ts | 70 ++++++++++++++++----------------- 1 file changed, 34 insertions(+), 36 deletions(-) diff --git a/dynamic_programming/knapsack.ts b/dynamic_programming/knapsack.ts index 853e50ab..0bc51012 100644 --- a/dynamic_programming/knapsack.ts +++ b/dynamic_programming/knapsack.ts @@ -1,56 +1,54 @@ /** - * @function knapsack - * @description Given weights and values of n (numberOfItems) items, put these items in a knapsack of capacity to get the maximum total value in the knapsack. In other words, given two integer arrays values[0..n-1] and weights[0..n-1] which represent values and weights associated with n items respectively. Also given an integer capacity which represents knapsack capacity, find out the maximum value subset of values[] such that sum of the weights of this subset is smaller than or equal to capacity. You cannot break an item, either pick the complete item or don’t pick it (0-1 property). - * @Complexity_Analysis - * Space complexity - O(1) - * Time complexity (independent of input) : O(numberOfItems * capacity) - * - * @return maximum value subset of values[] such that sum of the weights of this subset is smaller than or equal to capacity. - * @see [Knapsack](https://www.geeksforgeeks.org/0-1-knapsack-problem-dp-10/) - * @example knapsack(3, 8, [3, 4, 5], [30, 50, 60]) = 90 + * Solves the 0-1 Knapsack Problem. + * @param capacity Knapsack capacity + * @param weights Array of item weights + * @param values Array of item values + * @returns Maximum value subset such that sum of the weights of this subset is smaller than or equal to capacity + * @throws If weights and values arrays have different lengths + * @see [Knapsack](https://www.geeksforgeeks.org/0-1-knapsack-problem-dp-10/) + * @example knapsack(3, [3, 4, 5], [30, 50, 60]) // Output: 90 */ + export const knapsack = ( capacity: number, weights: number[], values: number[] -) => { - if (weights.length != values.length) { +): number => { + if (weights.length !== values.length) { throw new Error( - 'weights and values arrays should have same number of elements' + 'Weights and values arrays should have the same number of elements' ) } - const numberOfItems = weights.length + const numberOfItems: number = weights.length - // Declaring a data structure to store calculated states/values + // Initializing a 2D array to store calculated states/values const dp: number[][] = new Array(numberOfItems + 1) - - for (let i = 0; i < dp.length; i++) { - // Placing an array at each index of dp to make it a 2d matrix - dp[i] = new Array(capacity + 1) - } + .fill(0) + .map(() => new Array(capacity + 1).fill(0)) // Loop traversing each state of dp - for (let i = 0; i < numberOfItems; i++) { - for (let j = 0; j <= capacity; j++) { - if (i == 0) { - if (j >= weights[i]) { - // grab the first item if it's weight is less than remaining weight (j) - dp[i][j] = values[i] - } else { - // if weight[i] is more than remaining weight (j) leave it - dp[i][j] = 0 - } - } else if (j < weights[i]) { - // if weight of current item (weights[i]) is more than remaining weight (j), leave the current item and just carry on previous items - dp[i][j] = dp[i - 1][j] + for (let itemIndex = 1; itemIndex <= numberOfItems; itemIndex++) { + const weight = weights[itemIndex - 1] + const value = values[itemIndex - 1] + for ( + let currentCapacity = 1; + currentCapacity <= capacity; + currentCapacity++ + ) { + if (weight <= currentCapacity) { + // Select the maximum value of including the current item or excluding it + dp[itemIndex][currentCapacity] = Math.max( + value + dp[itemIndex - 1][currentCapacity - weight], + dp[itemIndex - 1][currentCapacity] + ) } else { - // select the maximum of (if current weight is collected thus adding it's value) and (if current weight is not collected thus not adding it's value) - dp[i][j] = Math.max(dp[i - 1][j - weights[i]] + values[i], dp[i - 1][j]) + // If the current item's weight exceeds the current capacity, exclude it + dp[itemIndex][currentCapacity] = dp[itemIndex - 1][currentCapacity] } } } - // Return the final maximized value at last position of dp matrix - return dp[numberOfItems - 1][capacity] + // Return the final maximized value at the last position of the dp matrix + return dp[numberOfItems][capacity] } From e1f635a7a19f1d28954eb60e4bfd7de5beffbc46 Mon Sep 17 00:00:00 2001 From: SOZEL <80200848+TruongNhanNguyen@users.noreply.github.com> Date: Fri, 12 Apr 2024 15:14:05 +0700 Subject: [PATCH 02/10] ref: update `Heap` implementation (#237) - Improved comments for clarity and correctness. - Used default parameter values where applicable for better readability. - Consistently used `void` return type for functions without a return value. - Ensured consistent use of semicolons for statement termination. - Made minor adjustments to ensure consistency in naming and coding style. --- data_structures/heap/heap.ts | 56 ++++++++++++++---------------------- 1 file changed, 22 insertions(+), 34 deletions(-) diff --git a/data_structures/heap/heap.ts b/data_structures/heap/heap.ts index 100ce1b3..65ce9f0b 100644 --- a/data_structures/heap/heap.ts +++ b/data_structures/heap/heap.ts @@ -3,14 +3,12 @@ * In a complete binary tree each level is filled before lower levels are added * Each level is filled from left to right * - * In a (min|max) heap the value of every node is (less|greater) than that if its children + * In a (min|max) heap the value of every node is (less|greater) than that of its children * - * The heap if often implemented using an array structure. + * The heap is often implemented using an array structure. * In the array implementation, the relationship between a parent index and its two children * are ((parentindex * 2) + 1) and ((parentindex * 2) + 2) - * */ - export abstract class Heap { protected heap: T[] // A comparison function. Returns true if a should be the parent of b. @@ -23,17 +21,16 @@ export abstract class Heap { /** * Compares the value at parentIndex with the value at childIndex - * In a maxHeap the value at parentIndex should be larger than the value at childIndex - * In a minHeap the value at parentIndex should be smaller than the value at childIndex - * + * In a maxHeap, the value at parentIndex should be larger than the value at childIndex + * In a minHeap, the value at parentIndex should be smaller than the value at childIndex */ - private isRightlyPlaced(childIndex: number, parentIndex: number) { + private isRightlyPlaced(childIndex: number, parentIndex: number): boolean { return this.compare(this.heap[parentIndex], this.heap[childIndex]) } /** - * In a maxHeap the index with the larger value is returned - * In a minHeap the index with the smaller value is returned + * In a maxHeap, the index with the larger value is returned + * In a minHeap, the index with the smaller value is returned */ private getChildIndexToSwap( leftChildIndex: number, @@ -68,11 +65,11 @@ export abstract class Heap { return this.size() === 0 } - protected swap(a: number, b: number) { + protected swap(a: number, b: number): void { ;[this.heap[a], this.heap[b]] = [this.heap[b], this.heap[a]] } - protected bubbleUp(index = this.size() - 1): void { + protected bubbleUp(index: number = this.size() - 1): void { let parentIndex while (index > 0) { @@ -111,7 +108,7 @@ export abstract class Heap { } public check(): void { - return this._check() + this._check() } private _check(index: number = 0): void { @@ -122,14 +119,16 @@ export abstract class Heap { if ( this.heap[leftChildIndex] && !this.isRightlyPlaced(leftChildIndex, index) - ) + ) { throw new Error('Heap does not adhere to heap invariant') + } if ( this.heap[rightChildIndex] && !this.isRightlyPlaced(rightChildIndex, index) - ) + ) { throw new Error('Heap does not adhere to heap invariant') + } this._check(leftChildIndex) this._check(rightChildIndex) @@ -137,26 +136,17 @@ export abstract class Heap { } export class MinHeap extends Heap { - constructor( - compare = (a: T, b: T) => { - return a < b - } - ) { + constructor(compare: (a: T, b: T) => boolean = (a: T, b: T) => a < b) { super(compare) } } export class MaxHeap extends Heap { - constructor( - compare = (a: T, b: T) => { - return a > b - } - ) { + constructor(compare: (a: T, b: T) => boolean = (a: T, b: T) => a > b) { super(compare) } } -// Priority queue that supports increasePriority() in O(log(n)). The limitation is that there can only be a single element for each key, and the max number or keys must be specified at heap construction. Most of the functions are wrappers around MinHeap functions and update the keys array. export class PriorityQueue extends MinHeap { // Maps from the n'th node to its index within the heap. private keys: number[] @@ -166,29 +156,27 @@ export class PriorityQueue extends MinHeap { constructor( keys_index: (a: T) => number, num_keys: number, - compare = (a: T, b: T) => { - return a < b - } + compare: (a: T, b: T) => boolean = (a: T, b: T) => a < b ) { super(compare) this.keys = Array(num_keys).fill(-1) this.keys_index = keys_index } - protected swap(a: number, b: number) { + protected swap(a: number, b: number): void { const akey = this.keys_index(this.heap[a]) const bkey = this.keys_index(this.heap[b]) ;[this.keys[akey], this.keys[bkey]] = [this.keys[bkey], this.keys[akey]] super.swap(a, b) } - public insert(value: T) { + public insert(value: T): void { this.keys[this.keys_index(value)] = this.size() super.insert(value) } public extract(): T { - // Unmark the the highest priority element and set key to zero for the last element in the heap. + // Unmark the highest priority element and set key to zero for the last element in the heap. this.keys[this.keys_index(this.heap[0])] = -1 if (this.size() > 1) { this.keys[this.keys_index(this.heap[this.size() - 1])] = 0 @@ -196,8 +184,8 @@ export class PriorityQueue extends MinHeap { return super.extract() } - public increasePriority(idx: number, value: T) { - if (this.keys[idx] == -1) { + public increasePriority(idx: number, value: T): void { + if (this.keys[idx] === -1) { // If the key does not exist, insert the value. this.insert(value) return From 59984eef0047e9a888ef0be2d2a751fce32d58d5 Mon Sep 17 00:00:00 2001 From: Piotr Idzik <65706193+vil02@users.noreply.github.com> Date: Wed, 24 Apr 2024 21:41:51 +0200 Subject: [PATCH 03/10] Add codecov badge (#225) * Update DIRECTORY.md * docs: add codecov badge --------- Co-authored-by: autoprettier --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 5412c60a..5836b72f 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ TypeScript Repository of TheAlgorithms, which implements various algorithms and [![TypeScript Banner][banner]](DIRECTORY.md) + [![codecov](https://codecov.io/gh/TheAlgorithms/TypeScript/graph/badge.svg?token=Z51PSAC1FD)](https://codecov.io/gh/TheAlgorithms/TypeScript) [![Contributions Welcome][welcome]](CONTRIBUTING.md) [![Discord chat][chat]][discord-server] From a08a1226b6a0d186954d16712d252b0a2a04faf0 Mon Sep 17 00:00:00 2001 From: Piotr Idzik <65706193+vil02@users.noreply.github.com> Date: Sat, 25 May 2024 13:04:50 +0200 Subject: [PATCH 04/10] style: fix some eslint warnings (#240) * style: mark `stepSize` as `const` * style: fix some `eslint` warnings --- graph/dijkstra.ts | 4 ++-- search/jump_search.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/graph/dijkstra.ts b/graph/dijkstra.ts index 7804a592..1ad76fa0 100644 --- a/graph/dijkstra.ts +++ b/graph/dijkstra.ts @@ -1,4 +1,4 @@ -import { MinHeap, PriorityQueue } from '../data_structures/heap/heap' +import { PriorityQueue } from '../data_structures/heap/heap' /** * @function dijkstra * @description Compute the shortest path from a source node to all other nodes. The input graph is in adjacency list form. It is a multidimensional array of edges. graph[i] holds the edges for the i'th node. Each edge is a 2-tuple where the 0'th item is the destination node, and the 1'th item is the edge weight. @@ -32,7 +32,7 @@ export const dijkstra = ( distances[start] = 0 while (priorityQueue.size() > 0) { - const [node, _] = priorityQueue.extract() + const node = priorityQueue.extract()[0] graph[node].forEach(([child, weight]) => { const new_distance = distances[node] + weight if (new_distance < distances[child]) { diff --git a/search/jump_search.ts b/search/jump_search.ts index 9707dbf3..e54aa196 100644 --- a/search/jump_search.ts +++ b/search/jump_search.ts @@ -22,8 +22,8 @@ export const jumpSearch = (array: number[], target: number): number => { if (array.length === 0) return -1 // declare pointers for the current and next indexes and step size + const stepSize: number = Math.floor(Math.sqrt(array.length)) let currentIdx: number = 0, - stepSize: number = Math.floor(Math.sqrt(array.length)), nextIdx: number = stepSize while (array[nextIdx - 1] < target) { From 8c8a0ecf2de803390983876e48d62455bd3f51ec Mon Sep 17 00:00:00 2001 From: Piotr Idzik <65706193+vil02@users.noreply.github.com> Date: Thu, 6 Jun 2024 04:44:45 +0200 Subject: [PATCH 05/10] style: use `for-of` (#241) --- dynamic_programming/coin_change.ts | 10 +++++----- graph/prim.ts | 3 +-- sorts/counting_sort.ts | 2 +- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/dynamic_programming/coin_change.ts b/dynamic_programming/coin_change.ts index 0734fb9a..2e9e7b0d 100644 --- a/dynamic_programming/coin_change.ts +++ b/dynamic_programming/coin_change.ts @@ -17,12 +17,12 @@ export const coinChange = (money: number, coins: number[]): CoinChange => { minCoins[0] = 0 // Fill in the DP table - for (let i = 0; i < coins.length; i++) { + for (const coin of coins) { for (let j = 0; j <= money; j++) { - if (j >= coins[i]) { - if (minCoins[j] > 1 + minCoins[j - coins[i]]) { - minCoins[j] = 1 + minCoins[j - coins[i]] - lastCoin[j] = coins[i] + if (j >= coin) { + if (minCoins[j] > 1 + minCoins[j - coin]) { + minCoins[j] = 1 + minCoins[j - coin] + lastCoin[j] = coin } } } diff --git a/graph/prim.ts b/graph/prim.ts index 3261c633..b3f565c7 100644 --- a/graph/prim.ts +++ b/graph/prim.ts @@ -51,8 +51,7 @@ const add_children = ( priorityQueue: PriorityQueue, node: number ) => { - for (let i = 0; i < graph[node].length; ++i) { - const out_edge = graph[node][i] + for (const out_edge of graph[node]) { // By increasing the priority, we ensure we only add each vertex to the queue one time, and the queue will be at most size V. priorityQueue.increasePriority( out_edge[0], diff --git a/sorts/counting_sort.ts b/sorts/counting_sort.ts index cedad89f..49e76232 100644 --- a/sorts/counting_sort.ts +++ b/sorts/counting_sort.ts @@ -13,7 +13,7 @@ export const countingSort = (inputArr: number[], min: number, max: number) => { const count = new Array(max - min + 1).fill(0) - for (let i = 0; i < inputArr.length; i++) count[inputArr[i] - min]++ + for (const element of inputArr) count[element - min]++ count[0] -= 1 From d42b9623626eea39372bfcfa051a11cffbc49a1d Mon Sep 17 00:00:00 2001 From: Piotr Idzik <65706193+vil02@users.noreply.github.com> Date: Sat, 8 Jun 2024 12:50:47 +0200 Subject: [PATCH 06/10] style: lint `tries.ts` (#242) * style: resolve `consistent-indexed-object-style` * style: remove redundant constructor * style: resolve `non-inferrable-types` --- data_structures/tries/tries.ts | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/data_structures/tries/tries.ts b/data_structures/tries/tries.ts index 78dc1432..2ec99c94 100644 --- a/data_structures/tries/tries.ts +++ b/data_structures/tries/tries.ts @@ -5,12 +5,12 @@ class TrieNode { /** * An object that stores child nodes for each character in the alphabet. */ - children: { [key: string]: TrieNode } = {} + children: Record = {} /** * Indicates whether the node represents the end of a word. */ - isWord: boolean = false + isWord = false } /** @@ -22,11 +22,6 @@ export class Trie { */ root: TrieNode = new TrieNode() - /** - * Creates a new Trie instance. - */ - constructor() {} - /** * Inserts a word into the Trie. * @@ -51,7 +46,7 @@ export class Trie { * If false, the method returns true only if an exact match is found. * @returns True if the word (or prefix) is found in the Trie; otherwise, false. */ - public find(word: string, isPrefixMatch: boolean = false): boolean { + public find(word: string, isPrefixMatch = false): boolean { return this.searchNode(this.root, word, isPrefixMatch) } From 869135a83629d6df9f3e0050f9d1449046e30966 Mon Sep 17 00:00:00 2001 From: Piotr Idzik <65706193+vil02@users.noreply.github.com> Date: Tue, 11 Jun 2024 22:09:30 +0200 Subject: [PATCH 07/10] style: use `T[]` instead of `Array` (#243) --- sorts/shell_sort.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sorts/shell_sort.ts b/sorts/shell_sort.ts index 4b1061ba..1a409659 100644 --- a/sorts/shell_sort.ts +++ b/sorts/shell_sort.ts @@ -9,11 +9,11 @@ * Average case - O(n log(n)^2) * * @param {T[]} arr - The input array - * @return {Array} - The sorted array. + * @return {T[]} - The sorted array. * @see [Shell Sort] (https://www.geeksforgeeks.org/shellsort/) * @example shellSort([4, 1, 8, 10, 3, 2, 5, 0, 7, 6, 9]) = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] */ -export function shellSort(arr: T[]): Array { +export function shellSort(arr: T[]): T[] { // start with the biggest gap, reduce gap twice on each step for (let gap = arr.length >> 1; gap > 0; gap >>= 1) { for (let i = gap; i < arr.length; i++) { From e6d916e7bb80a43750fe5f6bbf92cefa9612ead2 Mon Sep 17 00:00:00 2001 From: Federico Fiaschi Date: Sun, 4 Aug 2024 07:50:19 +0200 Subject: [PATCH 08/10] feat: add fibonaccisearch algorithm (#244) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: add fibonaccisearch algorithm * test: add test for fibonacci search algorithm * fix: changed variable names and function return * remove redundant @function * fix tests * fix type * fix formatting --------- Co-authored-by: Lars Müller <34514239+appgurueu@users.noreply.github.com> Co-authored-by: Lars Mueller --- search/fibonacci_search.ts | 57 ++++++++++++++++++++++++++++ search/test/fibonacci_search.test.ts | 18 +++++++++ 2 files changed, 75 insertions(+) create mode 100644 search/fibonacci_search.ts create mode 100644 search/test/fibonacci_search.test.ts diff --git a/search/fibonacci_search.ts b/search/fibonacci_search.ts new file mode 100644 index 00000000..b0125277 --- /dev/null +++ b/search/fibonacci_search.ts @@ -0,0 +1,57 @@ +/** + * @description Fibonacci search algorithm for a sorted array. + * + * The algorithm searches for a specific value in a sorted array using Fibonacci numbers + * to divide the array into smaller subarrays. This algorithm is useful for large arrays where + * the cost of accessing elements is high. + * + * @param {number[]} array - sorted list of numbers + * @param {number} target - target number to search for + * @return {number | null} - index of the target number in the list, or null if not found + * @see [FibonacciSearch](https://www.geeksforgeeks.org/fibonacci-search/) + * @example fibonacciSearch([1,2,3], 2) => 1 + * @example fibonacciSearch([4,5,6], 2) => null + */ + +export const fibonacciSearch = ( + array: number[], + target: number +): number | null => { + const arrayLength = array.length + let a = 0 // (n-2)'th Fibonacci No. + let b = 1 // (n-1)'th Fibonacci No. + let c = a + b // n'th Fibonacci + + while (c < arrayLength) { + a = b + b = c + c = a + b + } + + let offset = -1 + + while (c > 1) { + let i = Math.min(offset + a, arrayLength - 1) + + if (array[i] < target) { + c = b + b = a + a = c - b + offset = i + } else if (array[i] > target) { + c = a + b = b - a + a = c - b + } else { + // Element found then return index + return i + } + } + + if (b && array[offset + 1] === target) { + return offset + 1 + } + + // Element not found then return null + return null +} diff --git a/search/test/fibonacci_search.test.ts b/search/test/fibonacci_search.test.ts new file mode 100644 index 00000000..5b2b54b7 --- /dev/null +++ b/search/test/fibonacci_search.test.ts @@ -0,0 +1,18 @@ +import { fibonacciSearch } from '../fibonacci_search' + +describe('Fibonacci search', () => { + test.each([ + [[1, 2, 3], 2, 1], + [[4, 5, 6], 2, null], + [[10, 22, 35, 40, 45, 50, 80, 82, 85, 90, 100], 85, 8], + [[], 1, null], + [[1], 1, 0], + [[1, 3, 5, 7, 9, 11, 13], 11, 5], + [[1, 3, 5, 7, 9, 11, 13], 8, null] + ])( + 'of %o, searching for %o, expected %i', + (array: number[], target: number, expected: number | null) => { + expect(fibonacciSearch(array, target)).toBe(expected) + } + ) +}) From 44127b253941c06c93e2815bd8a8e222373328a7 Mon Sep 17 00:00:00 2001 From: Federico Fiaschi Date: Mon, 5 Aug 2024 17:44:53 +0200 Subject: [PATCH 09/10] Exponential search (#247) * feat: add exponential search * test: add test for exponential search * fix : prettier fix * fix: added parameters to iterative binary search * fix: prettier fix #2 * fix: modified the return on binary search and related tests --- search/binary_search.ts | 32 ++++++++++----------- search/exponential_search.ts | 40 ++++++++++++++++++++++++++ search/test/binary_search.test.ts | 16 ++++++----- search/test/exponential_search.test.ts | 19 ++++++++++++ 4 files changed, 84 insertions(+), 23 deletions(-) create mode 100644 search/exponential_search.ts create mode 100644 search/test/exponential_search.test.ts diff --git a/search/binary_search.ts b/search/binary_search.ts index 39d380a4..cf86f39a 100644 --- a/search/binary_search.ts +++ b/search/binary_search.ts @@ -8,25 +8,25 @@ * * @param {number[]} array - sorted list of numbers * @param {number} target - target number to search for - * @return {number} - index of the target number in the list, or -1 if not found + * @return {number} - index of the target number in the list, or null if not found * @see [BinarySearch](https://www.geeksforgeeks.org/binary-search/) * @example binarySearch([1,2,3], 2) => 1 - * @example binarySearch([4,5,6], 2) => -1 + * @example binarySearch([4,5,6], 2) => null */ export const binarySearchIterative = ( array: number[], - target: number -): number => { - if (array.length === 0) return -1 - - // declare pointers for the start, middle and end indices - let start = 0, - end = array.length - 1, - middle = (start + end) >> 1 + target: number, + start: number = 0, + end: number = array.length - 1 +): number | null => { + if (array.length === 0) return null // ensure the target is within the bounds of the array - if (target < array[start] || target > array[end]) return -1 + if (target < array[start] || target > array[end]) return null + + // declare pointers for the middle index + let middle = (start + end) >> 1 while (array[middle] !== target && start <= end) { // if the target is less than the middle value, move the end pointer to be middle -1 to narrow the search space @@ -37,7 +37,7 @@ export const binarySearchIterative = ( middle = (start + end) >> 1 } // return the middle index if it is equal to target - return array[middle] === target ? middle : -1 + return array[middle] === target ? middle : null } export const binarySearchRecursive = ( @@ -45,16 +45,16 @@ export const binarySearchRecursive = ( target: number, start = 0, end = array.length - 1 -): number => { - if (array.length === 0) return -1 +): number | null => { + if (array.length === 0) return null // ensure the target is within the bounds of the array - if (target < array[start] || target > array[end]) return -1 + if (target < array[start] || target > array[end]) return null const middle = (start + end) >> 1 if (array[middle] === target) return middle // target found - if (start > end) return -1 // target not found + if (start > end) return null // target not found // if the target is less than the middle value, move the end pointer to be middle -1 to narrow the search space // otherwise, move the start pointer to be middle + 1 diff --git a/search/exponential_search.ts b/search/exponential_search.ts new file mode 100644 index 00000000..4a8eba47 --- /dev/null +++ b/search/exponential_search.ts @@ -0,0 +1,40 @@ +import { binarySearchIterative } from './binary_search' + +/** + * @description Exponential search algorithm for a sorted array. + * + * The algorithm searches for a specific value in a sorted array by first finding a range + * where the value may be present and then performing a binary search within that range. + * + * Compared with binary search, exponential search can be more convenient and advantageous + * in cases where the element to be searched is closer to the beginning of the array, + * thus avoiding several comparisons that would make the search more verbose. + * + * @param {number[]} array - sorted list of numbers + * @param {number} x - target number to search for + * @return {number | null} - index of the target number in the list, or null if not found + * @see [ExponentialSearch](https://www.geeksforgeeks.org/exponential-search/) + * @example exponentialSearch([1, 2, 3, 4, 5], 3) => 2 + * @example exponentialSearch([10, 20, 30, 40, 50], 35) => null + */ + +export const exponentialSearch = ( + array: number[], + x: number +): number | null => { + const arrayLength = array.length + if (arrayLength === 0) return null + + if (array[0] === x) return 0 + + let i = 1 + while (i < arrayLength && array[i] <= x) { + i = i * 2 + } + + const start = Math.floor(i / 2) + const end = Math.min(i, arrayLength - 1) + const result = binarySearchIterative(array, x, start, end) + + return result +} diff --git a/search/test/binary_search.test.ts b/search/test/binary_search.test.ts index 6ebd12d9..13b13251 100644 --- a/search/test/binary_search.test.ts +++ b/search/test/binary_search.test.ts @@ -2,7 +2,7 @@ import { binarySearchIterative, binarySearchRecursive } from '../binary_search' describe('BinarySearch', () => { const testArray: number[] = [1, 2, 3, 4] - type FunctionsArray = { (array: number[], index: number): number }[] + type FunctionsArray = { (array: number[], index: number): number | null }[] const functions: FunctionsArray = [ binarySearchIterative, binarySearchRecursive @@ -12,14 +12,16 @@ describe('BinarySearch', () => { it('should be defined', () => { expect(func(testArray, 2)).toBeDefined() }) - it('should return a number', () => { - expect(typeof func(testArray, 2)).toBe('number') + it('should return a number or null', () => { + expect( + typeof func(testArray, 2) === 'number' || func(testArray, 2) === null + ).toBe(true) }) - it('should return -1 if the target is not found in the array', () => { - expect(func(testArray, 5)).toBe(-1) + it('should return null if the target is not found in the array', () => { + expect(func(testArray, 5)).toBe(null) }) - it('should return -1 if there are no elements in the array', () => { - expect(func([], 5)).toBe(-1) + it('should return null if there are no elements in the array', () => { + expect(func([], 5)).toBe(null) }) it('should return the index of the target if it is found in the array', () => { expect(func(testArray, 2)).toBe(1) diff --git a/search/test/exponential_search.test.ts b/search/test/exponential_search.test.ts new file mode 100644 index 00000000..80f6a07f --- /dev/null +++ b/search/test/exponential_search.test.ts @@ -0,0 +1,19 @@ +import { exponentialSearch } from '../exponential_search' + +describe('Exponential search', () => { + test.each([ + [[1, 2, 3, 4, 5], 3, 2], + [[10, 20, 30, 40, 50], 35, null], + [[10, 20, 30, 40, 50], 10, 0], + [[10, 20, 30, 40, 50], 50, 4], + [[10, 20, 30, 40, 50], 60, null], + [[], 10, null], + [[1, 2, 3, 4, 5], 1, 0], + [[1, 2, 3, 4, 5], 5, 4] + ])( + 'of %o, searching for %o, expected %i', + (array: number[], target: number, expected: number | null) => { + expect(exponentialSearch(array, target)).toBe(expected) + } + ) +}) From 19b4ced86c99815f142d4a46a028f55487b8038a Mon Sep 17 00:00:00 2001 From: Aadish Jain <121042282+mapcrafter2048@users.noreply.github.com> Date: Wed, 16 Oct 2024 23:51:48 +0530 Subject: [PATCH 10/10] Added the implementation of the Edmond Karp algorithm along with test cases (#252) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Added the implementation for bijection method binary to decimal and euler method * Added the implementation of the edmondkarp along with tests * Update graph/edmondkarp.ts Co-authored-by: Lars Müller <34514239+appgurueu@users.noreply.github.com> * Update edmondkarp.ts * Update graph/edmondkarp.ts Co-authored-by: Lars Müller <34514239+appgurueu@users.noreply.github.com> * Implement the furhter changes to make the algorithm more refined * Updated the test cases Updated the test cases * Updated the code and rewrite some functions Updated the code and rewrite some functions * Implement the optimal stack queue implementation in the edmond karp algorithm * removed the bisection_method.ts, decimal_convert.ts, euler_method.ts * Revert changes to package.json and package-lock.json --------- Co-authored-by: Lars Müller <34514239+appgurueu@users.noreply.github.com> --- graph/edmonds_karp.ts | 97 +++++++++++++++++++++++++++++++++ graph/test/edmonds_karp.test.ts | 82 ++++++++++++++++++++++++++++ 2 files changed, 179 insertions(+) create mode 100644 graph/edmonds_karp.ts create mode 100644 graph/test/edmonds_karp.test.ts diff --git a/graph/edmonds_karp.ts b/graph/edmonds_karp.ts new file mode 100644 index 00000000..fb781a42 --- /dev/null +++ b/graph/edmonds_karp.ts @@ -0,0 +1,97 @@ +import { StackQueue } from '../data_structures/queue/stack_queue' + +/** + * @function edmondsKarp + * @description Compute the maximum flow from a source node to a sink node using the Edmonds-Karp algorithm. + * @Complexity_Analysis + * Time complexity: O(V * E^2) where V is the number of vertices and E is the number of edges. + * Space Complexity: O(E) due to residual graph representation. + * @param {[number, number][][]} graph - The graph in adjacency list form. + * @param {number} source - The source node. + * @param {number} sink - The sink node. + * @return {number} - The maximum flow from the source node to the sink node. + * @see https://en.wikipedia.org/wiki/Edmonds%E2%80%93Karp_algorithm + */ +export default function edmondsKarp( + graph: [number, number][][], + source: number, + sink: number +): number { + const n = graph.length + + // Initialize residual graph + const residualGraph: [number, number][][] = Array.from( + { length: n }, + () => [] + ) + + // Build residual graph from the original graph + for (let u = 0; u < n; u++) { + for (const [v, cap] of graph[u]) { + if (cap > 0) { + residualGraph[u].push([v, cap]) // Forward edge + residualGraph[v].push([u, 0]) // Reverse edge with 0 capacity + } + } + } + + const findAugmentingPath = (parent: (number | null)[]): number => { + const visited = Array(n).fill(false) + const queue = new StackQueue() + queue.enqueue(source) + visited[source] = true + parent[source] = null + + while (queue.length() > 0) { + const u = queue.dequeue() + for (const [v, cap] of residualGraph[u]) { + if (!visited[v] && cap > 0) { + parent[v] = u + visited[v] = true + if (v === sink) { + // Return the bottleneck capacity along the path + let pathFlow = Infinity + let current = v + while (parent[current] !== null) { + const prev = parent[current]! + const edgeCap = residualGraph[prev].find( + ([node]) => node === current + )![1] + pathFlow = Math.min(pathFlow, edgeCap) + current = prev + } + return pathFlow + } + queue.enqueue(v) + } + } + } + return 0 + } + + let maxFlow = 0 + const parent = Array(n).fill(null) + + while (true) { + const pathFlow = findAugmentingPath(parent) + if (pathFlow === 0) break // No augmenting path found + + // Update the capacities and reverse capacities in the residual graph + let v = sink + while (parent[v] !== null) { + const u = parent[v]! + // Update capacity of the forward edge + const forwardEdge = residualGraph[u].find(([node]) => node === v)! + forwardEdge[1] -= pathFlow + // Update capacity of the reverse edge + const reverseEdge = residualGraph[v].find(([node]) => node === u)! + reverseEdge[1] += pathFlow + + v = u + } + + maxFlow += pathFlow + } + + return maxFlow +} diff --git a/graph/test/edmonds_karp.test.ts b/graph/test/edmonds_karp.test.ts new file mode 100644 index 00000000..22711ab9 --- /dev/null +++ b/graph/test/edmonds_karp.test.ts @@ -0,0 +1,82 @@ +import edmondsKarp from '../edmonds_karp' + +describe('Edmonds-Karp Algorithm', () => { + it('should find the maximum flow in a simple graph', () => { + const graph: [number, number][][] = [ + [ + [1, 3], + [2, 2] + ], // Node 0: Edges to node 1 (capacity 3), and node 2 (capacity 2) + [[3, 2]], // Node 1: Edge to node 3 (capacity 2) + [[3, 3]], // Node 2: Edge to node 3 (capacity 3) + [] // Node 3: No outgoing edges + ] + const source = 0 + const sink = 3 + const maxFlow = edmondsKarp(graph, source, sink) + expect(maxFlow).toBe(4) + }) + + it('should find the maximum flow in a more complex graph', () => { + const graph: [number, number][][] = [ + [ + [1, 10], + [2, 10] + ], // Node 0: Edges to node 1 and node 2 (both capacity 10) + [ + [3, 4], + [4, 8] + ], // Node 1: Edges to node 3 (capacity 4), and node 4 (capacity 8) + [[4, 9]], // Node 2: Edge to node 4 (capacity 9) + [[5, 10]], // Node 3: Edge to node 5 (capacity 10) + [[5, 10]], // Node 4: Edge to node 5 (capacity 10) + [] // Node 5: No outgoing edges (sink) + ] + const source = 0 + const sink = 5 + const maxFlow = edmondsKarp(graph, source, sink) + expect(maxFlow).toBe(14) + }) + + it('should return 0 when there is no path from source to sink', () => { + const graph: [number, number][][] = [ + [], // Node 0: No outgoing edges + [], // Node 1: No outgoing edges + [] // Node 2: No outgoing edges (sink) + ] + const source = 0 + const sink = 2 + const maxFlow = edmondsKarp(graph, source, sink) + expect(maxFlow).toBe(0) + }) + + it('should handle graphs with no edges', () => { + const graph: [number, number][][] = [ + [], // Node 0: No outgoing edges + [], // Node 1: No outgoing edges + [] // Node 2: No outgoing edges + ] + const source = 0 + const sink = 2 + const maxFlow = edmondsKarp(graph, source, sink) + expect(maxFlow).toBe(0) + }) + + it('should handle graphs with self-loops', () => { + const graph: [number, number][][] = [ + [ + [0, 10], + [1, 10] + ], // Node 0: Self-loop with capacity 10, and edge to node 1 (capacity 10) + [ + [1, 10], + [2, 10] + ], // Node 1: Self-loop and edge to node 2 + [] // Node 2: No outgoing edges (sink) + ] + const source = 0 + const sink = 2 + const maxFlow = edmondsKarp(graph, source, sink) + expect(maxFlow).toBe(10) + }) +})