Skip to content

Commit 7ac473f

Browse files
committedSep 22, 2018
hashmap functions compared
1 parent 8bc011e commit 7ac473f

File tree

4 files changed

+468
-7
lines changed

4 files changed

+468
-7
lines changed
 

‎benchmarks/hashing-functions.perf.js

Lines changed: 444 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,444 @@
1+
// nodemon benchmarks/hashing-functions.perf.js
2+
/* eslint-disable */
3+
const Benchmark = require('benchmark');
4+
const suite = new Benchmark.Suite;
5+
6+
const Stats = require('./stats');
7+
8+
const keys = [
9+
'',
10+
0,
11+
'aa',
12+
'stop',
13+
'pots',
14+
'@',
15+
'#!',
16+
'Ca',
17+
'DB',
18+
'polygenelubricants',
19+
'aoffckzdaoffckzdatafwjsh',
20+
'aoffckzdaoffckzdbhlijevx',
21+
'creamwove',
22+
'quists',
23+
'costarring',
24+
Math.PI,
25+
Number.MAX_VALUE,
26+
function a() {return;},
27+
{ a:1, 'max-d': 'test' },
28+
{ b:2 },
29+
Array(100).fill('1').join(''),
30+
];
31+
32+
let hashCodes = [];
33+
34+
// const primes = [7, 97, 997, 9973, 23251, 114451];
35+
// const primes = [7, 47, 223, 3967, 16127, 23251, 114451, 1046527];
36+
37+
// Centered triangular primes (3n**2 + 3n + 2) / 2.
38+
const primes = [19, 31, 109, 199, 409, 571, 631, 829, 1489, 1999, 2341, 2971, 3529, 4621];
39+
40+
const MAX = 109; // bucket size
41+
42+
// add tests
43+
suite
44+
// .add('hashCode31Shifting32bit', function() {
45+
// runner(hashCode31Shifting32bit);
46+
// })
47+
// .add('hashCode524287Shifting32bitSafeMod', function() {
48+
// runner(hashCode524287Shifting32bitSafeMod);
49+
// })
50+
// .add('hashCode524287Shifting32bitSafeModBefore', function() {
51+
// runner(hashCode524287Shifting32bitSafeModBefore);
52+
// })
53+
// .add('hashCode3Shifting', function() {
54+
// runner(hashCode3Shifting);
55+
// })
56+
// .add('hashFNV1a', function() {
57+
// runner(hashFNV1a);
58+
// })
59+
.add('murmurhash3_32_gc', function() {
60+
runner(murmurhash3_32_gc);
61+
})
62+
.add('HashJavaMultiplication', function() {
63+
runner(HashJavaMultiplication);
64+
})
65+
.add('hashFNV1a32bitSafeModBefore', function() {
66+
runner(hashFNV1a32bitSafeModBefore);
67+
})
68+
.add('hashFNV1a32bitSafeModAfter', function() {
69+
runner(hashFNV1a32bitSafeModAfter);
70+
})
71+
.add('hashFNV1a32bitSafeModAfterChop', function() {
72+
runner(hashFNV1a32bitSafeModAfterChop);
73+
})
74+
.add('fnv1a', function() {
75+
runner(fnv1a);
76+
})
77+
.add('hashFNV1a32bitSafeModAfterParams', function() {
78+
runner(hashFNV1a32bitSafeModAfterParams);
79+
})
80+
// .add('HashJavaShifting', function() {
81+
// runner(HashJavaShifting);
82+
// })
83+
//
84+
// too slow 30k ops/s vs 300k-700k ops/s
85+
//
86+
// .add('hashCode31Shifting', function() {
87+
// runner(hashCode31Shifting);
88+
// })
89+
// .add('hashCode41Multiplication', function() {
90+
// runner(hashCode41Multiplication);
91+
// })
92+
// .add('hashCode31Multiplication', function() {
93+
// runner(hashCode31Multiplication);
94+
// })
95+
96+
// add listeners
97+
.on('start', () => {
98+
console.log('Fasten your belt...');
99+
})
100+
.on('cycle', function(event) {
101+
console.log(String(event.target));
102+
// console.log(`\thashCodes ${hashCodes.sort((a, b) => a - b)}`);
103+
console.log(`\thashCodes dups ${findDuplicates(hashCodes)}`);
104+
const s = new Stats(hashCodes);
105+
// console.log(s.describe());
106+
console.log(`range: [${s.min}...${s.max}]`);
107+
})
108+
.on('complete', function() {
109+
console.log('Fastest is ' + this.filter('fastest').map('name'));
110+
printSortedResults(this);
111+
})
112+
.on('error', function(event) {
113+
console.log(event.target.error);
114+
})
115+
// run async
116+
.run({ 'async': true });
117+
118+
function printSortedResults(benchmark) {
119+
console.log('\n======== Results ========');
120+
const results = Object.values(benchmark).filter(b => b && b.name);
121+
const sortedResults = results.sort((a, b) => b.hz - a.hz);
122+
sortedResults.forEach((b) => {
123+
console.log(`${b.hz.toLocaleString()} ops/s with ${b.name}`);
124+
});
125+
}
126+
127+
function runner(fn) {
128+
hashCodes = [];
129+
for (let index = 0; index < keys.length; index++) {
130+
const key = keys[index];
131+
hashCodes.push(fn(key));
132+
}
133+
}
134+
135+
function findDuplicates(array) {
136+
const dups = new Set();
137+
const sortedArray = array.sort((a, b) => a - b);
138+
139+
for (let index = 1; index < sortedArray.length; index++) {
140+
const element = sortedArray[index];
141+
const previous = sortedArray[index - 1];
142+
if (element === previous) {
143+
dups.add(element);
144+
}
145+
}
146+
return [...dups];
147+
}
148+
149+
function hashCode3Shifting(key) {
150+
let hashValue = 0;
151+
const stringTypeKey = `${key}${typeof key}`;
152+
153+
for (let index = 0; index < stringTypeKey.length; index++) {
154+
const charCode = stringTypeKey.charCodeAt(index);
155+
hashValue += charCode << (index * 8);
156+
}
157+
158+
return hashValue;
159+
}
160+
161+
function hashCode41Multiplication(key) {
162+
return Array.from(key.toString()).reduce((hash, char) => {
163+
return char.codePointAt() + (hash * 41);
164+
}, 0);
165+
}
166+
167+
function hashCode31Multiplication(key) {
168+
return Array.from(key.toString()).reduce((hash, char) => {
169+
return char.codePointAt() + (hash * 31);
170+
}, 0);
171+
}
172+
173+
function hashCode31Shifting(key) {
174+
return Array.from(key.toString()).reduce((hash, char) => {
175+
return char.codePointAt() + (hash << 5) - hash;
176+
}, 0);
177+
}
178+
179+
function hashCode31Shifting32bit(key) {
180+
const str = key.toString();
181+
var hash = 0;
182+
if (str.length == 0) return hash;
183+
for (i = 0; i < str.length; i++) {
184+
hash = (hash<<5) - hash;
185+
hash = hash + str.codePointAt(i);
186+
hash = hash & hash; // Convert to 32bit integer
187+
}
188+
// return Math.abs(hash);
189+
return hash;
190+
}
191+
192+
// 524287
193+
function hashCode524287Shifting32bitSafe(key) {
194+
const str = key.toString();
195+
var hash = 0;
196+
if (str.length == 0) return hash;
197+
for (i = 0; i < str.length; i++) {
198+
hash = (hash << 19) - hash;
199+
hash = hash + str.codePointAt(i);
200+
hash = hash & 0x7fffffff; // Convert to 32bit integer
201+
}
202+
// return Math.abs(hash);
203+
return hash;
204+
}
205+
206+
function hashCode524287Shifting32bitSafeMod(key) {
207+
const str = key.toString();
208+
var hash = 0;
209+
if (str.length == 0) return hash;
210+
for (i = 0; i < str.length; i++) {
211+
hash = hash + str.codePointAt(i);
212+
hash = (hash << 19) - hash;
213+
hash = hash & 0x7fffffff; // Convert to 32bit integer
214+
}
215+
// return Math.abs(hash);
216+
return hash % MAX;
217+
}
218+
219+
function hashCode524287Shifting32bitSafeModBefore(key) {
220+
const str = key.toString();
221+
var hash = 0;
222+
if (str.length == 0) return hash;
223+
for (i = 0; i < str.length; i++) {
224+
hash = hash + str.codePointAt(i);
225+
hash = (hash << 19) - hash;
226+
// hash = hash & 0x7fffffff; // Convert to 32bit integer
227+
hash = hash % MAX;
228+
}
229+
// return Math.abs(hash);
230+
return hash;
231+
}
232+
233+
function hashFNV1a(key) {
234+
const str = key.toString();
235+
var hash = 0;
236+
if (str.length == 0) return hash;
237+
for (i = 0; i < str.length; i++) {
238+
hash = hash ^ str.codePointAt(i); // XOR
239+
// issue with overflow 50855934 << 19 = -1048576
240+
hash = (hash << 19) - hash; // 524287 * hash
241+
}
242+
return hash;
243+
}
244+
245+
function hashFNV1a32bitSafe(key) {
246+
const str = key.toString();
247+
var hash = 0;
248+
if (str.length == 0) return hash;
249+
for (i = 0; i < str.length; i++) {
250+
hash = hash ^ str.codePointAt(i); // XOR
251+
// issue with overflow 50855934 << 19 = -1048576
252+
// & 0x7fffffff keep 31bit
253+
hash = (hash << 19) & 0x7fffffff - hash; // 524287 * hash
254+
}
255+
return hash;
256+
}
257+
258+
function hashFNV1a32bitSafeModAfterChop(key) {
259+
const str = key.toString();
260+
var hash = 0;
261+
for (i = 0; i < str.length; i++) {
262+
hash = hash ^ str.codePointAt(i); // XOR
263+
// issue with overflow 50855934 << 19 = -1048576, so `& 0x7fffffff` keep 31bit positive
264+
hash = (hash << 19) & 0x7fffffff - hash; // 524287 * hash
265+
}
266+
return hash % MAX;
267+
}
268+
269+
function hashFNV1a32bitSafeModAfter(key) {
270+
const str = key.toString();
271+
var hash = 0;
272+
for (i = 0; i < str.length; i++) {
273+
hash = hash ^ str.codePointAt(i); // XOR
274+
// issue with overflow 50855934 << 19 = -1048576, so `>>> 0` keep 31bit positive
275+
hash = (hash << 19) - hash; // 524287 * hash
276+
}
277+
return (hash >>> 0) % MAX;
278+
}
279+
280+
function hashFNV1a32bitSafeModBefore(key) {
281+
const str = key.toString();
282+
var hash = 0;
283+
for (i = 0; i < str.length; i++) {
284+
hash ^= str.codePointAt(i); // XOR
285+
hash = (hash << 19) - hash; // 524287 * hash
286+
hash = hash % MAX;
287+
}
288+
return hash;
289+
}
290+
291+
function hashFNV1a32bitSafeModAfterParams(key) {
292+
const str = key.toString();
293+
var hash = 2166136261;
294+
if (str.length == 0) return hash;
295+
for (i = 0; i < str.length; i++) {
296+
hash ^= str.codePointAt(i); // XOR
297+
hash *= 16777619;
298+
}
299+
return (hash >>> 0) % MAX;
300+
}
301+
302+
function HashJavaMultiplication(key) {
303+
const str = key.toString();
304+
let hash = 0;
305+
for (let i = 0; i < str.length; i++) {
306+
hash = (524287 * hash + str.codePointAt(i)) % MAX;
307+
}
308+
return hash;
309+
}
310+
311+
/**
312+
* https://github.com/sindresorhus/fnv1a/blob/master/index.js
313+
* @param {*} key
314+
*/
315+
function fnv1a(key) {
316+
let hash = 2166136261;
317+
const string = key.toString();
318+
319+
for (let i = 0; i < string.length; i++) {
320+
hash ^= string.codePointAt(i);
321+
322+
// 32-bit FNV prime: 2**24 + 2**8 + 0x93 = 16777619
323+
// Using bitshift for accuracy and performance. Numbers in JS suck.
324+
hash += (hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24);
325+
}
326+
327+
return (hash >>> 0) % MAX;
328+
}
329+
330+
function HashJavaShifting(key) {
331+
const str = key.toString();
332+
let hash = 0;
333+
for (let i = 0; i < str.length; i++) {
334+
hash = (hash << 19) - hash; // 524287 * hash
335+
hash += str.codePointAt(i);
336+
hash = hash % MAX;
337+
}
338+
return hash;
339+
}
340+
341+
/**
342+
* JS Implementation of MurmurHash3 (r136) (as of May 20, 2011)
343+
*
344+
* @author <a href="mailto:gary.court@gmail.com">Gary Court</a>
345+
* @see http://github.com/garycourt/murmurhash-js
346+
* @author <a href="mailto:aappleby@gmail.com">Austin Appleby</a>
347+
* @see http://sites.google.com/site/murmurhash/
348+
*
349+
* @param {string} string ASCII only
350+
* @param {number} seed Positive integer only
351+
* @return {number} 32-bit positive integer hash
352+
*/
353+
354+
function murmurhash3_32_gc(key, seed = 17) {
355+
var remainder, bytes, h1, h1b, c1, c1b, c2, c2b, k1, i;
356+
const string = key.toString();
357+
358+
remainder = string.length & 3; // key.length % 4
359+
bytes = string.length - remainder;
360+
h1 = seed;
361+
c1 = 0xcc9e2d51;
362+
c2 = 0x1b873593;
363+
i = 0;
364+
365+
while (i < bytes) {
366+
k1 =
367+
((string.charCodeAt(i) & 0xff)) |
368+
((string.charCodeAt(++i) & 0xff) << 8) |
369+
((string.charCodeAt(++i) & 0xff) << 16) |
370+
((string.charCodeAt(++i) & 0xff) << 24);
371+
++i;
372+
373+
k1 = ((((k1 & 0xffff) * c1) + ((((k1 >>> 16) * c1) & 0xffff) << 16))) & 0xffffffff;
374+
k1 = (k1 << 15) | (k1 >>> 17);
375+
k1 = ((((k1 & 0xffff) * c2) + ((((k1 >>> 16) * c2) & 0xffff) << 16))) & 0xffffffff;
376+
377+
h1 ^= k1;
378+
h1 = (h1 << 13) | (h1 >>> 19);
379+
h1b = ((((h1 & 0xffff) * 5) + ((((h1 >>> 16) * 5) & 0xffff) << 16))) & 0xffffffff;
380+
h1 = (((h1b & 0xffff) + 0x6b64) + ((((h1b >>> 16) + 0xe654) & 0xffff) << 16));
381+
}
382+
383+
k1 = 0;
384+
385+
switch (remainder) {
386+
case 3: k1 ^= (string.charCodeAt(i + 2) & 0xff) << 16;
387+
case 2: k1 ^= (string.charCodeAt(i + 1) & 0xff) << 8;
388+
case 1: k1 ^= (string.charCodeAt(i) & 0xff);
389+
390+
k1 = (((k1 & 0xffff) * c1) + ((((k1 >>> 16) * c1) & 0xffff) << 16)) & 0xffffffff;
391+
k1 = (k1 << 15) | (k1 >>> 17);
392+
k1 = (((k1 & 0xffff) * c2) + ((((k1 >>> 16) * c2) & 0xffff) << 16)) & 0xffffffff;
393+
h1 ^= k1;
394+
}
395+
396+
h1 ^= string.length;
397+
398+
h1 ^= h1 >>> 16;
399+
h1 = (((h1 & 0xffff) * 0x85ebca6b) + ((((h1 >>> 16) * 0x85ebca6b) & 0xffff) << 16)) & 0xffffffff;
400+
h1 ^= h1 >>> 13;
401+
h1 = ((((h1 & 0xffff) * 0xc2b2ae35) + ((((h1 >>> 16) * 0xc2b2ae35) & 0xffff) << 16))) & 0xffffffff;
402+
h1 ^= h1 >>> 16;
403+
404+
return (h1 >>> 0) % MAX;
405+
}
406+
407+
/*
408+
Some results:
409+
410+
hashCode31Shifting32bit x 759,784 ops/sec ±0.54% (89 runs sampled)
411+
hashCode524287 x 761,726 ops/sec ±0.42% (92 runs sampled)
412+
413+
hashCode3 x 391,452 ops/sec ±0.55% (88 runs sampled)
414+
415+
hashCode31 x 28,797 ops/sec ±0.57% (87 runs sampled)
416+
hashCode31Shifting x 28,482 ops/sec ±0.85% (83 runs sampled)
417+
hashCode41 x 23,764 ops/sec ±6.83% (77 runs sampled)
418+
419+
Fastest is hashCode524287,hashCode31Shifting32bit
420+
421+
422+
with hashcodes
423+
424+
hashCode31Shifting32bit x 582,669 ops/sec ±6.72% (78 runs sampled)
425+
426+
hashCodes 0,0,64,1118,2174,2174,3104,3446974,3540994,722070080,1074417128,1500524167,1758434107,2042954132,2147483648
427+
428+
hashCode524287Shifting32bit x 680,640 ops/sec ±0.86% (87 runs sampled)
429+
hashCodes 64,18350078,35127326,35651582,50855936,117440514,120586238,210763577,245891020,385351712,446168968,475004805,503316480,553648128,1284505600
430+
431+
hashCode41Multiplication x 27,893 ops/sec ±0.68% (87 runs sampled)
432+
hashCodes 64,1468,2844,2854,4074,7910614,8125574,3.556517065053352e+24,3.325152872191153e+27,3.000313600725833e+29,7.750355967319522e+35,1.51902022979367e+37,1.2388723180112171e+39,1.2388723180112171e+39,2.325529005454274e+161
433+
434+
hashCode31Multiplication x 28,448 ops/sec ±0.94% (87 runs sampled)
435+
hashCodes 64,1118,2174,2174,3104,3446974,3540994,7.167221549533951e+22,3.8216381216178624e+25,2.608985481173328e+27,2.205256818032087e+33,3.263014092084299e+34,2.015146744279022e+36,2.015146744279022e+36,2.2348285391311887e+149
436+
437+
hashCode31Shifting x 28,531 ops/sec ±0.50% (87 runs sampled)
438+
hashCodes -8589934592,-8589934592,-3572897216,-2147483648,-1074417128,64,1118,2174,2174,3104,3446974,3540994,1500524167,1758434107,2042954132
439+
440+
hashCode3Shifting x 386,779 ops/sec ±0.65% (86 runs sampled)
441+
hashCodes 1927012777,3655487572,3688991381,3688999862,3689007797,3689007827,3706145617,5414626663,7566791752,7865868062,11198827154,11505401766,12046659649,12366339893,22401821098
442+
443+
Fastest is hashCode524287Shifting32bit,hashCode31Shifting32bit
444+
*/

