Skip to content

Commit 51f496c

Browse files
committedAug 9, 2018
Add SimplePolynomialHash function.
1 parent d5be477 commit 51f496c

File tree

3 files changed

+115
-1
lines changed

3 files changed

+115
-1
lines changed
 

‎src/algorithms/cryptography/polynomial-hash/PolynomialHash.js

-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ export default class PolynomialHash {
2323
const charCodes = Array.from(word).map(char => this.charToNumber(char));
2424

2525
let hash = 0;
26-
2726
for (let charIndex = 0; charIndex < charCodes.length; charIndex += 1) {
2827
hash *= this.base;
2928
hash %= this.modulus;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
const DEFAULT_BASE = 17;
2+
3+
export default class SimplePolynomialHash {
4+
/**
5+
* @param {number} [base] - Base number that is used to create the polynomial.
6+
*/
7+
constructor(base = DEFAULT_BASE) {
8+
this.base = base;
9+
}
10+
11+
/**
12+
* Function that creates hash representation of the word.
13+
*
14+
* Time complexity: O(word.length).
15+
*
16+
* @assumption: This version of the function doesn't use modulo operator.
17+
* Thus it may produce number overflows by generating numbers that are
18+
* bigger than Number.MAX_SAFE_INTEGER. This function is mentioned here
19+
* for simplicity and LEARNING reasons.
20+
*
21+
* @param {string} word - String that needs to be hashed.
22+
* @return {number}
23+
*/
24+
hash(word) {
25+
let hash = 0;
26+
for (let charIndex = 0; charIndex < word.length; charIndex += 1) {
27+
hash += word.charCodeAt(charIndex) * (this.base ** charIndex);
28+
}
29+
30+
return hash;
31+
}
32+
33+
/**
34+
* Function that creates hash representation of the word
35+
* based on previous word (shifted by one character left) hash value.
36+
*
37+
* Recalculates the hash representation of a word so that it isn't
38+
* necessary to traverse the whole word again.
39+
*
40+
* Time complexity: O(1).
41+
*
42+
* @assumption: This function doesn't use modulo operator and thus is not safe since
43+
* it may deal with numbers that are bigger than Number.MAX_SAFE_INTEGER. This
44+
* function is mentioned here for simplicity and LEARNING reasons.
45+
*
46+
* @param {number} prevHash
47+
* @param {string} prevWord
48+
* @param {string} newWord
49+
* @return {number}
50+
*/
51+
roll(prevHash, prevWord, newWord) {
52+
let hash = prevHash;
53+
54+
const prevValue = prevWord.charCodeAt(0);
55+
const newValue = newWord.charCodeAt(newWord.length - 1);
56+
57+
hash -= prevValue;
58+
hash /= this.base;
59+
hash += newValue * (this.base ** (newWord.length - 1));
60+
61+
return hash;
62+
}
63+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import SimplePolynomialHash from '../SimplePolynomialHash';
2+
3+
describe('PolynomialHash', () => {
4+
it('should calculate new hash based on previous one', () => {
5+
const bases = [3, 5];
6+
const frameSizes = [5, 10];
7+
8+
const text = 'Lorem Ipsum is simply dummy text of the printing and '
9+
+ 'typesetting industry. Lorem Ipsum has been the industry\'s standard '
10+
+ 'galley of type and \u{ffff} scrambled it to make a type specimen book. It '
11+
+ 'electronic 耀 typesetting, remaining essentially unchanged. It was '
12+
+ 'popularised in the 1960s with the release of Letraset sheets '
13+
+ 'publishing software like Aldus 耀 PageMaker including versions of Lorem.';
14+
15+
// Check hashing for different prime base.
16+
bases.forEach((base) => {
17+
const polynomialHash = new SimplePolynomialHash(base);
18+
19+
// Check hashing for different word lengths.
20+
frameSizes.forEach((frameSize) => {
21+
let previousWord = text.substr(0, frameSize);
22+
let previousHash = polynomialHash.hash(previousWord);
23+
24+
// Shift frame through the whole text.
25+
for (let frameShift = 1; frameShift < (text.length - frameSize); frameShift += 1) {
26+
const currentWord = text.substr(frameShift, frameSize);
27+
const currentHash = polynomialHash.hash(currentWord);
28+
const currentRollingHash = polynomialHash.roll(previousHash, previousWord, currentWord);
29+
30+
// Check that rolling hash is the same as directly calculated hash.
31+
expect(currentRollingHash).toBe(currentHash);
32+
33+
previousWord = currentWord;
34+
previousHash = currentHash;
35+
}
36+
});
37+
});
38+
});
39+
40+
it('should generate numeric hashed', () => {
41+
const polynomialHash = new SimplePolynomialHash();
42+
43+
expect(polynomialHash.hash('Test')).toBe(604944);
44+
expect(polynomialHash.hash('a')).toBe(97);
45+
expect(polynomialHash.hash('b')).toBe(98);
46+
expect(polynomialHash.hash('c')).toBe(99);
47+
expect(polynomialHash.hash('d')).toBe(100);
48+
expect(polynomialHash.hash('e')).toBe(101);
49+
expect(polynomialHash.hash('ab')).toBe(1763);
50+
expect(polynomialHash.hash('abc')).toBe(30374);
51+
});
52+
});

0 commit comments

Comments
 (0)
Please sign in to comment.