Skip to content

Commit 3a389f7

Browse files
committed
feat: implement Rabin-Karp algorithm for substring search
1 parent 55ff0ad commit 3a389f7

File tree

2 files changed

+123
-0
lines changed

2 files changed

+123
-0
lines changed

String/RabinKarp.js

+87
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/**
2+
* Rabin-Karp algorithm for finding all occurrences of a pattern in a text.
3+
* It uses hashing to efficiently find substrings.
4+
*
5+
* Time Complexity:
6+
* - Average: O(n + m)
7+
* - Worst: O(n * m) due to potential hash collisions.
8+
*
9+
* Space Complexity: O(1) - only uses a fixed number of variables.
10+
*
11+
* @param {string} haystack - The text to search within.
12+
* @param {string} needle - The substring to search for.
13+
* @returns {number[]} - An array containing the starting indices of matches.
14+
* @throws {RangeError} - If either input is an empty string.
15+
*
16+
* @example:
17+
* const text = "abracadabra";
18+
* const pattern = "abra";
19+
* const result = rabinKarp(text, pattern);
20+
* console.log(result); // Output: [0, 7]
21+
*
22+
* Explanation:
23+
* The pattern "abra" occurs at index 0 and index 7 in the text "abracadabra".
24+
*
25+
* Here's how the algorithm works:
26+
* 1. It computes the hash of the pattern and the hash of the first window of the text.
27+
* 2. If the hashes match, it performs a character-by-character comparison to confirm a match.
28+
* 3. The algorithm then slides over the text, recalculating the hash for each new window.
29+
* 4. It continues until it has checked all possible windows in the text.
30+
*/
31+
export function rabinKarp(haystack, needle) {
32+
if (!haystack || !needle) {
33+
throw new RangeError('Input text and pattern cannot be empty.')
34+
}
35+
36+
const base = 256 // Character set size (extended ASCII)
37+
const prime = 101 // Prime number for hash computation
38+
const needleLength = needle.length
39+
const haystackLength = haystack.length
40+
const matches = []
41+
42+
let needleHash = 0 // Hash value for the pattern
43+
let haystackHash = 0 // Hash value for the current window in the text
44+
let hashMultiplier = 1
45+
46+
// Calculate the multiplier used for the leading character in the hash
47+
for (let i = 0; i < needleLength - 1; i++) {
48+
hashMultiplier = (hashMultiplier * base) % prime
49+
}
50+
51+
// Calculate the initial hash values for the pattern and the first window of the text
52+
for (let i = 0; i < needleLength; i++) {
53+
needleHash = (base * needleHash + needle.charCodeAt(i)) % prime
54+
haystackHash = (base * haystackHash + haystack.charCodeAt(i)) % prime
55+
}
56+
57+
// Slide over the text to check for matches
58+
for (let i = 0; i <= haystackLength - needleLength; i++) {
59+
// If the hash values match, check for character by character equality
60+
if (needleHash === haystackHash) {
61+
let j
62+
for (j = 0; j < needleLength; j++) {
63+
if (haystack[i + j] !== needle[j]) {
64+
break
65+
}
66+
}
67+
if (j === needleLength) {
68+
matches.push(i) // Pattern found, add index to results
69+
}
70+
}
71+
72+
// Calculate the hash value for the next window
73+
if (i < haystackLength - needleLength) {
74+
haystackHash =
75+
(base * (haystackHash - haystack.charCodeAt(i) * hashMultiplier) +
76+
haystack.charCodeAt(i + needleLength)) %
77+
prime
78+
79+
// Ensure the hash value is non-negative
80+
if (haystackHash < 0) {
81+
haystackHash += prime
82+
}
83+
}
84+
}
85+
86+
return matches
87+
}

String/test/RabinKarp.test.js

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { rabinKarp } from '../RabinKarp'
2+
3+
describe('Rabin-Karp Algorithm', () => {
4+
it('should find occurrences of the pattern in the text', () => {
5+
const haystack = 'abracadabra'
6+
const needle = 'abra'
7+
const result = rabinKarp(haystack, needle)
8+
expect(result).toEqual([0, 7])
9+
})
10+
11+
it('should return an empty array if the pattern is absent', () => {
12+
const haystack = 'hello world'
13+
const needle = 'test'
14+
const result = rabinKarp(haystack, needle)
15+
expect(result).toEqual([])
16+
})
17+
18+
it('should throw RangeError for empty input', () => {
19+
expect(() => rabinKarp('', 'pattern')).toThrow(RangeError)
20+
expect(() => rabinKarp('text', '')).toThrow(RangeError)
21+
})
22+
23+
it('should return empty for patterns longer than text', () => {
24+
const haystack = 'short'
25+
const needle = 'longerpattern'
26+
const result = rabinKarp(haystack, needle)
27+
expect(result).toEqual([])
28+
})
29+
30+
it('should handle single-character patterns', () => {
31+
const haystack = 'hello'
32+
const needle = 'e'
33+
const result = rabinKarp(haystack, needle)
34+
expect(result).toEqual([1])
35+
})
36+
})

0 commit comments

Comments
 (0)