‎benchmarks/hashmap.perf.js

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// nodemon src/data-structures/hash-maps/hashmap.perf.js
1+
// nodemon benchmarks/hashmap.perf.js
22
/* eslint-disable */
33

44

@@ -110,28 +110,37 @@ function useBenchmark() {
110110
// suite.add('Map (built-in)', function() {
111111
// const map = new Map();
112112
// testMapOperations(map);
113-
// })
113+
// }, { onComplete: () => {
114+
// if (map.collisions) {
115+
// console.log('\tcollisions', map.collisions);
116+
// }
117+
// }})
118+
119+
let map;
114120

115121
suite.add('HashMap', function() {
116-
const map = new HashMap();
122+
map = new HashMap();
117123
testMapOperations(map);
118124
})
119125

120126
// HashMap3 x 543 ops/sec ±1.53% (84 runs sampled)
121127
suite.add('HashMap3', function() {
122-
const map = new HashMap3();
128+
map = new HashMap3();
123129
testMapOperations(map);
124130
})
125131

126132
// HashMap4 x 302 ops/sec ±2.09% (75 runs sampled)
127133
suite.add('HashMap4', function() {
128-
const map = new HashMap4();
134+
map = new HashMap4();
129135
testMapOperations(map);
130136
})
131137

132138
// add listeners
133139
.on('cycle', function(event) {
134140
console.log(String(event.target));
141+
if (map.collisions) {
142+
console.log('\tcollisions', map.collisions);
143+
}
135144
})
136145
.on('error', function(event) {
137146
console.log(event.target.error);

‎src/data-structures/hash-maps/hashmap.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@ const LinkedList = require('../linked-lists/linked-list');
22

33
class HashMap {
44
/**
5-
* Initialize array that holds the values. Default is size 16
5+
* Initialize array that holds the values.
66
* @param {number} initialCapacity initial size of the array
77
* @param {number} loadFactor if set, the Map will automatically
88
* rehash when the load factor threshold is met
99
*/
10-
constructor(initialCapacity = 16, loadFactor = 0.75) {
10+
constructor(initialCapacity = 19, loadFactor = 0.75) {
1111
this.buckets = new Array(initialCapacity);
1212
this.loadFactor = loadFactor;
1313
this.size = 0;
@@ -67,6 +67,10 @@ class HashMap {
6767
this.keysTrackerArray[this.keysTrackerIndex] = key;
6868
this.keysTrackerIndex += 1;
6969
this.size += 1;
70+
// count collisions
71+
if (bucket.size > 1) {
72+
this.collisions += 1;
73+
}
7074
}
7175
}
7276

‎src/data-structures/hash-maps/hashmap.spec.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,10 @@ describe('HashMap Tests', () => {
8080
hashMap.set('This Is What You Came For', 'Calvin Harris ');
8181
});
8282

83+
it('should count collisions', () => {
84+
expect(hashMap.collisions).toBe(7);
85+
});
86+
8387
it('gets values', () => {
8488
hashMap.set('test', 'one');
8589
expect(hashMap.get('test')).toBe('one');

0 commit comments

Comments
 (0)
Please sign in to comment.