From d74f242ac4f8cedb4c5de106f2e571d94daeefe7 Mon Sep 17 00:00:00 2001 From: Mahfoudh Arous Date: Mon, 30 Oct 2023 06:30:31 +0100 Subject: [PATCH] Rabin Karp Search Algorithm (#1545) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Search: Rabin-Karp algorithm * Prettier Style * Search: Rabin-Karp adding reference * Search: Rabin-Karp styling and remove unecessary logging * Search: Rabin-Karp review notes * Simplify return * Updated Documentation in README.md --------- Co-authored-by: Lars Müller <34514239+appgurueu@users.noreply.github.com> Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 1 + Search/RabinKarp.js | 64 +++++++++++++++++++++++++++++++++++ Search/test/RabinKarp.test.js | 30 ++++++++++++++++ 3 files changed, 95 insertions(+) create mode 100644 Search/RabinKarp.js create mode 100644 Search/test/RabinKarp.test.js diff --git a/DIRECTORY.md b/DIRECTORY.md index 730df9cdf..1b2c40b3c 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -311,6 +311,7 @@ * [LinearSearch](Search/LinearSearch.js) * [Minesweeper](Search/Minesweeper.js) * [QuickSelectSearch](Search/QuickSelectSearch.js) + * [RabinKarp](Search/RabinKarp.js) * [SlidingWindow](Search/SlidingWindow.js) * [StringSearch](Search/StringSearch.js) * [TernarySearch](Search/TernarySearch.js) diff --git a/Search/RabinKarp.js b/Search/RabinKarp.js new file mode 100644 index 000000000..e6d6394ed --- /dev/null +++ b/Search/RabinKarp.js @@ -0,0 +1,64 @@ +/* + * Implements the Rabin-Karp algorithm for pattern searching. + * + * The Rabin-Karp algorithm is a string searching algorithm that uses hashing to find patterns in strings. + * It is faster than naive string matching algorithms because it avoids comparing every character in the text. + * + * This implementation uses a rolling hash function to efficiently compute the hash values of substrings. + * It also uses a modulo operator to reduce the size of the hash values, which helps to prevent hash collisions. + * + * The algorithm returns an array of indices where the pattern is found in the text. If the pattern is not + * found, the algorithm returns an empty array. + * + * [Reference](https://en.wikipedia.org/wiki/Rabin%E2%80%93Karp_algorithm) + */ + +const BASE = 256 // The number of characters in the alphabet +const MOD = 997 // A prime number used for the hash function + +function rabinKarpSearch(text, pattern) { + const patternLength = pattern.length + const textLength = text.length + const hashPattern = hash(pattern, patternLength) + const hashText = [] + const indices = [] + + // Calculate the hash of the first substring in the text + hashText[0] = hash(text, patternLength) + + // Precompute BASE^(patternLength-1) % MOD + const basePow = Math.pow(BASE, patternLength - 1) % MOD + + for (let i = 1; i <= textLength - patternLength + 1; i++) { + // Update the rolling hash by removing the first character + // and adding the next character in the text + hashText[i] = + (BASE * (hashText[i - 1] - text.charCodeAt(i - 1) * basePow) + + text.charCodeAt(i + patternLength - 1)) % + MOD + + // In case of hash collision, check character by character + if (hashText[i] < 0) { + hashText[i] += MOD + } + + // Check if the hashes match and perform a character-wise comparison + if (hashText[i] === hashPattern) { + if (text.substring(i, i + patternLength) === pattern) { + indices.push(i) // Store the index where the pattern is found + } + } + } + + return indices +} + +function hash(str, length) { + let hashValue = 0 + for (let i = 0; i < length; i++) { + hashValue = (hashValue * BASE + str.charCodeAt(i)) % MOD + } + return hashValue +} + +export { rabinKarpSearch } diff --git a/Search/test/RabinKarp.test.js b/Search/test/RabinKarp.test.js new file mode 100644 index 000000000..3d1009750 --- /dev/null +++ b/Search/test/RabinKarp.test.js @@ -0,0 +1,30 @@ +import { rabinKarpSearch } from '../RabinKarp' + +describe('Rabin-Karp Search', function () { + it('should find the pattern in the text', function () { + const text = 'ABABDABACDABABCABAB' + const pattern = 'DAB' + const expected = [4, 9] + + const result = rabinKarpSearch(text, pattern) + expect(result).to.deep.equal(expected) + }) + + it('should handle multiple occurrences of the pattern', function () { + const text = 'ABABABABABAB' + const pattern = 'ABAB' + const expected = [2, 4, 6, 8] + + const result = rabinKarpSearch(text, pattern) + expect(result).to.deep.equal(expected) + }) + + it('should handle pattern not found', function () { + const text = 'ABCD' + const pattern = 'XYZ' + const expected = [] + + const result = rabinKarpSearch(text, pattern) + expect(result).to.deep.equal(expected) + }) +})