Skip to content

Commit 59548c6

Browse files
committedApr 24, 2018
Add Rabin.
1 parent 1437f0a commit 59548c6

File tree

2 files changed

+111
-0
lines changed

2 files changed

+111
-0
lines changed
 
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { rabinKarp, hashWord, reHashWord } from '../rabinKarp';
2+
3+
describe('rabinKarp', () => {
4+
it('should correctly calculates hash and re-hash', () => {
5+
expect(hashWord('a')).toBe(97);
6+
expect(hashWord('b')).toBe(98);
7+
expect(hashWord('abc')).toBe(941094);
8+
expect(hashWord('bcd')).toBe(950601);
9+
expect(reHashWord(hashWord('abc'), 'abc', 'bcd')).toBe(950601);
10+
expect(reHashWord(hashWord('abc'), 'abc', 'bcd')).toBe(hashWord('bcd'));
11+
});
12+
13+
it('should find substring in a string', () => {
14+
expect(rabinKarp('abcbcglx', 'abca')).toBe(-1);
15+
expect(rabinKarp('abcbcglx', 'bcgl')).toBe(3);
16+
expect(rabinKarp('abcxabcdabxabcdabcdabcy', 'abcdabcy')).toBe(15);
17+
expect(rabinKarp('abcxabcdabxabcdabcdabcy', 'abcdabca')).toBe(-1);
18+
expect(rabinKarp('abcxabcdabxaabcdabcabcdabcdabcy', 'abcdabca')).toBe(12);
19+
expect(rabinKarp('abcxabcdabxaabaabaaaabcdabcdabcy', 'aabaabaaa')).toBe(11);
20+
});
21+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
/**
2+
* A prime number used to create
3+
* the hash representation of a word
4+
*
5+
* Bigger the prime number,
6+
* bigger the hash value
7+
*/
8+
const PRIME = 97;
9+
10+
/**
11+
* Function that creates hash representation of the word.
12+
*
13+
* @param {string} word
14+
* @return {number}
15+
*/
16+
export function hashWord(word) {
17+
let hash = 0;
18+
19+
for (let charIndex = 0; charIndex < word.length; charIndex += 1) {
20+
hash += word[charIndex].charCodeAt(0) * (PRIME ** charIndex);
21+
}
22+
23+
return hash;
24+
}
25+
26+
/**
27+
* Function that creates hash representation of the word
28+
* based on previous word (shifted by one character left) hash value.
29+
*
30+
* Recalculates the hash representation of a word so that it isn't
31+
* necessary to traverse the whole word again
32+
*
33+
* @param {number} prevHash
34+
* @param {string} prevWord
35+
* @param {string} newWord
36+
* @return {number}
37+
*/
38+
export function reHashWord(prevHash, prevWord, newWord) {
39+
const newWordLastIndex = newWord.length - 1;
40+
let newHash = prevHash - prevWord[0].charCodeAt(0);
41+
newHash /= PRIME;
42+
newHash += newWord[newWordLastIndex].charCodeAt(0) * (PRIME ** newWordLastIndex);
43+
44+
return newHash;
45+
}
46+
47+
/**
48+
* @param {string} text
49+
* @param {string} word
50+
* @return {number}
51+
*/
52+
export function rabinKarp(text, word) {
53+
// Calculate word hash that we will use for comparison with other substring hashes.
54+
const wordHash = hashWord(word);
55+
56+
let prevSegment = null;
57+
let currentSegmentHash = null;
58+
59+
// Go through all substring of the text that may match
60+
for (let charIndex = 0; charIndex <= text.length - word.length; charIndex += 1) {
61+
const currentSegment = text.substring(charIndex, charIndex + word.length);
62+
63+
// Calculate the hash of current substring.
64+
if (currentSegmentHash === null) {
65+
currentSegmentHash = hashWord(currentSegment);
66+
} else {
67+
currentSegmentHash = reHashWord(currentSegmentHash, prevSegment, currentSegment);
68+
}
69+
70+
prevSegment = currentSegment;
71+
72+
// Compare the hash of current substring and seeking string.
73+
if (wordHash === currentSegmentHash) {
74+
// In case if hashes match let's check substring char by char.
75+
let numberOfMatches = 0;
76+
77+
for (let deepCharIndex = 0; deepCharIndex < word.length; deepCharIndex += 1) {
78+
if (word[deepCharIndex] === text[charIndex + deepCharIndex]) {
79+
numberOfMatches += 1;
80+
}
81+
}
82+
83+
if (numberOfMatches === word.length) {
84+
return charIndex;
85+
}
86+
}
87+
}
88+
89+
return -1;
90+
}

0 commit comments

Comments
 (0)
Please sign in to comment.