diff --git a/src/algorithms/cryptography/polynomial-hash/PolynomialHash.js b/src/algorithms/cryptography/polynomial-hash/PolynomialHash.js index c3a76f20..858faab9 100644 --- a/src/algorithms/cryptography/polynomial-hash/PolynomialHash.js +++ b/src/algorithms/cryptography/polynomial-hash/PolynomialHash.js @@ -23,7 +23,6 @@ export default class PolynomialHash { const charCodes = Array.from(word).map(char => this.charToNumber(char)); let hash = 0; - for (let charIndex = 0; charIndex < charCodes.length; charIndex += 1) { hash *= this.base; hash %= this.modulus; diff --git a/src/algorithms/cryptography/polynomial-hash/SimplePolynomialHash.js b/src/algorithms/cryptography/polynomial-hash/SimplePolynomialHash.js new file mode 100644 index 00000000..8e1d5123 --- /dev/null +++ b/src/algorithms/cryptography/polynomial-hash/SimplePolynomialHash.js @@ -0,0 +1,63 @@ +const DEFAULT_BASE = 17; + +export default class SimplePolynomialHash { + /** + * @param {number} [base] - Base number that is used to create the polynomial. + */ + constructor(base = DEFAULT_BASE) { + this.base = base; + } + + /** + * Function that creates hash representation of the word. + * + * Time complexity: O(word.length). + * + * @assumption: This version of the function doesn't use modulo operator. + * Thus it may produce number overflows by generating numbers that are + * bigger than Number.MAX_SAFE_INTEGER. This function is mentioned here + * for simplicity and LEARNING reasons. + * + * @param {string} word - String that needs to be hashed. + * @return {number} + */ + hash(word) { + let hash = 0; + for (let charIndex = 0; charIndex < word.length; charIndex += 1) { + hash += word.charCodeAt(charIndex) * (this.base ** charIndex); + } + + return hash; + } + + /** + * Function that creates hash representation of the word + * based on previous word (shifted by one character left) hash value. + * + * Recalculates the hash representation of a word so that it isn't + * necessary to traverse the whole word again. + * + * Time complexity: O(1). + * + * @assumption: This function doesn't use modulo operator and thus is not safe since + * it may deal with numbers that are bigger than Number.MAX_SAFE_INTEGER. This + * function is mentioned here for simplicity and LEARNING reasons. + * + * @param {number} prevHash + * @param {string} prevWord + * @param {string} newWord + * @return {number} + */ + roll(prevHash, prevWord, newWord) { + let hash = prevHash; + + const prevValue = prevWord.charCodeAt(0); + const newValue = newWord.charCodeAt(newWord.length - 1); + + hash -= prevValue; + hash /= this.base; + hash += newValue * (this.base ** (newWord.length - 1)); + + return hash; + } +} diff --git a/src/algorithms/cryptography/polynomial-hash/__test__/SimplePolynomialHash.test.js b/src/algorithms/cryptography/polynomial-hash/__test__/SimplePolynomialHash.test.js new file mode 100644 index 00000000..28c55196 --- /dev/null +++ b/src/algorithms/cryptography/polynomial-hash/__test__/SimplePolynomialHash.test.js @@ -0,0 +1,52 @@ +import SimplePolynomialHash from '../SimplePolynomialHash'; + +describe('PolynomialHash', () => { + it('should calculate new hash based on previous one', () => { + const bases = [3, 5]; + const frameSizes = [5, 10]; + + const text = 'Lorem Ipsum is simply dummy text of the printing and ' + + 'typesetting industry. Lorem Ipsum has been the industry\'s standard ' + + 'galley of type and \u{ffff} scrambled it to make a type specimen book. It ' + + 'electronic 耀 typesetting, remaining essentially unchanged. It was ' + + 'popularised in the 1960s with the release of Letraset sheets ' + + 'publishing software like Aldus 耀 PageMaker including versions of Lorem.'; + + // Check hashing for different prime base. + bases.forEach((base) => { + const polynomialHash = new SimplePolynomialHash(base); + + // Check hashing for different word lengths. + frameSizes.forEach((frameSize) => { + let previousWord = text.substr(0, frameSize); + let previousHash = polynomialHash.hash(previousWord); + + // Shift frame through the whole text. + for (let frameShift = 1; frameShift < (text.length - frameSize); frameShift += 1) { + const currentWord = text.substr(frameShift, frameSize); + const currentHash = polynomialHash.hash(currentWord); + const currentRollingHash = polynomialHash.roll(previousHash, previousWord, currentWord); + + // Check that rolling hash is the same as directly calculated hash. + expect(currentRollingHash).toBe(currentHash); + + previousWord = currentWord; + previousHash = currentHash; + } + }); + }); + }); + + it('should generate numeric hashed', () => { + const polynomialHash = new SimplePolynomialHash(); + + expect(polynomialHash.hash('Test')).toBe(604944); + expect(polynomialHash.hash('a')).toBe(97); + expect(polynomialHash.hash('b')).toBe(98); + expect(polynomialHash.hash('c')).toBe(99); + expect(polynomialHash.hash('d')).toBe(100); + expect(polynomialHash.hash('e')).toBe(101); + expect(polynomialHash.hash('ab')).toBe(1763); + expect(polynomialHash.hash('abc')).toBe(30374); + }); +});