Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 89c335b

Browse files
committedJul 26, 2018
Created Rabin Fingerprinting module within util directory
1 parent 86a5c77 commit 89c335b

File tree

2 files changed

+110
-0
lines changed

2 files changed

+110
-0
lines changed
 
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/**
2+
* Generates fingerprints using Rabin scheme with x = 2 (for potential compiler optimizations).
3+
* Guaranteed not to over or underflow if function assumptions are met.
4+
*/
5+
export default class RabinFingerprint {
6+
/**
7+
* @param { function() : number } [primeGenerator]
8+
* @assumes Output from any function call is prime less than Number.MAX_SAFE_INTEGER / 2.
9+
*/
10+
constructor(primeGenerator) {
11+
this.prime = primeGenerator();
12+
}
13+
14+
/**
15+
* @param { array[number] } [values]
16+
* @returns {number} - The hash value after digesting input.
17+
* @assumes All array elements are non-negative.
18+
* @note First element in array is considered to be oldest value.
19+
*/
20+
init(values) {
21+
this.val = 0;
22+
this.len = values.length;
23+
24+
for (let i = 0; i < values.length; i += 1) {
25+
this.val = (((this.val * 2) % this.prime) + (values[i] % this.prime)) % this.prime;
26+
}
27+
28+
return this.val;
29+
}
30+
31+
/*
32+
* @param {number} [oldValue]
33+
* @param {number} [newValue]
34+
* @returns {number} - The hash value after removing the oldest value & inserting the newest.
35+
* @assumes Instance has already been initialized.
36+
* @assumes oldValue is the oldest value still processed by the hash.
37+
* @assumes newValue is non-negative.
38+
*/
39+
roll(oldValue, newValue) {
40+
let oldVal = oldValue % this.prime;
41+
for (let i = 1; i < this.len; i += 1) {
42+
oldVal = (oldVal * 2) % this.prime;
43+
}
44+
this.val = (this.val + this.prime - (oldVal % this.prime)) % this.prime;
45+
46+
const newVal = newValue % this.prime;
47+
this.val = (((this.val * 2) % this.prime) + (newVal % this.prime)) % this.prime;
48+
49+
return this.val;
50+
}
51+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import RabinFingerprint from '../Rabin_Fingerprint';
2+
3+
describe('Rabin fingerprint Hash Family', () => {
4+
it('should hash deterministically', () => {
5+
const primeVals = [3, 5, 19, 53, 97, 401, 7039, 193939];
6+
for (let primeIdx = 0; primeIdx < primeVals.length; primeIdx += 1) {
7+
const primeVal = primeVals[primeIdx];
8+
const hasher = new RabinFingerprint(() => primeVal);
9+
10+
// Test basic values
11+
expect(hasher.init([])).toEqual(0);
12+
expect(hasher.init([1])).toEqual(1);
13+
14+
// Test overflow
15+
const largeVal = Number.MAX_SAFE_INTEGER;
16+
expect(hasher.init([primeVal])).toEqual(0);
17+
expect(hasher.init([largeVal])).toEqual(largeVal % primeVal);
18+
19+
const numLargeVal = 2; // 2 ^ numLargeVal fits in javascript number
20+
const largeValues = new Array(numLargeVal).fill(largeVal);
21+
22+
const expVal = ((largeVal % primeVal) * ((2 ** numLargeVal) - 1)) % primeVal;
23+
expect(hasher.init(largeValues)).toEqual(expVal);
24+
25+
// Test using Fermat's little theorem
26+
const fermatValues = new Array(primeVal).fill(primeVal);
27+
const numFermatTests = 100;
28+
for (let i = 0; i < numFermatTests; i += 1) {
29+
const randomValue = Math.floor(Math.random() * largeVal);
30+
fermatValues[0] = randomValue;
31+
expect(hasher.init(fermatValues)).toEqual(randomValue % primeVal);
32+
}
33+
}
34+
});
35+
36+
it('should roll appropriately', () => {
37+
const primeVals = [3, 5, 19, 53, 97, 401, 7039, 193939];
38+
39+
for (let primeIdx = 0; primeIdx < primeVals.length; primeIdx += 1) {
40+
const primeVal = primeVals[primeIdx];
41+
const hasher = new RabinFingerprint(() => primeVal);
42+
43+
// Test basic values
44+
const largeVal = Number.MAX_SAFE_INTEGER;
45+
expect(hasher.init([0])).toEqual(0);
46+
expect(hasher.roll(0, 1)).toEqual(1);
47+
expect(hasher.roll(1, primeVal)).toEqual(0);
48+
expect(hasher.roll(primeVal, largeVal)).toEqual(largeVal % primeVal);
49+
50+
const numRollTest = 100;
51+
let previousValue = largeVal;
52+
for (let i = 0; i < numRollTest; i += 1) {
53+
const randomVal = Math.floor(Math.random() * largeVal);
54+
expect(hasher.roll(previousValue, randomVal)).toEqual(randomVal % primeVal);
55+
previousValue = randomVal;
56+
}
57+
}
58+
});
59+
});

0 commit comments

Comments
 (0)
Please sign in to comment.