diff --git a/package.json b/package.json index 48121a7..68fb976 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,8 @@ "typecheck": "tsc --noEmit --skipLibCheck", "lint": "eslint ./**/*.ts", "lint:fix": "eslint --fix ./**/*.ts", - "prettier": "prettier --write './**/*.{js,ts,tsx}' --ignore-path '.gitignore'" + "prettier": "prettier --write './**/*.{js,ts,tsx}' --ignore-path '.gitignore'", + "check:all": "yarn test && yarn lint && yarn typecheck" }, "devDependencies": { "@types/jest": "^30.0.0", diff --git a/algorithms/dp/coin-change/coinChange.test.ts b/src/algorithms/dp/coin-change/coinChange.test.ts similarity index 100% rename from algorithms/dp/coin-change/coinChange.test.ts rename to src/algorithms/dp/coin-change/coinChange.test.ts diff --git a/algorithms/dp/coin-change/coinChange.ts b/src/algorithms/dp/coin-change/coinChange.ts similarity index 100% rename from algorithms/dp/coin-change/coinChange.ts rename to src/algorithms/dp/coin-change/coinChange.ts diff --git a/algorithms/dp/knapsack/knapsack.test.ts b/src/algorithms/dp/knapsack/knapsack.test.ts similarity index 100% rename from algorithms/dp/knapsack/knapsack.test.ts rename to src/algorithms/dp/knapsack/knapsack.test.ts diff --git a/algorithms/dp/knapsack/knapsack.ts b/src/algorithms/dp/knapsack/knapsack.ts similarity index 100% rename from algorithms/dp/knapsack/knapsack.ts rename to src/algorithms/dp/knapsack/knapsack.ts diff --git a/algorithms/graph/a-star/aStar.test.ts b/src/algorithms/graph/a-star/aStar.test.ts similarity index 100% rename from algorithms/graph/a-star/aStar.test.ts rename to src/algorithms/graph/a-star/aStar.test.ts diff --git a/algorithms/graph/a-star/aStar.ts b/src/algorithms/graph/a-star/aStar.ts similarity index 100% rename from algorithms/graph/a-star/aStar.ts rename to src/algorithms/graph/a-star/aStar.ts diff --git a/algorithms/graph/bellman-ford/bellmanFordSearch.test.ts b/src/algorithms/graph/bellman-ford/bellmanFordSearch.test.ts similarity index 100% rename from algorithms/graph/bellman-ford/bellmanFordSearch.test.ts rename to src/algorithms/graph/bellman-ford/bellmanFordSearch.test.ts diff --git a/algorithms/graph/bellman-ford/bellmanFordSearch.ts b/src/algorithms/graph/bellman-ford/bellmanFordSearch.ts similarity index 100% rename from algorithms/graph/bellman-ford/bellmanFordSearch.ts rename to src/algorithms/graph/bellman-ford/bellmanFordSearch.ts diff --git a/algorithms/graph/bfs/bfs.test.ts b/src/algorithms/graph/bfs/bfs.test.ts similarity index 100% rename from algorithms/graph/bfs/bfs.test.ts rename to src/algorithms/graph/bfs/bfs.test.ts diff --git a/algorithms/graph/bfs/bfs.ts b/src/algorithms/graph/bfs/bfs.ts similarity index 100% rename from algorithms/graph/bfs/bfs.ts rename to src/algorithms/graph/bfs/bfs.ts diff --git a/src/algorithms/graph/dfs/dfs.test.ts b/src/algorithms/graph/dfs/dfs.test.ts new file mode 100644 index 0000000..604ddd6 --- /dev/null +++ b/src/algorithms/graph/dfs/dfs.test.ts @@ -0,0 +1,163 @@ +import {dfs, dfsRecursive, reconstructPath} from './dfs'; + +describe('DFS Algorithm (Iterative)', () => { + test('should traverse a simple graph', () => { + const graph = [ + [1, 2], // Edges from vertex 0 + [0, 3, 4], // Edges from vertex 1 + [0, 5], // Edges from vertex 2 + [1], // Edges from vertex 3 + [1], // Edges from vertex 4 + [2], // Edges from vertex 5 + ]; + const {visited, predecessors} = dfs(graph, 0); + // Iterative DFS uses a stack (LIFO), so the last neighbor pushed + // is visited first — yielding a different order from recursive DFS. + expect(visited).toEqual([0, 2, 5, 1, 4, 3]); + expect(predecessors[0]).toBe(-1); + }); + + test('should handle disconnected graph', () => { + const graph = [ + [1], // Edges from vertex 0 + [0], // Edges from vertex 1 + [3], // Edges from vertex 2 + [2], // Edges from vertex 3 + [], // Edges from vertex 4 (isolated) + ]; + const {visited, predecessors} = dfs(graph, 0); + expect(visited).toEqual([0, 1]); + expect(predecessors[1]).toBe(0); + expect(predecessors[2]).toBe(-1); + expect(predecessors[3]).toBe(-1); + expect(predecessors[4]).toBe(-1); + }); + + test('should throw error for invalid start vertex', () => { + const graph = [[1, 2], [0], [0]]; + expect(() => dfs(graph, -1)).toThrow('Start vertex is out of range'); + expect(() => dfs(graph, 3)).toThrow('Start vertex is out of range'); + }); + + test('should handle a graph with a single vertex', () => { + const graph = [[]]; + const {visited, predecessors} = dfs(graph, 0); + expect(visited).toEqual([0]); + expect(predecessors).toEqual([-1]); + }); + + test('should handle cyclic graphs correctly', () => { + const graph = [ + [1, 2], // Edges from vertex 0 + [0, 2], // Edges from vertex 1 + [0, 1], // Edges from vertex 2 + ]; + const {visited} = dfs(graph, 0); + expect(visited.length).toBe(3); + expect(new Set(visited).size).toBe(3); + expect(visited[0]).toBe(0); + }); + + test('should handle large graphs efficiently', () => { + const largeGraph: number[][] = Array(1000) + .fill(0) + .map((_, i) => { + if (i === 0) return [1]; + if (i === 999) return [998]; + return [i - 1, i + 1]; + }); + + const startTime = performance.now(); + const {visited} = dfs(largeGraph, 0); + const endTime = performance.now(); + + expect(visited.length).toBe(1000); + expect(visited[0]).toBe(0); + expect(endTime - startTime).toBeLessThan(1000); + }); +}); + +describe('DFS Algorithm (Recursive)', () => { + test('should traverse a simple graph in depth-first order', () => { + const graph = [ + [1, 2], // Edges from vertex 0 + [0, 3, 4], // Edges from vertex 1 + [0, 5], // Edges from vertex 2 + [1], // Edges from vertex 3 + [1], // Edges from vertex 4 + [2], // Edges from vertex 5 + ]; + const {visited} = dfsRecursive(graph, 0); + expect(visited[0]).toBe(0); + expect(visited.length).toBe(6); + expect(new Set(visited).size).toBe(6); + // Recursive DFS follows adjacency list order: 0 -> 1 -> 3 -> 4 -> 2 -> 5 + expect(visited).toEqual([0, 1, 3, 4, 2, 5]); + }); + + test('should handle disconnected graph', () => { + const graph = [ + [1], // Edges from vertex 0 + [0], // Edges from vertex 1 + [3], // Edges from vertex 2 + [2], // Edges from vertex 3 + [], // Edges from vertex 4 (isolated) + ]; + const {visited} = dfsRecursive(graph, 0); + expect(visited).toEqual([0, 1]); + }); + + test('should throw error for invalid start vertex', () => { + const graph = [[1, 2], [0], [0]]; + expect(() => dfsRecursive(graph, -1)).toThrow('Start vertex is out of range'); + expect(() => dfsRecursive(graph, 3)).toThrow('Start vertex is out of range'); + }); + + test('should handle a graph with a single vertex', () => { + const graph = [[]]; + const {visited, predecessors} = dfsRecursive(graph, 0); + expect(visited).toEqual([0]); + expect(predecessors).toEqual([-1]); + }); +}); + +describe('DFS Recursive - stack depth limitation', () => { + test('should throw RangeError on a very deep linear graph', () => { + const depth = 20000; + const deepGraph: number[][] = Array(depth) + .fill(0) + .map((_, i) => (i < depth - 1 ? [i + 1] : [])); + + expect(() => dfsRecursive(deepGraph, 0)).toThrow(RangeError); + }); +}); + +describe('reconstructPath (DFS)', () => { + test('should reconstruct a valid path', () => { + const graph = [[1, 2], [3], [4], [], []]; + const {predecessors} = dfsRecursive(graph, 0); + const path = reconstructPath(0, 3, predecessors); + expect(path).toEqual([0, 1, 3]); + }); + + test('should handle path from vertex to itself', () => { + const graph = [[1], [2], []]; + const {predecessors} = dfsRecursive(graph, 0); + const path = reconstructPath(0, 0, predecessors); + expect(path).toEqual([0]); + }); + + test('should return null for unreachable vertices', () => { + const graph = [[1], [0], [3], [2]]; + const {predecessors} = dfsRecursive(graph, 0); + const path = reconstructPath(0, 2, predecessors); + expect(path).toBeNull(); + }); + + test('should handle invalid target vertex', () => { + const graph = [[1], [0]]; + const {predecessors} = dfsRecursive(graph, 0); + const path = reconstructPath(0, 2, predecessors); + expect(path).toBeNull(); + }); +}); diff --git a/src/algorithms/graph/dfs/dfs.ts b/src/algorithms/graph/dfs/dfs.ts new file mode 100644 index 0000000..c459d06 --- /dev/null +++ b/src/algorithms/graph/dfs/dfs.ts @@ -0,0 +1,124 @@ +/** + * Performs a depth-first search (DFS) traversal on a graph. + * DFS explores as far as possible along each branch before backtracking. + * + * @param {number[][]} graph - An adjacency list representation of the graph. + * @param {number} start - The starting vertex. + * @returns {{ visited: number[], predecessors: number[] }} + * An object containing the order of visited vertices + * and predecessors array for path reconstruction. + * @throws {Error} If the start vertex is out of range. + */ +export function dfs( + graph: number[][], + start: number, +): { + visited: number[]; + predecessors: number[]; +} { + if (start < 0 || start >= graph.length) { + throw new Error('Start vertex is out of range'); + } + const n = graph.length; + const predecessors: number[] = Array(n).fill(-1); + const seen: boolean[] = Array(n).fill(false); + const visited: number[] = []; + const stack: number[] = [start]; + seen[start] = true; + + while (stack.length > 0) { + const current = stack.pop()!; + visited.push(current); + + for (const neighbor of graph[current]) { + if (!seen[neighbor]) { + seen[neighbor] = true; + predecessors[neighbor] = current; + stack.push(neighbor); + } + } + } + + return {visited, predecessors}; +} + +/** + * Performs a recursive depth-first search (DFS) traversal on a graph. + * + * Note: This implementation uses the call stack for recursion, so it may throw + * a RangeError (Maximum call stack size exceeded) on graphs with depth + * exceeding ~10,000 vertices. For large or deep graphs, use the iterative + * {@link dfs} instead. + * + * @param {number[][]} graph - An adjacency list representation of the graph. + * @param {number} start - The starting vertex. + * @returns {{ visited: number[], predecessors: number[] }} + * An object containing the order of visited vertices + * and predecessors array for path reconstruction. + * @throws {Error} If the start vertex is out of range. + */ +export function dfsRecursive( + graph: number[][], + start: number, +): { + visited: number[]; + predecessors: number[]; +} { + if (start < 0 || start >= graph.length) { + throw new Error('Start vertex is out of range'); + } + const n = graph.length; + const predecessors: number[] = Array(n).fill(-1); + const seen: boolean[] = Array(n).fill(false); + const visited: number[] = []; + + function traverse(vertex: number): void { + seen[vertex] = true; + visited.push(vertex); + for (const neighbor of graph[vertex]) { + if (!seen[neighbor]) { + predecessors[neighbor] = vertex; + traverse(neighbor); + } + } + } + + traverse(start); + return {visited, predecessors}; +} + +/** + * Reconstructs the path from a source vertex to a target vertex + * using the predecessors array obtained from DFS. + * + * @param {number} source - The source vertex. + * @param {number} target - The target vertex. + * @param {number[]} predecessors - The predecessors array from DFS. + * @returns {number[] | null} The path from source to target, or null if no path exists. + */ +export function reconstructPath( + source: number, + target: number, + predecessors: number[], +): number[] | null { + if ( + target < 0 || + target >= predecessors.length || + (predecessors[target] === -1 && source !== target) + ) { + return null; + } + + const path: number[] = []; + let current = target; + + while (current !== -1) { + path.unshift(current); + if (current === source) { + break; + } + current = predecessors[current]; + } + + return path[0] === source ? path : null; +} diff --git a/algorithms/graph/dijkstra/dijkstra.test.ts b/src/algorithms/graph/dijkstra/dijkstra.test.ts similarity index 100% rename from algorithms/graph/dijkstra/dijkstra.test.ts rename to src/algorithms/graph/dijkstra/dijkstra.test.ts diff --git a/algorithms/graph/dijkstra/dijkstra.ts b/src/algorithms/graph/dijkstra/dijkstra.ts similarity index 100% rename from algorithms/graph/dijkstra/dijkstra.ts rename to src/algorithms/graph/dijkstra/dijkstra.ts diff --git a/algorithms/graph/topological-sort/topological-sort.test.ts b/src/algorithms/graph/topological-sort/topological-sort.test.ts similarity index 100% rename from algorithms/graph/topological-sort/topological-sort.test.ts rename to src/algorithms/graph/topological-sort/topological-sort.test.ts diff --git a/algorithms/graph/topological-sort/topological-sort.ts b/src/algorithms/graph/topological-sort/topological-sort.ts similarity index 100% rename from algorithms/graph/topological-sort/topological-sort.ts rename to src/algorithms/graph/topological-sort/topological-sort.ts diff --git a/algorithms/search/binary-search/binarySearch.test.ts b/src/algorithms/search/binary-search/binarySearch.test.ts similarity index 100% rename from algorithms/search/binary-search/binarySearch.test.ts rename to src/algorithms/search/binary-search/binarySearch.test.ts diff --git a/algorithms/search/binary-search/binarySearch.ts b/src/algorithms/search/binary-search/binarySearch.ts similarity index 100% rename from algorithms/search/binary-search/binarySearch.ts rename to src/algorithms/search/binary-search/binarySearch.ts diff --git a/algorithms/search/interpolation-search/interpolationSearch.test.ts b/src/algorithms/search/interpolation-search/interpolationSearch.test.ts similarity index 100% rename from algorithms/search/interpolation-search/interpolationSearch.test.ts rename to src/algorithms/search/interpolation-search/interpolationSearch.test.ts diff --git a/algorithms/search/interpolation-search/interpolationSearch.ts b/src/algorithms/search/interpolation-search/interpolationSearch.ts similarity index 100% rename from algorithms/search/interpolation-search/interpolationSearch.ts rename to src/algorithms/search/interpolation-search/interpolationSearch.ts diff --git a/algorithms/search/jump-search/jumpSearch.test.ts b/src/algorithms/search/jump-search/jumpSearch.test.ts similarity index 100% rename from algorithms/search/jump-search/jumpSearch.test.ts rename to src/algorithms/search/jump-search/jumpSearch.test.ts diff --git a/algorithms/search/jump-search/jumpSearch.ts b/src/algorithms/search/jump-search/jumpSearch.ts similarity index 100% rename from algorithms/search/jump-search/jumpSearch.ts rename to src/algorithms/search/jump-search/jumpSearch.ts diff --git a/algorithms/search/linear-search/linearSearch.test.ts b/src/algorithms/search/linear-search/linearSearch.test.ts similarity index 100% rename from algorithms/search/linear-search/linearSearch.test.ts rename to src/algorithms/search/linear-search/linearSearch.test.ts diff --git a/algorithms/search/linear-search/linearSearch.ts b/src/algorithms/search/linear-search/linearSearch.ts similarity index 100% rename from algorithms/search/linear-search/linearSearch.ts rename to src/algorithms/search/linear-search/linearSearch.ts diff --git a/algorithms/sorting/bubble-sort-counters/bubbleSortCounters.test.ts b/src/algorithms/sorting/bubble-sort-counters/bubbleSortCounters.test.ts similarity index 100% rename from algorithms/sorting/bubble-sort-counters/bubbleSortCounters.test.ts rename to src/algorithms/sorting/bubble-sort-counters/bubbleSortCounters.test.ts diff --git a/algorithms/sorting/bubble-sort-counters/bubbleSortCounters.ts b/src/algorithms/sorting/bubble-sort-counters/bubbleSortCounters.ts similarity index 100% rename from algorithms/sorting/bubble-sort-counters/bubbleSortCounters.ts rename to src/algorithms/sorting/bubble-sort-counters/bubbleSortCounters.ts diff --git a/algorithms/sorting/bubble-sort/bubbleSort.test.ts b/src/algorithms/sorting/bubble-sort/bubbleSort.test.ts similarity index 100% rename from algorithms/sorting/bubble-sort/bubbleSort.test.ts rename to src/algorithms/sorting/bubble-sort/bubbleSort.test.ts diff --git a/algorithms/sorting/bubble-sort/bubbleSort.ts b/src/algorithms/sorting/bubble-sort/bubbleSort.ts similarity index 100% rename from algorithms/sorting/bubble-sort/bubbleSort.ts rename to src/algorithms/sorting/bubble-sort/bubbleSort.ts diff --git a/algorithms/sorting/heap-sort/heapSort.test.ts b/src/algorithms/sorting/heap-sort/heapSort.test.ts similarity index 100% rename from algorithms/sorting/heap-sort/heapSort.test.ts rename to src/algorithms/sorting/heap-sort/heapSort.test.ts diff --git a/algorithms/sorting/heap-sort/heapSort.ts b/src/algorithms/sorting/heap-sort/heapSort.ts similarity index 100% rename from algorithms/sorting/heap-sort/heapSort.ts rename to src/algorithms/sorting/heap-sort/heapSort.ts diff --git a/algorithms/sorting/insertion-sort-counters/insertionSortCounters.test.ts b/src/algorithms/sorting/insertion-sort-counters/insertionSortCounters.test.ts similarity index 100% rename from algorithms/sorting/insertion-sort-counters/insertionSortCounters.test.ts rename to src/algorithms/sorting/insertion-sort-counters/insertionSortCounters.test.ts diff --git a/algorithms/sorting/insertion-sort-counters/insertionSortCounters.ts b/src/algorithms/sorting/insertion-sort-counters/insertionSortCounters.ts similarity index 100% rename from algorithms/sorting/insertion-sort-counters/insertionSortCounters.ts rename to src/algorithms/sorting/insertion-sort-counters/insertionSortCounters.ts diff --git a/algorithms/sorting/insertion-sort/insertionSort.test.ts b/src/algorithms/sorting/insertion-sort/insertionSort.test.ts similarity index 100% rename from algorithms/sorting/insertion-sort/insertionSort.test.ts rename to src/algorithms/sorting/insertion-sort/insertionSort.test.ts diff --git a/algorithms/sorting/insertion-sort/insertionSort.ts b/src/algorithms/sorting/insertion-sort/insertionSort.ts similarity index 100% rename from algorithms/sorting/insertion-sort/insertionSort.ts rename to src/algorithms/sorting/insertion-sort/insertionSort.ts diff --git a/algorithms/sorting/merge-sort-counters/mergeSortCounters.test.ts b/src/algorithms/sorting/merge-sort-counters/mergeSortCounters.test.ts similarity index 100% rename from algorithms/sorting/merge-sort-counters/mergeSortCounters.test.ts rename to src/algorithms/sorting/merge-sort-counters/mergeSortCounters.test.ts diff --git a/algorithms/sorting/merge-sort-counters/mergeSortCounters.ts b/src/algorithms/sorting/merge-sort-counters/mergeSortCounters.ts similarity index 100% rename from algorithms/sorting/merge-sort-counters/mergeSortCounters.ts rename to src/algorithms/sorting/merge-sort-counters/mergeSortCounters.ts diff --git a/algorithms/sorting/merge-sort/mergeSort.test.ts b/src/algorithms/sorting/merge-sort/mergeSort.test.ts similarity index 100% rename from algorithms/sorting/merge-sort/mergeSort.test.ts rename to src/algorithms/sorting/merge-sort/mergeSort.test.ts diff --git a/algorithms/sorting/merge-sort/mergeSort.ts b/src/algorithms/sorting/merge-sort/mergeSort.ts similarity index 100% rename from algorithms/sorting/merge-sort/mergeSort.ts rename to src/algorithms/sorting/merge-sort/mergeSort.ts diff --git a/algorithms/sorting/quick-sort/quickSort.test.ts b/src/algorithms/sorting/quick-sort/quickSort.test.ts similarity index 100% rename from algorithms/sorting/quick-sort/quickSort.test.ts rename to src/algorithms/sorting/quick-sort/quickSort.test.ts diff --git a/algorithms/sorting/quick-sort/quickSort.ts b/src/algorithms/sorting/quick-sort/quickSort.ts similarity index 100% rename from algorithms/sorting/quick-sort/quickSort.ts rename to src/algorithms/sorting/quick-sort/quickSort.ts diff --git a/algorithms/sorting/selection-sort-counters/selectionSortCounters.test.ts b/src/algorithms/sorting/selection-sort-counters/selectionSortCounters.test.ts similarity index 100% rename from algorithms/sorting/selection-sort-counters/selectionSortCounters.test.ts rename to src/algorithms/sorting/selection-sort-counters/selectionSortCounters.test.ts diff --git a/algorithms/sorting/selection-sort-counters/selectionSortCounters.ts b/src/algorithms/sorting/selection-sort-counters/selectionSortCounters.ts similarity index 100% rename from algorithms/sorting/selection-sort-counters/selectionSortCounters.ts rename to src/algorithms/sorting/selection-sort-counters/selectionSortCounters.ts diff --git a/algorithms/sorting/selection-sort/selectionSort.test.ts b/src/algorithms/sorting/selection-sort/selectionSort.test.ts similarity index 100% rename from algorithms/sorting/selection-sort/selectionSort.test.ts rename to src/algorithms/sorting/selection-sort/selectionSort.test.ts diff --git a/algorithms/sorting/selection-sort/selectionSort.ts b/src/algorithms/sorting/selection-sort/selectionSort.ts similarity index 100% rename from algorithms/sorting/selection-sort/selectionSort.ts rename to src/algorithms/sorting/selection-sort/selectionSort.ts diff --git a/algorithms/sorting/shell-sort-counters/shellSortCounters.test.ts b/src/algorithms/sorting/shell-sort-counters/shellSortCounters.test.ts similarity index 100% rename from algorithms/sorting/shell-sort-counters/shellSortCounters.test.ts rename to src/algorithms/sorting/shell-sort-counters/shellSortCounters.test.ts diff --git a/algorithms/sorting/shell-sort-counters/shellSortCounters.ts b/src/algorithms/sorting/shell-sort-counters/shellSortCounters.ts similarity index 100% rename from algorithms/sorting/shell-sort-counters/shellSortCounters.ts rename to src/algorithms/sorting/shell-sort-counters/shellSortCounters.ts diff --git a/algorithms/sorting/shell-sort/shellSort.test.ts b/src/algorithms/sorting/shell-sort/shellSort.test.ts similarity index 100% rename from algorithms/sorting/shell-sort/shellSort.test.ts rename to src/algorithms/sorting/shell-sort/shellSort.test.ts diff --git a/algorithms/sorting/shell-sort/shellSort.ts b/src/algorithms/sorting/shell-sort/shellSort.ts similarity index 100% rename from algorithms/sorting/shell-sort/shellSort.ts rename to src/algorithms/sorting/shell-sort/shellSort.ts diff --git a/algorithms/sorting/types.ts b/src/algorithms/sorting/types.ts similarity index 100% rename from algorithms/sorting/types.ts rename to src/algorithms/sorting/types.ts diff --git a/algorithms/string/lcs/lcs.test.ts b/src/algorithms/string/lcs/lcs.test.ts similarity index 100% rename from algorithms/string/lcs/lcs.test.ts rename to src/algorithms/string/lcs/lcs.test.ts diff --git a/algorithms/string/lcs/lcs.ts b/src/algorithms/string/lcs/lcs.ts similarity index 100% rename from algorithms/string/lcs/lcs.ts rename to src/algorithms/string/lcs/lcs.ts diff --git a/algorithms/string/levenshtein-distance/levenshteinDistance.test.ts b/src/algorithms/string/levenshtein-distance/levenshteinDistance.test.ts similarity index 100% rename from algorithms/string/levenshtein-distance/levenshteinDistance.test.ts rename to src/algorithms/string/levenshtein-distance/levenshteinDistance.test.ts diff --git a/algorithms/string/levenshtein-distance/levenshteinDistance.ts b/src/algorithms/string/levenshtein-distance/levenshteinDistance.ts similarity index 100% rename from algorithms/string/levenshtein-distance/levenshteinDistance.ts rename to src/algorithms/string/levenshtein-distance/levenshteinDistance.ts diff --git a/src/algorithms/string/rabin-karp/rabin-karp.test.ts b/src/algorithms/string/rabin-karp/rabin-karp.test.ts new file mode 100644 index 0000000..d236db0 --- /dev/null +++ b/src/algorithms/string/rabin-karp/rabin-karp.test.ts @@ -0,0 +1,82 @@ +import {rabinKarp} from './rabin-karp'; + +describe('Rabin-Karp Algorithm', () => { + test('should find a single occurrence', () => { + expect(rabinKarp('hello world', 'world')).toEqual([6]); + }); + + test('should find multiple occurrences', () => { + expect(rabinKarp('ababababab', 'abab')).toEqual([0, 2, 4, 6]); + }); + + test('should return empty array when pattern is not found', () => { + expect(rabinKarp('hello world', 'xyz')).toEqual([]); + }); + + test('should return empty array for empty pattern', () => { + expect(rabinKarp('hello', '')).toEqual([]); + }); + + test('should return empty array when pattern is longer than text', () => { + expect(rabinKarp('hi', 'hello')).toEqual([]); + }); + + test('should handle pattern equal to text', () => { + expect(rabinKarp('abc', 'abc')).toEqual([0]); + }); + + test('should handle single character pattern', () => { + expect(rabinKarp('abcabc', 'a')).toEqual([0, 3]); + }); + + test('should handle single character text and pattern', () => { + expect(rabinKarp('a', 'a')).toEqual([0]); + expect(rabinKarp('a', 'b')).toEqual([]); + }); + + test('should handle repeated characters', () => { + expect(rabinKarp('aaaaaa', 'aaa')).toEqual([0, 1, 2, 3]); + }); + + test('should handle pattern at the end of text', () => { + expect(rabinKarp('abcdef', 'def')).toEqual([3]); + }); + + test('should handle pattern at the beginning of text', () => { + expect(rabinKarp('abcdef', 'abc')).toEqual([0]); + }); + + test('should handle large text efficiently', () => { + const text = 'a'.repeat(10000) + 'b'; + const pattern = 'a'.repeat(100) + 'b'; + + const startTime = performance.now(); + const result = rabinKarp(text, pattern); + const endTime = performance.now(); + + expect(result).toEqual([9900]); + expect(endTime - startTime).toBeLessThan(1000); + }); + + test('should handle hash collisions correctly', () => { + // Use longer strings to increase collision potential + const text = 'The quick brown fox jumps over the lazy dog'; + const pattern = 'fox'; + expect(rabinKarp(text, pattern)).toEqual([16]); + }); + + test('should handle non-ASCII characters without integer overflow', () => { + const ch = String.fromCharCode(50000); + const text = ch.repeat(25); + const pattern = ch.repeat(3); + // Should find all 23 overlapping occurrences + const expected = Array.from({length: 23}, (_, i) => i); + expect(rabinKarp(text, pattern)).toEqual(expected); + }); + + test('should find pattern in Unicode text', () => { + const text = 'こんにちは世界、こんにちは日本'; + const pattern = 'こんにちは'; + expect(rabinKarp(text, pattern)).toEqual([0, 8]); + }); +}); diff --git a/src/algorithms/string/rabin-karp/rabin-karp.ts b/src/algorithms/string/rabin-karp/rabin-karp.ts new file mode 100644 index 0000000..c6836be --- /dev/null +++ b/src/algorithms/string/rabin-karp/rabin-karp.ts @@ -0,0 +1,66 @@ +/** + * Performs Rabin-Karp string search using a rolling hash. + * Finds all occurrences of a pattern in a text string. + * + * Uses a polynomial rolling hash with BigInt arithmetic + * to safely handle non-ASCII characters without integer overflow. + * + * @param {string} text - The text to search in. + * @param {string} pattern - The pattern to search for. + * @returns {number[]} An array of starting indices where the pattern is found. + */ +export function rabinKarp(text: string, pattern: string): number[] { + const results: number[] = []; + const n = text.length; + const m = pattern.length; + + if (m === 0) return results; + if (m > n) return results; + + const base = 256n; + const mod = 1_000_000_007n; + + // Compute base^(m-1) % mod for removing the leading character + let highPow = 1n; + for (let i = 0; i < m - 1; i++) { + highPow = (highPow * base) % mod; + } + + // Compute initial hash values for pattern and the first window of text + let patternHash = 0n; + let textHash = 0n; + for (let i = 0; i < m; i++) { + patternHash = (patternHash * base + BigInt(pattern.charCodeAt(i))) % mod; + textHash = (textHash * base + BigInt(text.charCodeAt(i))) % mod; + } + + for (let i = 0; i <= n - m; i++) { + // If hashes match, verify character by character to avoid false positives + if (patternHash === textHash) { + let match = true; + for (let j = 0; j < m; j++) { + if (text[i + j] !== pattern[j]) { + match = false; + break; + } + } + if (match) { + results.push(i); + } + } + + // Compute hash for the next window by rolling the hash + if (i < n - m) { + textHash = + ((textHash - BigInt(text.charCodeAt(i)) * highPow) * base + + BigInt(text.charCodeAt(i + m))) % + mod; + // Ensure non-negative (BigInt % can return negative) + if (textHash < 0n) { + textHash += mod; + } + } + } + + return results; +} diff --git a/data-structures/binary-tree/binaryTree.test.ts b/src/data-structures/binary-tree/binaryTree.test.ts similarity index 100% rename from data-structures/binary-tree/binaryTree.test.ts rename to src/data-structures/binary-tree/binaryTree.test.ts diff --git a/data-structures/binary-tree/binaryTree.ts b/src/data-structures/binary-tree/binaryTree.ts similarity index 100% rename from data-structures/binary-tree/binaryTree.ts rename to src/data-structures/binary-tree/binaryTree.ts diff --git a/data-structures/graph/graph.test.ts b/src/data-structures/graph/graph.test.ts similarity index 100% rename from data-structures/graph/graph.test.ts rename to src/data-structures/graph/graph.test.ts diff --git a/data-structures/graph/graph.ts b/src/data-structures/graph/graph.ts similarity index 100% rename from data-structures/graph/graph.ts rename to src/data-structures/graph/graph.ts diff --git a/data-structures/hash-table/hashTable.test.ts b/src/data-structures/hash-table/hashTable.test.ts similarity index 100% rename from data-structures/hash-table/hashTable.test.ts rename to src/data-structures/hash-table/hashTable.test.ts diff --git a/data-structures/hash-table/hashTable.ts b/src/data-structures/hash-table/hashTable.ts similarity index 100% rename from data-structures/hash-table/hashTable.ts rename to src/data-structures/hash-table/hashTable.ts diff --git a/data-structures/heap/minHeap.test.ts b/src/data-structures/heap/minHeap.test.ts similarity index 100% rename from data-structures/heap/minHeap.test.ts rename to src/data-structures/heap/minHeap.test.ts diff --git a/data-structures/heap/minHeap.ts b/src/data-structures/heap/minHeap.ts similarity index 100% rename from data-structures/heap/minHeap.ts rename to src/data-structures/heap/minHeap.ts diff --git a/data-structures/list-node/linkedList.test.ts b/src/data-structures/list-node/linkedList.test.ts similarity index 100% rename from data-structures/list-node/linkedList.test.ts rename to src/data-structures/list-node/linkedList.test.ts diff --git a/data-structures/list-node/linkedList.ts b/src/data-structures/list-node/linkedList.ts similarity index 100% rename from data-structures/list-node/linkedList.ts rename to src/data-structures/list-node/linkedList.ts diff --git a/data-structures/queue/circularQueue.test.ts b/src/data-structures/queue/circularQueue.test.ts similarity index 100% rename from data-structures/queue/circularQueue.test.ts rename to src/data-structures/queue/circularQueue.test.ts diff --git a/data-structures/queue/circularQueue.ts b/src/data-structures/queue/circularQueue.ts similarity index 100% rename from data-structures/queue/circularQueue.ts rename to src/data-structures/queue/circularQueue.ts diff --git a/data-structures/queue/queue.test.ts b/src/data-structures/queue/queue.test.ts similarity index 100% rename from data-structures/queue/queue.test.ts rename to src/data-structures/queue/queue.test.ts diff --git a/data-structures/queue/queue.ts b/src/data-structures/queue/queue.ts similarity index 100% rename from data-structures/queue/queue.ts rename to src/data-structures/queue/queue.ts diff --git a/data-structures/red-black-tree/redBlackTree.test.ts b/src/data-structures/red-black-tree/redBlackTree.test.ts similarity index 100% rename from data-structures/red-black-tree/redBlackTree.test.ts rename to src/data-structures/red-black-tree/redBlackTree.test.ts diff --git a/data-structures/red-black-tree/redBlackTree.ts b/src/data-structures/red-black-tree/redBlackTree.ts similarity index 100% rename from data-structures/red-black-tree/redBlackTree.ts rename to src/data-structures/red-black-tree/redBlackTree.ts diff --git a/data-structures/stack/stack.test.ts b/src/data-structures/stack/stack.test.ts similarity index 100% rename from data-structures/stack/stack.test.ts rename to src/data-structures/stack/stack.test.ts diff --git a/data-structures/stack/stack.ts b/src/data-structures/stack/stack.ts similarity index 100% rename from data-structures/stack/stack.ts rename to src/data-structures/stack/stack.ts diff --git a/data-structures/trie/trie.test.ts b/src/data-structures/trie/trie.test.ts similarity index 100% rename from data-structures/trie/trie.test.ts rename to src/data-structures/trie/trie.test.ts diff --git a/data-structures/trie/trie.ts b/src/data-structures/trie/trie.ts similarity index 100% rename from data-structures/trie/trie.ts rename to src/data-structures/trie/trie.ts diff --git a/data-structures/union-find/unionFind.test.ts b/src/data-structures/union-find/unionFind.test.ts similarity index 100% rename from data-structures/union-find/unionFind.test.ts rename to src/data-structures/union-find/unionFind.test.ts diff --git a/data-structures/union-find/unionFind.ts b/src/data-structures/union-find/unionFind.ts similarity index 100% rename from data-structures/union-find/unionFind.ts rename to src/data-structures/union-find/unionFind.ts diff --git a/math/binomial-coefficient/binomialCoefficient.test.ts b/src/math/binomial-coefficient/binomialCoefficient.test.ts similarity index 100% rename from math/binomial-coefficient/binomialCoefficient.test.ts rename to src/math/binomial-coefficient/binomialCoefficient.test.ts diff --git a/math/binomial-coefficient/binomialCoefficient.ts b/src/math/binomial-coefficient/binomialCoefficient.ts similarity index 100% rename from math/binomial-coefficient/binomialCoefficient.ts rename to src/math/binomial-coefficient/binomialCoefficient.ts diff --git a/math/euler-totient/eulerTotient.test.ts b/src/math/euler-totient/eulerTotient.test.ts similarity index 100% rename from math/euler-totient/eulerTotient.test.ts rename to src/math/euler-totient/eulerTotient.test.ts diff --git a/math/euler-totient/eulerTotient.ts b/src/math/euler-totient/eulerTotient.ts similarity index 100% rename from math/euler-totient/eulerTotient.ts rename to src/math/euler-totient/eulerTotient.ts diff --git a/math/extended-gcd/extendedGcd.test.ts b/src/math/extended-gcd/extendedGcd.test.ts similarity index 100% rename from math/extended-gcd/extendedGcd.test.ts rename to src/math/extended-gcd/extendedGcd.test.ts diff --git a/math/extended-gcd/extendedGcd.ts b/src/math/extended-gcd/extendedGcd.ts similarity index 100% rename from math/extended-gcd/extendedGcd.ts rename to src/math/extended-gcd/extendedGcd.ts diff --git a/math/factorial/factorial.test.ts b/src/math/factorial/factorial.test.ts similarity index 100% rename from math/factorial/factorial.test.ts rename to src/math/factorial/factorial.test.ts diff --git a/math/factorial/factorial.ts b/src/math/factorial/factorial.ts similarity index 100% rename from math/factorial/factorial.ts rename to src/math/factorial/factorial.ts diff --git a/math/fib-matrix/fibMatrix.test.ts b/src/math/fib-matrix/fibMatrix.test.ts similarity index 100% rename from math/fib-matrix/fibMatrix.test.ts rename to src/math/fib-matrix/fibMatrix.test.ts diff --git a/math/fib-matrix/fibMatrix.ts b/src/math/fib-matrix/fibMatrix.ts similarity index 100% rename from math/fib-matrix/fibMatrix.ts rename to src/math/fib-matrix/fibMatrix.ts diff --git a/math/gcd/gcb.test.ts b/src/math/gcd/gcb.test.ts similarity index 100% rename from math/gcd/gcb.test.ts rename to src/math/gcd/gcb.test.ts diff --git a/math/gcd/gcd.ts b/src/math/gcd/gcd.ts similarity index 100% rename from math/gcd/gcd.ts rename to src/math/gcd/gcd.ts diff --git a/math/matrix/matrix.test.ts b/src/math/matrix/matrix.test.ts similarity index 100% rename from math/matrix/matrix.test.ts rename to src/math/matrix/matrix.test.ts diff --git a/math/matrix/matrix.ts b/src/math/matrix/matrix.ts similarity index 100% rename from math/matrix/matrix.ts rename to src/math/matrix/matrix.ts diff --git a/math/mod-pow/modPow.test.ts b/src/math/mod-pow/modPow.test.ts similarity index 100% rename from math/mod-pow/modPow.test.ts rename to src/math/mod-pow/modPow.test.ts diff --git a/math/mod-pow/modPow.ts b/src/math/mod-pow/modPow.ts similarity index 100% rename from math/mod-pow/modPow.ts rename to src/math/mod-pow/modPow.ts diff --git a/math/prime-factors/primeFactors.test.ts b/src/math/prime-factors/primeFactors.test.ts similarity index 100% rename from math/prime-factors/primeFactors.test.ts rename to src/math/prime-factors/primeFactors.test.ts diff --git a/math/prime-factors/primeFactors.ts b/src/math/prime-factors/primeFactors.ts similarity index 100% rename from math/prime-factors/primeFactors.ts rename to src/math/prime-factors/primeFactors.ts diff --git a/math/sieve-of-eratosthenes/sieveOfEratosthenes.test.ts b/src/math/sieve-of-eratosthenes/sieveOfEratosthenes.test.ts similarity index 100% rename from math/sieve-of-eratosthenes/sieveOfEratosthenes.test.ts rename to src/math/sieve-of-eratosthenes/sieveOfEratosthenes.test.ts diff --git a/math/sieve-of-eratosthenes/sieveOfEratosthenes.ts b/src/math/sieve-of-eratosthenes/sieveOfEratosthenes.ts similarity index 100% rename from math/sieve-of-eratosthenes/sieveOfEratosthenes.ts rename to src/math/sieve-of-eratosthenes/sieveOfEratosthenes.ts diff --git a/tsconfig.json b/tsconfig.json index 46abf15..52740c2 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,8 +2,8 @@ "compilerOptions": { "module": "NodeNext", "moduleResolution": "NodeNext", - "target": "ES2025", - "lib": ["ES2025", "DOM"], + "target": "ES2024", + "lib": ["ES2024", "DOM"], "isolatedModules": true, "strict": true, "noImplicitReturns": true, @@ -15,9 +15,7 @@ "types": ["node", "jest"] }, "include": [ - "./data-structures/**/*", - "./math/**/*", - "./algorithms/**/*" + "./src/**/*" ], "exclude": ["node_modules"] }