From fba94b1b5fa098b545ce733a017184b07355a14d Mon Sep 17 00:00:00 2001 From: Jack <1098119+jackmoxley@users.noreply.github.com> Date: Tue, 16 Feb 2021 17:41:07 +0000 Subject: [PATCH 1/8] Update Readme.md --- Readme.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Readme.md b/Readme.md index aed4ee7..dd22972 100644 --- a/Readme.md +++ b/Readme.md @@ -1,3 +1,5 @@ +Will turn this into an actual hashmap, do not use as a hashmap! + # HashMap Class for JavaScript ## Installation From 73b280c42e6986faac1a1942371f1d758ad4c869 Mon Sep 17 00:00:00 2001 From: jackmoxley Date: Wed, 17 Feb 2021 02:20:43 +0000 Subject: [PATCH 2/8] First cut, all tests pass apart from the not actually hash functions. I included the support for NaN which never equals itself, we really shouldn't. --- hashmap.js | 628 +++++++++++++++++++++++++++++++++------------------ test/test.js | 90 ++++---- 2 files changed, 454 insertions(+), 264 deletions(-) diff --git a/hashmap.js b/hashmap.js index 4f2c066..f8442f3 100644 --- a/hashmap.js +++ b/hashmap.js @@ -5,223 +5,413 @@ * Homepage: https://github.com/flesler/hashmap */ -(function(factory) { - if (typeof define === 'function' && define.amd) { - // AMD. Register as an anonymous module. - define([], factory); - } else if (typeof module === 'object') { - // Node js environment - var HashMap = module.exports = factory(); - // Keep it backwards compatible - HashMap.HashMap = HashMap; - } else { - // Browser globals (this is window) - this.HashMap = factory(); - } -}(function() { - - function HashMap(other) { - this.clear(); - switch (arguments.length) { - case 0: break; - case 1: { - if ('length' in other) { - // Flatten 2D array to alternating key-value array - multi(this, Array.prototype.concat.apply([], other)); - } else { // Assumed to be a HashMap instance - this.copy(other); - } - break; - } - default: multi(this, arguments); break; - } - } - - var proto = HashMap.prototype = { - constructor:HashMap, - - get:function(key) { - var data = this._data[this.hash(key)]; - return data && data[1]; - }, - - set:function(key, value) { - // Store original key as well (for iteration) - var hash = this.hash(key); - if ( !(hash in this._data) ) { - this.size++; - } - this._data[hash] = [key, value]; - }, - - multi:function() { - multi(this, arguments); - }, - - copy:function(other) { - for (var hash in other._data) { - if ( !(hash in this._data) ) { - this.size++; - } - this._data[hash] = other._data[hash]; - } - }, - - has:function(key) { - return this.hash(key) in this._data; - }, - - search:function(value) { - for (var key in this._data) { - if (this._data[key][1] === value) { - return this._data[key][0]; - } - } - - return null; - }, - - delete:function(key) { - var hash = this.hash(key); - if ( hash in this._data ) { - this.size--; - delete this._data[hash]; - } - }, - - type:function(key) { - var str = Object.prototype.toString.call(key); - var type = str.slice(8, -1).toLowerCase(); - // Some browsers yield DOMWindow or Window for null and undefined, works fine on Node - if (!key && (type === 'domwindow' || type === 'window')) { - return key + ''; - } - return type; - }, - - keys:function() { - var keys = []; - this.forEach(function(_, key) { keys.push(key); }); - return keys; - }, - - values:function() { - var values = []; - this.forEach(function(value) { values.push(value); }); - return values; - }, - - entries:function() { - var entries = []; - this.forEach(function(value, key) { entries.push([key, value]); }); - return entries; - }, - - // TODO: This is deprecated and will be deleted in a future version - count:function() { - return this.size; - }, - - clear:function() { - // TODO: Would Object.create(null) make any difference - this._data = {}; - this.size = 0; - }, - - clone:function() { - return new HashMap(this); - }, - - hash:function(key) { - switch (this.type(key)) { - case 'undefined': - case 'null': - case 'boolean': - case 'number': - case 'regexp': - return key + ''; - - case 'date': - return '♣' + key.getTime(); - - case 'string': - return '♠' + key; - - case 'array': - var hashes = []; - for (var i = 0; i < key.length; i++) { - hashes[i] = this.hash(key[i]); - } - return '♥' + hashes.join('⁞'); - - default: - // TODO: Don't use expandos when Object.defineProperty is not available? - if (!key.hasOwnProperty('_hmuid_')) { - key._hmuid_ = ++HashMap.uid; - hide(key, '_hmuid_'); - } - - return '♦' + key._hmuid_; - } - }, - - forEach:function(func, ctx) { - for (var key in this._data) { - var data = this._data[key]; - func.call(ctx || this, data[1], data[0]); - } - } - }; - - HashMap.uid = 0; - - // Iterator protocol for ES6 - if (typeof Symbol !== 'undefined' && typeof Symbol.iterator !== 'undefined') { - proto[Symbol.iterator] = function() { - var entries = this.entries(); - var i = 0; - return { - next:function() { - if (i === entries.length) { return { done: true }; } - var currentEntry = entries[i++]; - return { - value: { key: currentEntry[0], value: currentEntry[1] }, - done: false - }; - } - }; - }; - } - - //- Add chaining to all methods that don't return something - - ['set','multi','copy','delete','clear','forEach'].forEach(function(method) { - var fn = proto[method]; - proto[method] = function() { - fn.apply(this, arguments); - return this; - }; - }); - - //- Backwards compatibility - - // TODO: remove() is deprecated and will be deleted in a future version - HashMap.prototype.remove = HashMap.prototype.delete; - - //- Utils - - function multi(map, args) { - for (var i = 0; i < args.length; i += 2) { - map.set(args[i], args[i+1]); - } - } - - function hide(obj, prop) { - // Make non iterable if supported - if (Object.defineProperty) { - Object.defineProperty(obj, prop, {enumerable:false}); - } - } - - return HashMap; +(function (factory) { + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define([], factory); + } else if (typeof module === 'object') { + // Node js environment + var HashMap = module.exports = factory(); + // Keep it backwards compatible + HashMap.HashMap = HashMap; + } else { + // Browser globals (this is window) + this.HashMap = factory(); + } +}(function () { + + function HashMap(other) { + this.clear(); + switch (arguments.length) { + case 0: + break; + case 1: { + if ('length' in other) { + // Flatten 2D array to alternating key-value array + multi(this, Array.prototype.concat.apply([], other)); + } else { // Assumed to be a HashMap instance + this.copy(other); + } + break; + } + default: + multi(this, arguments); + break; + } + } + + function HashBucket(safeKey, key, value) { + this._safeKey = safeKey; + this._key = key; + this._value= value; + this._next= null; + this._size= 1; + } + HashBucket.prototype = { + constructor: HashBucket, + get: function (hash, key) { + var bucket = this; + // avoid recursion + do { + if (bucket._safeKey === key) { + return bucket._value; + } + bucket = bucket._next; + } + while (bucket != null); + return null; + }, + set: function (hash, safeKey, key, value) { + var bucket = this; + // avoid recursion + while (true) { + if (bucket._safeKey === safeKey) { + bucket._value = value; + return false; + } + if(bucket._next) { + bucket = bucket._next; + } else { + bucket._next = new HashBucket(safeKey, key, value); + this._size++; + return true; + } + } + }, + + has: function (hash, key) { + var bucket = this; + // avoid recursion + do { + if (bucket._safeKey === key) { + return true; + } + bucket = bucket._next; + } + while (bucket != null); + return false; + }, + + search: function (value) { + var bucket = this; + // avoid recursion + do { + if (bucket._value === value) { + return bucket._key; + } + bucket = bucket._next; + } + while (bucket != null); + return null; + }, + + delete: function (hash, key) { + var bucket = this; + var prev = null; + // avoid recursion + do { + if (bucket._safeKey === key) { + var next = bucket._next; + if(bucket._next) { + bucket._key = next._key; + bucket._safeKey = next._safeKey; + bucket._value = next._value; + bucket._next = next._next; + } else if(prev) { + delete prev._next; + } + this._size--; + return true; + } + prev = bucket; + bucket = bucket._next; + } + while (bucket != null); + return false; + }, + + forEach: function (func, ctx) { + var bucket = this; + // avoid recursion + do { + func.call(ctx, bucket._value, bucket._key); + bucket = bucket._next; + } + while (bucket != null); + } + }; + + function HashBuckets(depth) { + this._depth = depth || 3; + this._buckets= new Array(16); + this._size= 0; + } + + HashBuckets.prototype = { + constructor: HashBuckets, + get: function (hash, key) { + var bucket = this._buckets[hash % 16]; + if (bucket) { + return bucket.get(hash >> 5, key); + } + return null; + }, + set: function (hash, safeKey, key, value) { + var idx = hash % 16; + var bucket = this._buckets[idx]; + if (bucket) { + return bucket.set(hash >> 5, safeKey, key, value); + } else { + if( this._depth > 0){ + this._buckets[idx] = new HashBucket(safeKey, key, value); + } else { + bucket = new HashBuckets(this._depth-1); + bucket.set(hash >> 5, safeKey, key, value); + this._buckets[idx] = bucket; + } + this._size++; + return true; + } + }, + + has: function (hash, key) { + var bucket = this._buckets[hash % 16]; + if(bucket) { + return bucket.has(hash >> 5, key); + } + return false; + }, + search: function (value) { + for (var idx in this._buckets) { + var data = this._buckets[idx]; + var key = data.search(value); + if(key){ + return key; + } + } + return null; + }, + + delete: function (hash,key) { + var idx= hash % 16; + var bucket = this._buckets[idx]; + if (bucket) { + if(bucket.delete(hash, key)){ + if(bucket._size === 0){ + delete this._buckets[idx]; + this._size--; + } + return true; + } + } + return false; + }, + + forEach: function (func, ctx) { + for (var idx in this._buckets) { + var data = this._buckets[idx]; + data.forEach(func, ctx); + } + } + }; + + var proto = HashMap.prototype = { + constructor: HashMap, + + get: function (key) { + var safeKey = this.safeKey(key); + var hash = hashCode(safeKey); + return this._buckets.get(hash,safeKey); + }, + + set: function (key, value) { + var safeKey = this.safeKey(key); + var hash = hashCode(safeKey); + // Store original key as well (for iteration) + if (this._buckets.set(hash,safeKey, key, value)) { + this.size++; + } + }, + + multi: function () { + multi(this, arguments); + }, + + copy: function (other) { + var map = this; + other.forEach(function(value, key){ + map.set(key,value); + }); + }, + + has: function (key) { + var safeKey = this.safeKey(key); + var hash = hashCode(safeKey); + return this._buckets.has(hash,safeKey); + }, + + search: function (value) { + return this._buckets.search(value); + }, + + delete: function (key) { + var safeKey = this.safeKey(key); + var hash = hashCode(safeKey); + if (this._buckets.delete(hash,safeKey)) { + this.size--; + } + }, + + type: function (key) { + var str = Object.prototype.toString.call(key); + var type = str.slice(8, -1).toLowerCase(); + // Some browsers yield DOMWindow or Window for null and undefined, works fine on Node + if (!key && (type === 'domwindow' || type === 'window')) { + return key + ''; + } + return type; + }, + + keys: function () { + var keys = []; + this.forEach(function (_, key) { + keys.push(key); + }); + return keys; + }, + + values: function () { + var values = []; + this.forEach(function (value) { + values.push(value); + }); + return values; + }, + + entries: function () { + var entries = []; + this.forEach(function (value, key) { + entries.push([key, value]); + }); + return entries; + }, + + // TODO: This is deprecated and will be deleted in a future version + count: function () { + return this.size; + }, + + clear: function () { + // TODO: Would Object.create(null) make any difference + this._buckets = new HashBuckets(); + this.size = 0; + }, + + clone: function () { + return new HashMap(this); + }, + hash: function(key) { + return hashCode(this.safeKey(key)); + }, + safeKey: function (key) { + switch (this.type(key)) { + case 'undefined': + case 'null': + case 'boolean': + case 'number': + case 'regexp': + return key + ''; + + case 'date': + return '♣' + key.getTime(); + + case 'string': + return '♠' + key; + + case 'array': + var hashes = []; + for (var i = 0; i < key.length; i++) { + hashes[i] = this.hash(key[i]); + } + return '♥' + hashes.join('⁞'); + + default: + // TODO: Don't use expandos when Object.defineProperty is not available? + if (!key.hasOwnProperty('_hmuid_')) { + key._hmuid_ = ++HashMap.uid; + hide(key, '_hmuid_'); + } + + return '♦' + key._hmuid_; + } + }, + + forEach: function (func, ctx) { + this._buckets.forEach(func, ctx || this); + } + }; + + HashMap.uid = 0; + + // Iterator protocol for ES6 + if (typeof Symbol !== 'undefined' && typeof Symbol.iterator !== 'undefined') { + proto[Symbol.iterator] = function () { + var entries = this.entries(); + var i = 0; + return { + next: function () { + if (i === entries.length) { + return {done: true}; + } + var currentEntry = entries[i++]; + return { + value: {key: currentEntry[0], value: currentEntry[1]}, + done: false + }; + } + }; + }; + } + + //- Add chaining to all methods that don't return something + + ['set', 'multi', 'copy', 'delete', 'clear', 'forEach'].forEach(function (method) { + var fn = proto[method]; + proto[method] = function () { + fn.apply(this, arguments); + return this; + }; + }); + + //- Backwards compatibility + + // TODO: remove() is deprecated and will be deleted in a future version + HashMap.prototype.remove = HashMap.prototype.delete; + + //- Utils + + function multi(map, args) { + for (var i = 0; i < args.length; i += 2) { + map.set(args[i], args[i + 1]); + } + } + + function hashCode(str) { + var hash = 0, i, chr; + for (i = 0; i < str.length; i++) { + chr = str.charCodeAt(i); + hash = ((hash << 5) - hash) + chr; + hash |= 0; // Convert to 32bit integer + } + return hash; + } + + function hide(obj, prop) { + // Make non iterable if supported + if (Object.defineProperty) { + Object.defineProperty(obj, prop, {enumerable: false}); + } + } + + return HashMap; })); diff --git a/test/test.js b/test/test.js index 27f227d..4c672c3 100644 --- a/test/test.js +++ b/test/test.js @@ -49,48 +49,48 @@ describe('hashmap', function() { }); }); - describe('hashmap.hash()', function() { - function check(data, hash) { - expect(hashmap.hash(data)).to.equal(hash); - } - - it('should hash primitives accurately', function() { - check(null, 'null'); - check(undefined, 'undefined'); - check(true, 'true'); - check(false, 'false'); - check(NaN, 'NaN'); - check(1, '1'); - check(1.1, '1.1'); - check('1.1', '♠1.1'); - check('Test', '♠Test'); - }); - - it('should hash objects with primitive representation accurately', function() { - check(/test/, '/test/'); - check(new Date(Date.parse('Fri, 15 Aug 1986 15:05:00 GMT')), '♣524502300000'); - }); - - it('should hash arrays accurately', function() { - check([], '♥'); - check([1, 2, 3], '♥1⁞2⁞3'); - }); - - it('should hash unrecognized objects accurately', function() { - check({}, '♦1'); - check(HashMap, '♦2'); - check(hashmap, '♦3'); - }); - - it('should not add any iterable property to objects', function() { - var obj = {}; - hashmap.hash(obj); - expect(obj).to.be.empty; - }); - - // TODO: expect two hashmaps to hash the same object to the same value - // TODO: test hash(1) !== hash('1') - }); + // describe('hashmap.hash()', function() { + // function check(data, hash) { + // expect(hashmap.hash(data)).to.equal(hash); + // } + // + // it('should hash primitives accurately', function() { + // check(null, 'null'); + // check(undefined, 'undefined'); + // check(true, 'true'); + // check(false, 'false'); + // check(NaN, 'NaN'); + // check(1, '1'); + // check(1.1, '1.1'); + // check('1.1', '♠1.1'); + // check('Test', '♠Test'); + // }); + // + // it('should hash objects with primitive representation accurately', function() { + // check(/test/, '/test/'); + // check(new Date(Date.parse('Fri, 15 Aug 1986 15:05:00 GMT')), '♣524502300000'); + // }); + // + // it('should hash arrays accurately', function() { + // check([], '♥'); + // check([1, 2, 3], '♥1⁞2⁞3'); + // }); + // + // it('should hash unrecognized objects accurately', function() { + // check({}, '♦1'); + // check(HashMap, '♦2'); + // check(hashmap, '♦3'); + // }); + // + // it('should not add any iterable property to objects', function() { + // var obj = {}; + // hashmap.hash(obj); + // expect(obj).to.be.empty; + // }); + // + // // TODO: expect two hashmaps to hash the same object to the same value + // // TODO: test hash(1) !== hash('1') + // }); describe('method chaining', function() { it('should return the instance on some methods', function() { @@ -274,7 +274,7 @@ describe('hashmap', function() { it('should work for several keys', function() { hashmap.set('key', 'value'); hashmap.set('key2', 'value2'); - expect(hashmap.keys()).to.deep.equal(['key', 'key2']); + expect(hashmap.keys().sort()).to.deep.equal(['key', 'key2']); }); }); @@ -291,7 +291,7 @@ describe('hashmap', function() { it('should work for several values', function() { hashmap.set('key', 'value'); hashmap.set('key2', 'value2'); - expect(hashmap.values()).to.deep.equal(['value', 'value2']); + expect(hashmap.values().sort()).to.deep.equal(['value', 'value2']); }); }); @@ -308,7 +308,7 @@ describe('hashmap', function() { it('should work for several values', function() { hashmap.set('key', 'value'); hashmap.set('key2', 'value2'); - expect(hashmap.entries()).to.deep.equal([['key','value'], ['key2','value2']]); + expect(hashmap.entries().sort()).to.deep.equal([['key','value'], ['key2','value2']]); }); }); From da857267f881d0532fecf54b6dc0b15e80d735b1 Mon Sep 17 00:00:00 2001 From: jackmoxley Date: Wed, 17 Feb 2021 23:45:00 +0000 Subject: [PATCH 3/8] Fixed horrific vulnerabilities in dependancies. Added benchmarking library. --- package-lock.json | 1050 +++++++++++++++++++++++++++++++++++++++++++++ package.json | 5 +- 2 files changed, 1053 insertions(+), 2 deletions(-) create mode 100644 package-lock.json diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..3628f26 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1050 @@ +{ + "name": "hashmap", + "version": "2.4.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@ungap/promise-all-settled": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", + "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", + "dev": true + }, + "ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true + }, + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "anymatch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", + "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "dependencies": { + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + } + } + }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "benchtest": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/benchtest/-/benchtest-2.0.7.tgz", + "integrity": "sha512-7AVlK+EMgprCdhmxQ9VD6VjHk0rEXE5oGwtdwx0yXuzn1a8pSMxd5n/4Q+dvWeZx2HB5XtDDAKdbEEZSIG4mtQ==", + "dev": true, + "requires": { + "performance-now": "^2.1.0" + } + }, + "binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "camelcase": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", + "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", + "dev": true + }, + "chai": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.1.1.tgz", + "integrity": "sha1-ZuISeebzxkFf+CMYeCJ5AOIXGzk=", + "dev": true, + "requires": { + "assertion-error": "^1.0.1", + "check-error": "^1.0.1", + "deep-eql": "^2.0.1", + "get-func-name": "^2.0.0", + "pathval": "^1.0.0", + "type-detect": "^4.0.0" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "dependencies": { + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", + "dev": true + }, + "chokidar": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz", + "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==", + "dev": true, + "requires": { + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "fsevents": "~2.3.1", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.5.0" + }, + "dependencies": { + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + } + } + }, + "ci-info": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.6.0.tgz", + "integrity": "sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A==", + "dev": true + }, + "cli": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cli/-/cli-1.0.1.tgz", + "integrity": "sha1-IoF1NPJL+klQw01TLUjsvGIbjBQ=", + "dev": true, + "requires": { + "exit": "0.1.2", + "glob": "^7.1.1" + } + }, + "cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + } + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "console-browserify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz", + "integrity": "sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA=", + "dev": true, + "requires": { + "date-now": "^0.1.4" + } + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "date-now": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz", + "integrity": "sha1-6vQ5/U1ISK105cx9vvIAZyueNFs=", + "dev": true + }, + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + }, + "dependencies": { + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true + }, + "deep-eql": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-2.0.2.tgz", + "integrity": "sha1-sbrAblbwp2d3aG1Qyf63XC7XZ5o=", + "dev": true, + "requires": { + "type-detect": "^3.0.0" + }, + "dependencies": { + "type-detect": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-3.0.0.tgz", + "integrity": "sha1-RtDMhVOrt7E6NSsNbeov1Y8tm1U=", + "dev": true + } + } + }, + "diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "dev": true + }, + "dom-serializer": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz", + "integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==", + "dev": true, + "requires": { + "domelementtype": "^2.0.1", + "entities": "^2.0.0" + }, + "dependencies": { + "domelementtype": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.1.0.tgz", + "integrity": "sha512-LsTgx/L5VpD+Q8lmsXSHW2WpA+eBlZ9HPf3erD1IoPF00/3JKHZ3BknUVA2QGDNu69ZNmyFmCWBSO45XjYKC5w==", + "dev": true + }, + "entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "dev": true + } + } + }, + "domelementtype": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", + "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==", + "dev": true + }, + "domhandler": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.3.0.tgz", + "integrity": "sha1-LeWaCCLVAn+r/28DLCsloqir5zg=", + "dev": true, + "requires": { + "domelementtype": "1" + } + }, + "domutils": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", + "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", + "dev": true, + "requires": { + "dom-serializer": "0", + "domelementtype": "1" + } + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "entities": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.0.0.tgz", + "integrity": "sha1-sph6o4ITR/zeZCsk/fyeT7cSvyY=", + "dev": true + }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", + "dev": true + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, + "get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", + "dev": true + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", + "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true + }, + "htmlparser2": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.8.3.tgz", + "integrity": "sha1-mWwosZFRaovoZQGn15dX5ccMEGg=", + "dev": true, + "requires": { + "domelementtype": "1", + "domhandler": "2.3", + "domutils": "1.5", + "entities": "1.0", + "readable-stream": "1.1" + } + }, + "husky": { + "version": "0.14.3", + "resolved": "https://registry.npmjs.org/husky/-/husky-0.14.3.tgz", + "integrity": "sha512-e21wivqHpstpoiWA/Yi8eFti8E+sQDSS53cpJsPptPs295QTOQR0ZwnHo2TXy1XOpZFD9rPOd3NpmqTK6uMLJA==", + "dev": true, + "requires": { + "is-ci": "^1.0.10", + "normalize-path": "^1.0.0", + "strip-indent": "^2.0.0" + } + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-ci": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.2.1.tgz", + "integrity": "sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg==", + "dev": true, + "requires": { + "ci-info": "^1.5.0" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "js-yaml": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.0.0.tgz", + "integrity": "sha512-pqon0s+4ScYUvX30wxQi3PogGFAlUyH0awepWvwkj4jD4v+ova3RiYw8bmA6x2rDrEaj8i/oWKoRxpVNW+Re8Q==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "jshint": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/jshint/-/jshint-2.12.0.tgz", + "integrity": "sha512-TwuuaUDmra0JMkuqvqy+WGo2xGHSNjv1BA1nTIgtH2K5z1jHuAEeAgp7laaR+hLRmajRjcrM71+vByBDanCyYA==", + "dev": true, + "requires": { + "cli": "~1.0.0", + "console-browserify": "1.1.x", + "exit": "0.1.x", + "htmlparser2": "3.8.x", + "lodash": "~4.17.19", + "minimatch": "~3.0.2", + "shelljs": "0.3.x", + "strip-json-comments": "1.0.x" + } + }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "requires": { + "p-locate": "^5.0.0" + } + }, + "lodash": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", + "dev": true + }, + "log-symbols": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz", + "integrity": "sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==", + "dev": true, + "requires": { + "chalk": "^4.0.0" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "mocha": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-8.3.0.tgz", + "integrity": "sha512-TQqyC89V1J/Vxx0DhJIXlq9gbbL9XFNdeLQ1+JsnZsVaSOV1z3tWfw0qZmQJGQRIfkvZcs7snQnZnOCKoldq1Q==", + "dev": true, + "requires": { + "@ungap/promise-all-settled": "1.1.2", + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.5.1", + "debug": "4.3.1", + "diff": "5.0.0", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "7.1.6", + "growl": "1.10.5", + "he": "1.2.0", + "js-yaml": "4.0.0", + "log-symbols": "4.0.0", + "minimatch": "3.0.4", + "ms": "2.1.3", + "nanoid": "3.1.20", + "serialize-javascript": "5.0.1", + "strip-json-comments": "3.1.1", + "supports-color": "8.1.1", + "which": "2.0.2", + "wide-align": "1.1.3", + "workerpool": "6.1.0", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", + "yargs-unparser": "2.0.0" + }, + "dependencies": { + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + } + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "nanoid": { + "version": "3.1.20", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.20.tgz", + "integrity": "sha512-a1cQNyczgKbLX9jwbS/+d7W8fX/RfgYR7lVWwWOGIPNgK2m0MWvrGF6/m4kk6U3QcFMnZf3RIhL0v2Jgh/0Uxw==", + "dev": true + }, + "normalize-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-1.0.0.tgz", + "integrity": "sha1-MtDkcvkf80VwHBWoMRAY07CpA3k=", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "requires": { + "p-limit": "^3.0.2" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", + "dev": true + }, + "picomatch": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", + "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", + "dev": true + }, + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "readdirp": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", + "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", + "dev": true, + "requires": { + "picomatch": "^2.2.1" + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + }, + "serialize-javascript": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz", + "integrity": "sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==", + "dev": true, + "requires": { + "randombytes": "^2.1.0" + } + }, + "shelljs": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.3.0.tgz", + "integrity": "sha1-NZbmMHp4FUT1kfN9phg2DzHbV7E=", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + }, + "strip-indent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-2.0.0.tgz", + "integrity": "sha1-XvjbKV0B5u1sv3qrlpmNeCJSe2g=", + "dev": true + }, + "strip-json-comments": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-1.0.4.tgz", + "integrity": "sha1-HhX7ysl9Pumb8tc7TGVrCCu6+5E=", + "dev": true + }, + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "dev": true, + "requires": { + "string-width": "^1.0.2 || 2" + } + }, + "workerpool": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.1.0.tgz", + "integrity": "sha512-toV7q9rWNYha963Pl/qyeZ6wG+3nnsyvolaNUS8+R5Wtw6qJPTxIlOP1ZSvcGhEJw+l3HMMmtiNo9Gl61G4GVg==", + "dev": true + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "y18n": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.5.tgz", + "integrity": "sha512-hsRUr4FFrvhhRH12wOdfs38Gy7k2FFzB9qgN9v3aLykRq0dRcdcpz5C9FxdS2NuhOrI/628b/KSTJ3rwHysYSg==", + "dev": true + }, + "yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + } + } + }, + "yargs-parser": { + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "dev": true + }, + "yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "requires": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + } + }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true + } + } +} diff --git a/package.json b/package.json index 75a73ab..6533154 100644 --- a/package.json +++ b/package.json @@ -33,9 +33,10 @@ }, "dependencies": {}, "devDependencies": { + "benchtest": "^2.0.7", "chai": "4.1.1", "husky": "0.14.3", - "jshint": "2.9.5", - "mocha": "3.5.0" + "jshint": "^2.12.0", + "mocha": "^8.3.0" } } From 9043abc4a4d1786c191002b45c95622913a719ac Mon Sep 17 00:00:00 2001 From: jackmoxley Date: Thu, 18 Feb 2021 02:23:40 +0000 Subject: [PATCH 4/8] Added first cut of benchmark suite --- hashmap.js | 25 +++++++++------ package-lock.json | 45 +++++++++++++++++--------- package.json | 7 +++-- test/benchmark.js | 80 +++++++++++++++++++++++++++++++++++++++++++++++ test/mocha.opts | 3 +- test/test.js | 3 ++ 6 files changed, 136 insertions(+), 27 deletions(-) create mode 100644 test/benchmark.js diff --git a/hashmap.js b/hashmap.js index f8442f3..2b69aa5 100644 --- a/hashmap.js +++ b/hashmap.js @@ -20,6 +20,11 @@ } }(function () { + var _widthB = 8; + var _width = 2^_widthB; + var _mask = _width-1; + var _depth = Math.floor(32/_widthB); + function HashMap(other) { this.clear(); switch (arguments.length) { @@ -142,31 +147,31 @@ }; function HashBuckets(depth) { - this._depth = depth || 3; - this._buckets= new Array(16); + this._depth = depth || _depth; + this._buckets= new Array(_width); this._size= 0; } HashBuckets.prototype = { constructor: HashBuckets, get: function (hash, key) { - var bucket = this._buckets[hash % 16]; + var bucket = this._buckets[hash & _mask]; if (bucket) { - return bucket.get(hash >> 5, key); + return bucket.get(hash >> _widthB, key); } return null; }, set: function (hash, safeKey, key, value) { - var idx = hash % 16; + var idx = hash & _mask; var bucket = this._buckets[idx]; if (bucket) { - return bucket.set(hash >> 5, safeKey, key, value); + return bucket.set(hash >> _widthB, safeKey, key, value); } else { if( this._depth > 0){ this._buckets[idx] = new HashBucket(safeKey, key, value); } else { bucket = new HashBuckets(this._depth-1); - bucket.set(hash >> 5, safeKey, key, value); + bucket.set(hash >> _widthB, safeKey, key, value); this._buckets[idx] = bucket; } this._size++; @@ -175,9 +180,9 @@ }, has: function (hash, key) { - var bucket = this._buckets[hash % 16]; + var bucket = this._buckets[hash & _mask]; if(bucket) { - return bucket.has(hash >> 5, key); + return bucket.has(hash >> _widthB, key); } return false; }, @@ -193,7 +198,7 @@ }, delete: function (hash,key) { - var idx= hash % 16; + var idx= hash & _mask; var bucket = this._buckets[idx]; if (bucket) { if(bucket.delete(hash, key)){ diff --git a/package-lock.json b/package-lock.json index 3628f26..5660402 100644 --- a/package-lock.json +++ b/package-lock.json @@ -67,13 +67,13 @@ "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", "dev": true }, - "benchtest": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/benchtest/-/benchtest-2.0.7.tgz", - "integrity": "sha512-7AVlK+EMgprCdhmxQ9VD6VjHk0rEXE5oGwtdwx0yXuzn1a8pSMxd5n/4Q+dvWeZx2HB5XtDDAKdbEEZSIG4mtQ==", - "dev": true, + "benchmark": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/benchmark/-/benchmark-2.1.4.tgz", + "integrity": "sha1-CfPeMckWQl1JjMLuVloOvzwqVik=", "requires": { - "performance-now": "^2.1.0" + "lodash": "^4.17.4", + "platform": "^1.3.3" } }, "binary-extensions": { @@ -637,8 +637,7 @@ "lodash": { "version": "4.17.20", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", - "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", - "dev": true + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==" }, "log-symbols": { "version": "4.0.0", @@ -649,6 +648,15 @@ "chalk": "^4.0.0" } }, + "microtime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/microtime/-/microtime-3.0.0.tgz", + "integrity": "sha512-SirJr7ZL4ow2iWcb54bekS4aWyBQNVcEDBiwAz9D/sTgY59A+uE8UJU15cp5wyZmPBwg/3zf8lyCJ5NUe1nVlQ==", + "requires": { + "node-addon-api": "^1.2.0", + "node-gyp-build": "^3.8.0" + } + }, "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", @@ -711,6 +719,16 @@ "integrity": "sha512-a1cQNyczgKbLX9jwbS/+d7W8fX/RfgYR7lVWwWOGIPNgK2m0MWvrGF6/m4kk6U3QcFMnZf3RIhL0v2Jgh/0Uxw==", "dev": true }, + "node-addon-api": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-1.7.2.tgz", + "integrity": "sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg==" + }, + "node-gyp-build": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-3.9.0.tgz", + "integrity": "sha512-zLcTg6P4AbcHPq465ZMFNXx7XpKKJh+7kkN699NiQWisR2uWYOWNWqRHAmbnmKiL4e9aLSlmy5U7rEMUXV59+A==" + }, "normalize-path": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-1.0.0.tgz", @@ -762,18 +780,17 @@ "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", "dev": true }, - "performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", - "dev": true - }, "picomatch": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", "dev": true }, + "platform": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/platform/-/platform-1.3.6.tgz", + "integrity": "sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg==" + }, "randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", diff --git a/package.json b/package.json index 6533154..34fdbcc 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "main": "./hashmap.js", "scripts": { "test": "mocha test/", + "benchmark": "node test/benchmark.js", "lint": "jshint hashmap.js test/test.js", "precommit": "npm run lint", "prepush": "npm run test -- --reporter dot" @@ -31,9 +32,11 @@ "engines": { "node": "*" }, - "dependencies": {}, + "dependencies": { + "benchmark": "^2.1.4", + "microtime": "^3.0.0" + }, "devDependencies": { - "benchtest": "^2.0.7", "chai": "4.1.1", "husky": "0.14.3", "jshint": "^2.12.0", diff --git a/test/benchmark.js b/test/benchmark.js new file mode 100644 index 0000000..3f306c2 --- /dev/null +++ b/test/benchmark.js @@ -0,0 +1,80 @@ +/*jshint -W030,-W121 */ +var HashMap = require('../hashmap').HashMap; +var Benchmark = require('benchmark'); +var hashmap = new HashMap(); +var suite = new Benchmark.Suite("hashmap"); +console.log("setup constants"); +var key = makeid(16); +var value = makeid(128); + +console.log("1'024 hashmap"); +var hashmap1024 = new HashMap(); + +for (var i = 0; i < 1024; i++) { + hashmap1024.set(makeid(16), makeid(128)); +} +// console.log("262'144 hashmap"); +// var hashmap262144 = new HashMap(); +// for (var i = 0; i < 262144; i++) { +// hashmap262144.set(makeid(16), makeid(128)); +// } +console.log("define benchmarks"); +suite.add("create", function () { + hashmap = new HashMap(); +}) +.add("singleSet", function () { + hashmap.set(key, value); +},{'onCycle': function () { + key = makeid(16); + value = makeid(128); + hashmap.clear(); +}}) +.add("singleReplace", function () { + hashmap.set(key, value); +},{'onCycle': function () { + key = makeid(16); + value = makeid(128); + hashmap.clear(); + hashmap.set(key, makeid(128)); +}}) +.add("setAfter 1,024", function () { + hashmap1024.set(key, value); +},{'onCycle': function () { + hashmap1024.delete(key); + key = makeid(16); + value = makeid(128); +}}) +// .add("setAfter 262,144", function () { +// hashmap262144.set(key, value); +// },{'onCycle': function () { +// hashmap262144.delete(key); +// key = makeid(16); +// value = makeid(128); +// }}) +.on('cycle', function(event) { + console.log(String(event.target)); +}).on('complete', function () { + console.log('Fastest is ' + this.filter('fastest').map('name')); + console.log('Slowest is ' + this.filter('slowest').map('name')); +}).on('onError', function(err) { + console.log("Error",err); +}).run(); + + +function makeid(length) { + var result = ''; + var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + var charactersLength = characters.length; + for (var i = 0; i < length; i++) { + result += characters.charAt(Math.floor(Math.random() * charactersLength)); + } + return result; +} + +/** + * create x 14,494,621 ops/sec ±3.17% (89 runs sampled) + singleSet x 3,198,502 ops/sec ±1.52% (85 runs sampled) + singleReplace x 3,084,387 ops/sec ±1.51% (83 runs sampled) + setAfter 1,024 x 137,042 ops/sec ±2.51% (59 runs sampled) + + */ \ No newline at end of file diff --git a/test/mocha.opts b/test/mocha.opts index 63abe93..1f8d9ac 100644 --- a/test/mocha.opts +++ b/test/mocha.opts @@ -2,4 +2,5 @@ --bail --sort --recursive ---inline-diffs \ No newline at end of file +--inline-diffs +--timeout 0 \ No newline at end of file diff --git a/test/test.js b/test/test.js index 4c672c3..4b22d4b 100644 --- a/test/test.js +++ b/test/test.js @@ -11,6 +11,7 @@ describe('hashmap', function() { describe('hashmap.type()', function() { function check(data, type) { expect(hashmap.type(data)).to.equal(type); + } it('should detect types accurately', function() { check(null, 'null'); @@ -469,6 +470,8 @@ describe('hashmap', function() { }); describe('constructor', function() { + this.timeout(0); + it('should create an empty hashmap when no arguments', function() { expect(hashmap.count()).to.equal(0); }); From 438e424a5dab86750435d563afde452cb4d52309 Mon Sep 17 00:00:00 2001 From: Jack <1098119+jackmoxley@users.noreply.github.com> Date: Thu, 18 Feb 2021 12:05:38 +0000 Subject: [PATCH 5/8] Update hashmap.js using << instead of ^ because its not an xor its a pow --- hashmap.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hashmap.js b/hashmap.js index 2b69aa5..df55535 100644 --- a/hashmap.js +++ b/hashmap.js @@ -21,9 +21,9 @@ }(function () { var _widthB = 8; - var _width = 2^_widthB; + var _width = 2 << _widthB; // 2 ^ widthB var _mask = _width-1; - var _depth = Math.floor(32/_widthB); + var _depth = _widthB >> 5; //divide by 32 function HashMap(other) { this.clear(); From aa4720af114f8b800a651513314411dd4af5ad5d Mon Sep 17 00:00:00 2001 From: Jack <1098119+jackmoxley@users.noreply.github.com> Date: Thu, 18 Feb 2021 12:11:37 +0000 Subject: [PATCH 6/8] Update hashmap.js fixing bitshifts, need to test this later --- hashmap.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/hashmap.js b/hashmap.js index df55535..c257e54 100644 --- a/hashmap.js +++ b/hashmap.js @@ -21,9 +21,9 @@ }(function () { var _widthB = 8; - var _width = 2 << _widthB; // 2 ^ widthB + var _width = 1 << _widthB; // 2 ^ widthB var _mask = _width-1; - var _depth = _widthB >> 5; //divide by 32 + var _depth = _widthB >>> 5; //divide by 32 function HashMap(other) { this.clear(); @@ -157,7 +157,7 @@ get: function (hash, key) { var bucket = this._buckets[hash & _mask]; if (bucket) { - return bucket.get(hash >> _widthB, key); + return bucket.get(hash >>> _widthB, key); } return null; }, @@ -165,13 +165,13 @@ var idx = hash & _mask; var bucket = this._buckets[idx]; if (bucket) { - return bucket.set(hash >> _widthB, safeKey, key, value); + return bucket.set(hash >>> _widthB, safeKey, key, value); } else { if( this._depth > 0){ this._buckets[idx] = new HashBucket(safeKey, key, value); } else { bucket = new HashBuckets(this._depth-1); - bucket.set(hash >> _widthB, safeKey, key, value); + bucket.set(hash >>> _widthB, safeKey, key, value); this._buckets[idx] = bucket; } this._size++; @@ -182,7 +182,7 @@ has: function (hash, key) { var bucket = this._buckets[hash & _mask]; if(bucket) { - return bucket.has(hash >> _widthB, key); + return bucket.has(hash >>> _widthB, key); } return false; }, From 1edc21b451ca1f00eaac66ceac098386258d53e9 Mon Sep 17 00:00:00 2001 From: jackmoxley Date: Fri, 19 Feb 2021 01:56:12 +0000 Subject: [PATCH 7/8] Added first cut of benchmark suite --- HashMap2.4.0/hashmap.js | 227 ++++++++++++++++++++++++++++++++++++++++ hashmap.js | 131 +++++++++++++++-------- package.json | 3 +- test/benchmark.js | 119 ++++++++++++--------- 4 files changed, 382 insertions(+), 98 deletions(-) create mode 100644 HashMap2.4.0/hashmap.js diff --git a/HashMap2.4.0/hashmap.js b/HashMap2.4.0/hashmap.js new file mode 100644 index 0000000..e253f71 --- /dev/null +++ b/HashMap2.4.0/hashmap.js @@ -0,0 +1,227 @@ +/** + * HashMap - HashMap Class for JavaScript + * @author Ariel Flesler + * @version 2.4.0 + * Homepage: https://github.com/flesler/hashmap + */ + +(function(factory) { + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define([], factory); + } else if (typeof module === 'object') { + // Node js environment + var HashMap = module.exports = factory(); + // Keep it backwards compatible + HashMap.HashMap = HashMap; + } else { + // Browser globals (this is window) + this.HashMap = factory(); + } +}(function() { + + function HashMap(other) { + this.clear(); + switch (arguments.length) { + case 0: break; + case 1: { + if ('length' in other) { + // Flatten 2D array to alternating key-value array + multi(this, Array.prototype.concat.apply([], other)); + } else { // Assumed to be a HashMap instance + this.copy(other); + } + break; + } + default: multi(this, arguments); break; + } + } + + var proto = HashMap.prototype = { + constructor:HashMap, + + get:function(key) { + var data = this._data[this.hash(key)]; + return data && data[1]; + }, + + set:function(key, value) { + // Store original key as well (for iteration) + var hash = this.hash(key); + if ( !(hash in this._data) ) { + this.size++; + } + this._data[hash] = [key, value]; + }, + + multi:function() { + multi(this, arguments); + }, + + copy:function(other) { + for (var hash in other._data) { + if ( !(hash in this._data) ) { + this.size++; + } + this._data[hash] = other._data[hash]; + } + }, + + has:function(key) { + return this.hash(key) in this._data; + }, + + search:function(value) { + for (var key in this._data) { + if (this._data[key][1] === value) { + return this._data[key][0]; + } + } + + return null; + }, + + delete:function(key) { + var hash = this.hash(key); + if ( hash in this._data ) { + this.size--; + delete this._data[hash]; + } + }, + + type:function(key) { + var str = Object.prototype.toString.call(key); + var type = str.slice(8, -1).toLowerCase(); + // Some browsers yield DOMWindow or Window for null and undefined, works fine on Node + if (!key && (type === 'domwindow' || type === 'window')) { + return key + ''; + } + return type; + }, + + keys:function() { + var keys = []; + this.forEach(function(_, key) { keys.push(key); }); + return keys; + }, + + values:function() { + var values = []; + this.forEach(function(value) { values.push(value); }); + return values; + }, + + entries:function() { + var entries = []; + this.forEach(function(value, key) { entries.push([key, value]); }); + return entries; + }, + + // TODO: This is deprecated and will be deleted in a future version + count:function() { + return this.size; + }, + + clear:function() { + // TODO: Would Object.create(null) make any difference + this._data = {}; + this.size = 0; + }, + + clone:function() { + return new HashMap(this); + }, + + hash:function(key) { + switch (this.type(key)) { + case 'undefined': + case 'null': + case 'boolean': + case 'number': + case 'regexp': + return key + ''; + + case 'date': + return '♣' + key.getTime(); + + case 'string': + return '♠' + key; + + case 'array': + var hashes = []; + for (var i = 0; i < key.length; i++) { + hashes[i] = this.hash(key[i]); + } + return '♥' + hashes.join('⁞'); + + default: + // TODO: Don't use expandos when Object.defineProperty is not available? + if (!key.hasOwnProperty('_hmuid_')) { + key._hmuid_ = ++HashMap.uid; + hide(key, '_hmuid_'); + } + + return '♦' + key._hmuid_; + } + }, + + forEach:function(func, ctx) { + for (var key in this._data) { + var data = this._data[key]; + func.call(ctx || this, data[1], data[0]); + } + } + }; + + HashMap.uid = 0; + + // Iterator protocol for ES6 + if (typeof Symbol !== 'undefined' && typeof Symbol.iterator !== 'undefined') { + proto[Symbol.iterator] = function() { + var entries = this.entries(); + var i = 0; + return { + next:function() { + if (i === entries.length) { return { done: true }; } + var currentEntry = entries[i++]; + return { + value: { key: currentEntry[0], value: currentEntry[1] }, + done: false + }; + } + }; + }; + } + + //- Add chaining to all methods that don't return something + + ['set','multi','copy','delete','clear','forEach'].forEach(function(method) { + var fn = proto[method]; + proto[method] = function() { + fn.apply(this, arguments); + return this; + }; + }); + + //- Backwards compatibility + + // TODO: remove() is deprecated and will be deleted in a future version + HashMap.prototype.remove = HashMap.prototype.delete; + + //- Utils + + function multi(map, args) { + for (var i = 0; i < args.length; i += 2) { + map.set(args[i], args[i+1]); + } + } + + function hide(obj, prop) { + // Make non iterable if supported + if (Object.defineProperty) { + Object.defineProperty(obj, prop, {enumerable:false}); + } + } + + return HashMap; +})); \ No newline at end of file diff --git a/hashmap.js b/hashmap.js index c257e54..8f949c4 100644 --- a/hashmap.js +++ b/hashmap.js @@ -20,10 +20,6 @@ } }(function () { - var _widthB = 8; - var _width = 1 << _widthB; // 2 ^ widthB - var _mask = _width-1; - var _depth = _widthB >>> 5; //divide by 32 function HashMap(other) { this.clear(); @@ -48,10 +44,11 @@ function HashBucket(safeKey, key, value) { this._safeKey = safeKey; this._key = key; - this._value= value; - this._next= null; - this._size= 1; + this._value = value; + this._next = null; + this._size = 1; } + HashBucket.prototype = { constructor: HashBucket, get: function (hash, key) { @@ -71,10 +68,10 @@ // avoid recursion while (true) { if (bucket._safeKey === safeKey) { - bucket._value = value; - return false; + bucket._value = value; + return false; } - if(bucket._next) { + if (bucket._next) { bucket = bucket._next; } else { bucket._next = new HashBucket(safeKey, key, value); @@ -117,12 +114,12 @@ do { if (bucket._safeKey === key) { var next = bucket._next; - if(bucket._next) { + if (bucket._next) { bucket._key = next._key; bucket._safeKey = next._safeKey; bucket._value = next._value; bucket._next = next._next; - } else if(prev) { + } else if (prev) { delete prev._next; } this._size--; @@ -139,40 +136,52 @@ var bucket = this; // avoid recursion do { - func.call(ctx, bucket._value, bucket._key); + func.call(ctx, bucket._value, bucket._key); bucket = bucket._next; } while (bucket != null); } }; - function HashBuckets(depth) { - this._depth = depth || _depth; - this._buckets= new Array(_width); - this._size= 0; + function HashBuckets(options, depth) { + this._options = options; + this._buckets = new Array(this._options.width); + this._size = 0; + switch (arguments.length) { + case 0: + this._depth = 0; + break; + case 1: + this._depth = options.depth; + break; + case 2: + default: + this._depth = depth; + break; + } } HashBuckets.prototype = { constructor: HashBuckets, get: function (hash, key) { - var bucket = this._buckets[hash & _mask]; + var bucket = this._buckets[hash & this._options.mask]; if (bucket) { - return bucket.get(hash >>> _widthB, key); + return bucket.get(hash >>> this._options.widthB, key); } return null; }, set: function (hash, safeKey, key, value) { - var idx = hash & _mask; + var idx = hash & this._options.mask; var bucket = this._buckets[idx]; if (bucket) { - return bucket.set(hash >>> _widthB, safeKey, key, value); + return bucket.set(hash >>> this._options.widthB, safeKey, key, value); } else { - if( this._depth > 0){ - this._buckets[idx] = new HashBucket(safeKey, key, value); - } else { - bucket = new HashBuckets(this._depth-1); - bucket.set(hash >>> _widthB, safeKey, key, value); + if (this._depth > 0) { + bucket = new HashBuckets(this._options, this._depth - 1); + bucket.set(hash >>> this._options.widthB, safeKey, key, value); this._buckets[idx] = bucket; + } else { + this._buckets[idx] = new HashBucket(safeKey, key, value); } this._size++; return true; @@ -180,9 +189,9 @@ }, has: function (hash, key) { - var bucket = this._buckets[hash & _mask]; - if(bucket) { - return bucket.has(hash >>> _widthB, key); + var bucket = this._buckets[hash & this._options.mask]; + if (bucket) { + return bucket.has(hash >>> this._options.widthB, key); } return false; }, @@ -190,19 +199,19 @@ for (var idx in this._buckets) { var data = this._buckets[idx]; var key = data.search(value); - if(key){ + if (key) { return key; } } return null; }, - delete: function (hash,key) { - var idx= hash & _mask; + delete: function (hash, key) { + var idx = hash & this._options.mask; var bucket = this._buckets[idx]; if (bucket) { - if(bucket.delete(hash, key)){ - if(bucket._size === 0){ + if (bucket.delete(hash >>> this._options.widthB, key)) { + if (bucket._size === 0) { delete this._buckets[idx]; this._size--; } @@ -220,20 +229,21 @@ } }; + var proto = HashMap.prototype = { constructor: HashMap, get: function (key) { var safeKey = this.safeKey(key); var hash = hashCode(safeKey); - return this._buckets.get(hash,safeKey); + return this._buckets.get(hash, safeKey); }, set: function (key, value) { var safeKey = this.safeKey(key); var hash = hashCode(safeKey); // Store original key as well (for iteration) - if (this._buckets.set(hash,safeKey, key, value)) { + if (this._buckets.set(hash, safeKey, key, value)) { this.size++; } }, @@ -244,15 +254,15 @@ copy: function (other) { var map = this; - other.forEach(function(value, key){ - map.set(key,value); + other.forEach(function (value, key) { + map.set(key, value); }); }, has: function (key) { var safeKey = this.safeKey(key); var hash = hashCode(safeKey); - return this._buckets.has(hash,safeKey); + return this._buckets.has(hash, safeKey); }, search: function (value) { @@ -262,7 +272,7 @@ delete: function (key) { var safeKey = this.safeKey(key); var hash = hashCode(safeKey); - if (this._buckets.delete(hash,safeKey)) { + if (this._buckets.delete(hash, safeKey)) { this.size--; } }, @@ -305,17 +315,48 @@ count: function () { return this.size; }, + // how many layers of hashmap do we want, and to the power of 2 how many buckets do we want. + setOptions: function (_depth, _widthB) { + + var widthB; + var depth; + switch (arguments.length) { + case 0: + widthB = 4; // 16 buckets + depth = 4; + break; + case 1: + widthB = 4; + depth = _depth; + break; + default: + widthB = _widthB; + depth = _depth; + break; + } + var width = 1 << widthB; // 2 ^ widthB + var mask = width - 1; + this.options = {widthB, width, mask, depth}; + }, clear: function () { - // TODO: Would Object.create(null) make any difference - this._buckets = new HashBuckets(); + if (!this.options) { + this.setOptions(); + } + // we clone the options as its dangerous to modify mid execution. + this._buckets = new HashBuckets({ + widthB: this.options.widthB, + width: this.options.width, + mask: this.options.mask, + depth: this.options.depth + }); this.size = 0; }, clone: function () { return new HashMap(this); }, - hash: function(key) { + hash: function (key) { return hashCode(this.safeKey(key)); }, safeKey: function (key) { @@ -404,8 +445,8 @@ function hashCode(str) { var hash = 0, i, chr; for (i = 0; i < str.length; i++) { - chr = str.charCodeAt(i); - hash = ((hash << 5) - hash) + chr; + chr = str.charCodeAt(i); + hash = ((hash << 5) - hash) + chr; hash |= 0; // Convert to 32bit integer } return hash; diff --git a/package.json b/package.json index 34fdbcc..25c5de2 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,8 @@ "repository": "git://github.com/flesler/hashmap", "main": "./hashmap.js", "scripts": { - "test": "mocha test/", + "test": "mocha test/test.js", + "alltest": "mocha test/", "benchmark": "node test/benchmark.js", "lint": "jshint hashmap.js test/test.js", "precommit": "npm run lint", diff --git a/test/benchmark.js b/test/benchmark.js index 3f306c2..d773e4b 100644 --- a/test/benchmark.js +++ b/test/benchmark.js @@ -1,64 +1,79 @@ /*jshint -W030,-W121 */ -var HashMap = require('../hashmap').HashMap; +var HashMapNew = require('../hashmap'); +var HashMapOld = require('../HashMap2.4.0/hashmap'); var Benchmark = require('benchmark'); -var hashmap = new HashMap(); -var suite = new Benchmark.Suite("hashmap"); -console.log("setup constants"); -var key = makeid(16); -var value = makeid(128); -console.log("1'024 hashmap"); -var hashmap1024 = new HashMap(); +for(var loop = 0; loop < 2 ; loop++) { + var HashMap; + var suite; + if(loop === 1){ + console.log("New"); + HashMap = HashMapNew.HashMap; + suite = new Benchmark.Suite("hashmap new"); + } else { + console.log("Old"); + HashMap = HashMapOld.HashMap; + suite = new Benchmark.Suite("hashmap old"); + } + var hashmap = new HashMap(); + console.log("setup constants"); + var key = makeid(16); + var value = makeid(128); -for (var i = 0; i < 1024; i++) { - hashmap1024.set(makeid(16), makeid(128)); -} + console.log("1'024 hashmap"); + var hashmap1024 = new HashMap(); + + for (var i = 0; i < 1024; i++) { + hashmap1024.set(makeid(16), makeid(128)); + } // console.log("262'144 hashmap"); // var hashmap262144 = new HashMap(); // for (var i = 0; i < 262144; i++) { // hashmap262144.set(makeid(16), makeid(128)); // } -console.log("define benchmarks"); -suite.add("create", function () { - hashmap = new HashMap(); -}) -.add("singleSet", function () { - hashmap.set(key, value); -},{'onCycle': function () { - key = makeid(16); - value = makeid(128); - hashmap.clear(); -}}) -.add("singleReplace", function () { - hashmap.set(key, value); -},{'onCycle': function () { - key = makeid(16); - value = makeid(128); - hashmap.clear(); - hashmap.set(key, makeid(128)); -}}) -.add("setAfter 1,024", function () { - hashmap1024.set(key, value); -},{'onCycle': function () { - hashmap1024.delete(key); - key = makeid(16); - value = makeid(128); -}}) -// .add("setAfter 262,144", function () { -// hashmap262144.set(key, value); -// },{'onCycle': function () { -// hashmap262144.delete(key); -// key = makeid(16); -// value = makeid(128); -// }}) -.on('cycle', function(event) { - console.log(String(event.target)); -}).on('complete', function () { - console.log('Fastest is ' + this.filter('fastest').map('name')); - console.log('Slowest is ' + this.filter('slowest').map('name')); -}).on('onError', function(err) { - console.log("Error",err); -}).run(); + console.log("define benchmarks"); + suite.add("create", function () { + hashmap = new HashMap(); + }) + .add("singleSet", function () { + hashmap.set(key, value); + },{'onCycle': function () { + key = makeid(16); + value = makeid(128); + hashmap.clear(); + }}) + .add("singleReplace", function () { + hashmap.set(key, value); + },{'onCycle': function () { + key = makeid(16); + value = makeid(128); + hashmap.clear(); + hashmap.set(key, makeid(128)); + }}) + .add("setAfter 1,024", function () { + hashmap1024.set(key, value); + },{'onCycle': function () { + hashmap1024.delete(key); + key = makeid(16); + value = makeid(128); + }}) + // .add("setAfter 262,144", function () { + // hashmap262144.set(key, value); + // },{'onCycle': function () { + // hashmap262144.delete(key); + // key = makeid(16); + // value = makeid(128); + // }}) + .on('cycle', function(event) { + console.log(String(event.target)); + }).on('complete', function () { + console.log('Fastest is ' + this.filter('fastest').map('name')); + console.log('Slowest is ' + this.filter('slowest').map('name')); + }).on('onError', function(err) { + console.log("Error",err); + }).run(); + +} function makeid(length) { From ab6b19a7c09cd2b5bedfc8cd5a6d8e37ed835757 Mon Sep 17 00:00:00 2001 From: jackmoxley Date: Sat, 20 Feb 2021 02:58:23 +0000 Subject: [PATCH 8/8] First optimisation pass, made the code functionality identical, added self to copyright as 2/3 of code is new. Performance: 2.4.0 is 38.76 X faster on _create_ an increase of 3775.95% 3.0.0 is 1.76 X faster on _singleSet_ an increase of 76.11% 3.0.0 is 2.16 X faster on _singleSet 20_ an increase of 116.21% 3.0.0 is 1.91 X faster on _singleReplace_ an increase of 90.53% 3.0.0 is 1.44 X faster on _setAfter 1,024_ an increase of 44.02% 3.0.0 is 1.91 X faster on _set 20 After 1,024_ an increase of 90.84% 3.0.0 is 3.25 X faster on _setAfter 131'072_ an increase of 225.36% 3.0.0 is 3.70 X faster on _set 20 After 131'072_ an increase of 270.20% --- LICENSE | 1 + Readme.md | 2 - hashmap.js | 210 ++++++++++++++++++++++++++++++++++++---------- test/benchmark.js | 199 +++++++++++++++++++++++++++++++------------ test/test.js | 84 +++++++++---------- 5 files changed, 352 insertions(+), 144 deletions(-) diff --git a/LICENSE b/LICENSE index 8a384f7..724949e 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,7 @@ (The MIT License) Copyright (c) 2012 Ariel Flesler +Copyright (c) 2021 Jack Moxley https://github.com/jackmoxley Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/Readme.md b/Readme.md index dd22972..aed4ee7 100644 --- a/Readme.md +++ b/Readme.md @@ -1,5 +1,3 @@ -Will turn this into an actual hashmap, do not use as a hashmap! - # HashMap Class for JavaScript ## Installation diff --git a/hashmap.js b/hashmap.js index 8f949c4..90343a3 100644 --- a/hashmap.js +++ b/hashmap.js @@ -1,7 +1,8 @@ /** * HashMap - HashMap Class for JavaScript * @author Ariel Flesler - * @version 2.4.0 + * @author Jack Moxley + * @version 3.0.0 * Homepage: https://github.com/flesler/hashmap */ @@ -41,8 +42,7 @@ } } - function HashBucket(safeKey, key, value) { - this._safeKey = safeKey; + function HashBucket(key, value) { this._key = key; this._value = value; this._next = null; @@ -51,11 +51,11 @@ HashBucket.prototype = { constructor: HashBucket, - get: function (hash, key) { + get: function (hash, equalTo, key) { var bucket = this; // avoid recursion do { - if (bucket._safeKey === key) { + if (equalTo(key, bucket._key)) { return bucket._value; } bucket = bucket._next; @@ -63,29 +63,29 @@ while (bucket != null); return null; }, - set: function (hash, safeKey, key, value) { + set: function (hash, equalTo, key, value) { var bucket = this; // avoid recursion while (true) { - if (bucket._safeKey === safeKey) { + if (equalTo(key, bucket._key)) { bucket._value = value; return false; } if (bucket._next) { bucket = bucket._next; } else { - bucket._next = new HashBucket(safeKey, key, value); + bucket._next = new HashBucket(key, value); this._size++; return true; } } }, - has: function (hash, key) { + has: function (hash, equalTo, key) { var bucket = this; // avoid recursion do { - if (bucket._safeKey === key) { + if (equalTo(key, bucket._key)) { return true; } bucket = bucket._next; @@ -107,16 +107,15 @@ return null; }, - delete: function (hash, key) { + delete: function (hash, equalTo, key) { var bucket = this; var prev = null; // avoid recursion do { - if (bucket._safeKey === key) { + if (equalTo(key, bucket._key)) { var next = bucket._next; if (bucket._next) { bucket._key = next._key; - bucket._safeKey = next._safeKey; bucket._value = next._value; bucket._next = next._next; } else if (prev) { @@ -163,35 +162,35 @@ HashBuckets.prototype = { constructor: HashBuckets, - get: function (hash, key) { + get: function (hash, equalTo, key) { var bucket = this._buckets[hash & this._options.mask]; if (bucket) { - return bucket.get(hash >>> this._options.widthB, key); + return bucket.get(hash >>> this._options.widthB, equalTo, key); } return null; }, - set: function (hash, safeKey, key, value) { + set: function (hash, equalTo, key, value) { var idx = hash & this._options.mask; var bucket = this._buckets[idx]; if (bucket) { - return bucket.set(hash >>> this._options.widthB, safeKey, key, value); + return bucket.set(hash >>> this._options.widthB, equalTo, key, value); } else { if (this._depth > 0) { bucket = new HashBuckets(this._options, this._depth - 1); - bucket.set(hash >>> this._options.widthB, safeKey, key, value); + bucket.set(hash >>> this._options.widthB, equalTo, key, value); this._buckets[idx] = bucket; } else { - this._buckets[idx] = new HashBucket(safeKey, key, value); + this._buckets[idx] = new HashBucket(key, value); } this._size++; return true; } }, - has: function (hash, key) { + has: function (hash, equalTo, key) { var bucket = this._buckets[hash & this._options.mask]; if (bucket) { - return bucket.has(hash >>> this._options.widthB, key); + return bucket.has(hash >>> this._options.widthB, equalTo, key); } return false; }, @@ -206,11 +205,11 @@ return null; }, - delete: function (hash, key) { + delete: function (hash, equalTo, key) { var idx = hash & this._options.mask; var bucket = this._buckets[idx]; if (bucket) { - if (bucket.delete(hash >>> this._options.widthB, key)) { + if (bucket.delete(hash >>> this._options.widthB, equalTo, key)) { if (bucket._size === 0) { delete this._buckets[idx]; this._size--; @@ -234,16 +233,13 @@ constructor: HashMap, get: function (key) { - var safeKey = this.safeKey(key); - var hash = hashCode(safeKey); - return this._buckets.get(hash, safeKey); + var hashEquals = this.hashEquals(key); + return this._buckets.get(hashEquals.hash, hashEquals.equalTo, key); }, set: function (key, value) { - var safeKey = this.safeKey(key); - var hash = hashCode(safeKey); - // Store original key as well (for iteration) - if (this._buckets.set(hash, safeKey, key, value)) { + var hashEquals = this.hashEquals(key); + if (this._buckets.set(hashEquals.hash, hashEquals.equalTo, key, value)) { this.size++; } }, @@ -260,9 +256,8 @@ }, has: function (key) { - var safeKey = this.safeKey(key); - var hash = hashCode(safeKey); - return this._buckets.has(hash, safeKey); + var hashEquals = this.hashEquals(key); + return this._buckets.has(hashEquals.hash, hashEquals.equalTo, key); }, search: function (value) { @@ -270,9 +265,8 @@ }, delete: function (key) { - var safeKey = this.safeKey(key); - var hash = hashCode(safeKey); - if (this._buckets.delete(hash, safeKey)) { + var hashEquals = this.hashEquals(key); + if (this._buckets.delete(hashEquals.hash, hashEquals.equalTo, key)) { this.size--; } }, @@ -356,10 +350,99 @@ clone: function () { return new HashMap(this); }, - hash: function (key) { - return hashCode(this.safeKey(key)); + hashEquals: function (key) { + var typeFunction = this.type; + switch (this.type(key)) { + case 'undefined': + case 'null': + return { + equalTo: defaultEquals, hash: 0 + }; + case 'boolean': + return { + equalTo: defaultEquals, hash: key ? 0 : 1 + }; + case 'number': + if (Number.isNaN(key)) { + return { + equalTo: function (me, them) { + return Number.isNaN(them); + }, + hash: 0 + }; + } + if (!Number.isFinite(key)) { + return { + equalTo: defaultEquals, hash: 0 + }; + } + if (Number.isInteger(key)) { + return { + equalTo: defaultEquals, hash: key + }; + } + return { + equalTo: defaultEquals, hash: compute_hash(key.toString()) + }; + case 'regexp': + return { + equalTo: function (me, them) { + if (typeFunction(them) === 'regexp') { + return me + '' === them + ''; + } + return false; + }, hash: compute_hash(key + '') + }; + + case 'date': + return { + equalTo: function (me, them) { + if (typeFunction(them) === 'date') { + return me.toString() === them.toString(); + } + return false; + }, hash: key.getTime() | 0 + }; + + case 'string': + return { + equalTo: defaultEquals, hash: compute_hash(key) + }; + case 'array': + var functions = []; + var hash_code = key.length; + for (var i = 0; i < key.length; i++) { + var hashEquals = this.hashEquals(key[i]); + functions.push(hashEquals.equalTo); + hash_code = hash_code + (hashEquals.hash * prime_powers[i & 0xFF]); + } + Object.freeze(functions); + return { + equalTo: function (me, them) { + if (Array.isArray(them) && me.length === them.length) { + for (var i = 0; i < me.length; i++) { + if (!functions[i](me[i], them[i])) { + return false; + } + } + return true; + } + return false; + }, + hash: hash_code | 0 + }; + default: + // TODO: Don't use expandos when Object.defineProperty is not available? + if (!key.hasOwnProperty('_hmuid_')) { + key._hmuid_ = ++HashMap.uid; + hide(key, '_hmuid_'); + } + + return this.hashEquals(key._hmuid_); + } }, - safeKey: function (key) { + // Deprecated + hash: function (key) { switch (this.type(key)) { case 'undefined': case 'null': @@ -391,7 +474,6 @@ return '♦' + key._hmuid_; } }, - forEach: function (func, ctx) { this._buckets.forEach(func, ctx || this); } @@ -442,14 +524,19 @@ } } - function hashCode(str) { - var hash = 0, i, chr; - for (i = 0; i < str.length; i++) { + function defaultEquals(me, them) { + return me === them; + } + + function compute_hash(str) { + var m = 2162722087; + var len = Math.min(str.length, 1024); // lets not go above 1024, it does mean we reuse prime powers 4 times. + var hash_value = len, i, chr; + for (i = 0; i < len; i++) { chr = str.charCodeAt(i); - hash = ((hash << 5) - hash) + chr; - hash |= 0; // Convert to 32bit integer + hash_value = (hash_value + (chr * prime_powers[i & 0xFF])) % m; } - return hash; + return hash_value; } function hide(obj, prop) { @@ -459,5 +546,36 @@ } } + //256 prime_powers + let prime_powers = Object.freeze( + [1, 127, 16129, 2048383, 260144641, 597538102, 192065909, 602427486, 813017677, 1605306890, + 578098852, 2048725333, 661466851, 1822850771, 90784608, 716034781, 102089533, 2151760256, 770569550, 539838935, + 1515160048, 2105782440, 1419553179, 777320512, 1397211109, 102599709, 53830521, 348309906, 980916322, + 1301213935, 887291133, 224425367, 386634478, 1522692792, 899718841, 1802744283, 1862704806, 826802879, + 1193305457, 159246949, 759863740, 1342923152, 1858917518, 345817303, 664355741, 27017714, 1268527591, + 1061569619, 730572219, 1948344159, 889390275, 491016401, 1802864491, 1877971222, 602915624, 875011203, + 827596344, 1294075512, 2143433499, 1875793498, 326344676, 354054199, 1710441533, 953865991, 28543985, + 1462364008, 1888851621, 1984726297, 1184477627, 1200834626, 1115451412, 1085393669, 1593504482, 1241915123, + 2007230357, 1879771160, 831507750, 1790824074, 348838263, 1048017661, 1172195640, 1803744364, 1989715093, + 1818054719, 1644408091, 1218507205, 1197146858, 647104876, 2161602033, 2020475229, 1399147817, 348561625, + 1012884635, 1035745512, 1776354804, 673963060, 1247147227, 508985478, 1922215183, 1896454497, 787569462, + 536105672, 1041035647, 285479862, 1652389082, 69370975, 159225477, 757136796, 996601264, 1130479482, 831236472, + 1756371768, 298839575, 1186350546, 1438695339, 1045652745, 871851308, 426289679, 70737058, 332718018, + 1163468633, 695414475, 1808754845, 463324093, 448663462, 749485412, 24875496, 996465905, 1113288889, 810753248, + 1317724407, 821398990, 507011554, 1671526835, 337143519, 1725507260, 704491233, 798781024, 1959974046, + 203663837, 2075364342, 1881898907, 1101731619, 1505702045, 904616059, 261968882, 829216709, 1499861867, + 162913453, 1225509748, 2086469819, 1129572399, 716036931, 102362583, 23715519, 849148826, 1868518639, + 1565159670, 1967568173, 1168117966, 1285879766, 1102573757, 1612653571, 1511127339, 1593628397, 1257652328, + 1843133305, 503944339, 1281990530, 608640785, 1602106650, 171668372, 174662374, 554900628, 1265272972, + 648233006, 142152456, 751585216, 291550604, 260651229, 661874778, 1874657500, 182072930, 1496041240, + 1840415911, 158835301, 707584444, 1191618821, 2107766264, 1671498827, 333586503, 1273766228, 1726876518, + 878386999, 1256322436, 1674237021, 681337141, 20933427, 495823142, 250598511, 1547901679, 1938525403, + 1805130350, 3013228, 382679956, 1020468498, 1998896113, 821322172, 497255668, 432529313, 863170576, + 1486558802, 636146285, 769860976, 449850037, 900180437, 1861366975, 656898342, 1242650128, 2100575992, + 758334283, 1148682113, 980248522, 1216403335, 929955368, 1317339038, 772457127, 779561214, 1681780263, + 1639328875, 573446773, 1457911300, 1323357705, 1536827836, 532147342, 538327737, 1323237902, 1521612855, + 762566842, 1686217106, 40085849, 765458649, 2053476595, 1264877125, 597960437, 245702454, 926102440, 828017182, + 1347521938, 280241253, 987085739, 2084729894, 908601924, 768173737, 235570684, 1802089737]); + return HashMap; })); diff --git a/test/benchmark.js b/test/benchmark.js index d773e4b..f75be15 100644 --- a/test/benchmark.js +++ b/test/benchmark.js @@ -1,77 +1,168 @@ -/*jshint -W030,-W121 */ -var HashMapNew = require('../hashmap'); -var HashMapOld = require('../HashMap2.4.0/hashmap'); -var Benchmark = require('benchmark'); - -for(var loop = 0; loop < 2 ; loop++) { - var HashMap; - var suite; - if(loop === 1){ - console.log("New"); - HashMap = HashMapNew.HashMap; - suite = new Benchmark.Suite("hashmap new"); - } else { - console.log("Old"); - HashMap = HashMapOld.HashMap; - suite = new Benchmark.Suite("hashmap old"); - } - var hashmap = new HashMap(); + +const Benchmark = require('benchmark'); +const hashmapImplementations = {'2.4.0': '../HashMap2.4.0/hashmap', '3.0.0': '../hashmap'}; +const array = require('lodash/array'); + +let theSuite = new Benchmark.Suite('hashmap benchmarks'); + +Object.entries(hashmapImplementations) + .forEach(([version, location]) => benchmarkHashMapImplementation(version, location)); + + +theSuite = theSuite.on('cycle', function (event) { + console.log(String(event.target)); +}).on('complete', function () { + array.uniq(this.filter('name').map('name')) + .forEach((name) => { + const fastest = Benchmark.filter(this.filter({'name': name}), 'fastest')[0]; + const slowest = Benchmark.filter(this.filter({'name': name}), 'slowest')[0]; + const fastestVersion = fastest.version; + const difference = ((fastest.hz - slowest.hz) / slowest.hz); + const percentageDifference = difference * 100; + console.log( fastestVersion, 'is', (difference + 1).toFixed(2), 'X faster on', name , 'an increase of', + percentageDifference.toFixed(2) + '%'); + } + ); +}).on('onError', function (err) { + console.log("Error", err); +}); +const RUN_AMOUNTS = 1; +for(let k = 0; k < RUN_AMOUNTS; k++){ + console.info("Iteration",k); + theSuite.run(); +} + +function benchmarkHashMapImplementation(version, location) { + console.info("Benchmarking:", version, "from:", location); + const HashMap = require(location).HashMap; + + let hashmap = new HashMap(); console.log("setup constants"); - var key = makeid(16); - var value = makeid(128); + let key = makeid(16); + let value = makeid(128); console.log("1'024 hashmap"); - var hashmap1024 = new HashMap(); + let hashmap1024 = new HashMap(); - for (var i = 0; i < 1024; i++) { + for (let i = 0; i < 1024; i++) { hashmap1024.set(makeid(16), makeid(128)); } -// console.log("262'144 hashmap"); -// var hashmap262144 = new HashMap(); -// for (var i = 0; i < 262144; i++) { -// hashmap262144.set(makeid(16), makeid(128)); -// } + console.log("131'072 hashmap"); + var hashmap131072 = new HashMap(); + for (var i = 0; i < 131072; i++) { + hashmap131072.set(makeid(16), makeid(128)); + } console.log("define benchmarks"); - suite.add("create", function () { - hashmap = new HashMap(); - }) - .add("singleSet", function () { + theSuite = theSuite + .add("_create_", function () { + new HashMap(); + }, { + 'version': version, + 'onStart': function () { + console.info("============="); + console.info("Testing", version); + console.info("============="); + } + }) + .add("_singleSet_", function () { hashmap.set(key, value); - },{'onCycle': function () { + }, { + 'onCycle': function () { key = makeid(16); value = makeid(128); hashmap.clear(); - }}) - .add("singleReplace", function () { + }, + 'version': version + }) + .add("_singleSet 20_", function () { + for (let k = 0; k < 20; k++) { + hashmap.set(key[k], value[k]); + } + }, { + 'onCycle': function () { + hashmap.clear(key); + + key = []; + value = []; + for (let k = 0; k < 20; k++) { + key.push(makeid(16)); + value.push(makeid(128)); + } + }, + 'version': version + }) + .add("_singleReplace_", function () { hashmap.set(key, value); - },{'onCycle': function () { + }, { + 'onCycle': function () { key = makeid(16); value = makeid(128); hashmap.clear(); hashmap.set(key, makeid(128)); - }}) - .add("setAfter 1,024", function () { + }, + 'version': version + }) + .add("_setAfter 1,024_", function () { hashmap1024.set(key, value); - },{'onCycle': function () { + }, { + 'onCycle': function () { hashmap1024.delete(key); key = makeid(16); value = makeid(128); - }}) - // .add("setAfter 262,144", function () { - // hashmap262144.set(key, value); - // },{'onCycle': function () { - // hashmap262144.delete(key); - // key = makeid(16); - // value = makeid(128); - // }}) - .on('cycle', function(event) { - console.log(String(event.target)); - }).on('complete', function () { - console.log('Fastest is ' + this.filter('fastest').map('name')); - console.log('Slowest is ' + this.filter('slowest').map('name')); - }).on('onError', function(err) { - console.log("Error",err); - }).run(); + }, + 'version': version + }) + .add("_set 20 After 1,024_", function () { + for (let k = 0; k < 20; k++) { + hashmap1024.set(key[k], value[k]); + } + }, { + 'onCycle': function () { + if (key && key.length) { + for (let k = 0; k < 20; k++) { + hashmap1024.delete(key[k]); + } + } + key = []; + value = []; + for (let k = 0; k < 20; k++) { + key.push(makeid(16)); + value.push(makeid(128)); + } + }, + 'version': version + }) + .add("_setAfter 131'072_", function () { + hashmap131072.set(key, value); + }, { + 'onCycle': function () { + hashmap131072.delete(key); + key = makeid(16); + value = makeid(128); + }, + 'version': version + }) + .add("_set 20 After 131'072_", function () { + for (let k = 0; k < 20; k++) { + hashmap131072.set(key[k], value[k]); + } + }, { + 'onCycle': function () { + if (key && key.length) { + for (let k = 0; k < 20; k++) { + hashmap131072.delete(key[k]); + } + } + + key = []; + value = []; + for (let k = 0; k < 20; k++) { + key.push(makeid(16)); + value.push(makeid(128)); + } + }, + 'version': version + }); } diff --git a/test/test.js b/test/test.js index 4b22d4b..869431c 100644 --- a/test/test.js +++ b/test/test.js @@ -50,48 +50,48 @@ describe('hashmap', function() { }); }); - // describe('hashmap.hash()', function() { - // function check(data, hash) { - // expect(hashmap.hash(data)).to.equal(hash); - // } - // - // it('should hash primitives accurately', function() { - // check(null, 'null'); - // check(undefined, 'undefined'); - // check(true, 'true'); - // check(false, 'false'); - // check(NaN, 'NaN'); - // check(1, '1'); - // check(1.1, '1.1'); - // check('1.1', '♠1.1'); - // check('Test', '♠Test'); - // }); - // - // it('should hash objects with primitive representation accurately', function() { - // check(/test/, '/test/'); - // check(new Date(Date.parse('Fri, 15 Aug 1986 15:05:00 GMT')), '♣524502300000'); - // }); - // - // it('should hash arrays accurately', function() { - // check([], '♥'); - // check([1, 2, 3], '♥1⁞2⁞3'); - // }); - // - // it('should hash unrecognized objects accurately', function() { - // check({}, '♦1'); - // check(HashMap, '♦2'); - // check(hashmap, '♦3'); - // }); - // - // it('should not add any iterable property to objects', function() { - // var obj = {}; - // hashmap.hash(obj); - // expect(obj).to.be.empty; - // }); - // - // // TODO: expect two hashmaps to hash the same object to the same value - // // TODO: test hash(1) !== hash('1') - // }); + describe('hashmap.hash()', function() { + function check(data, hash) { + expect(hashmap.hash(data)).to.equal(hash); + } + + it('should hash primitives accurately', function() { + check(null, 'null'); + check(undefined, 'undefined'); + check(true, 'true'); + check(false, 'false'); + check(NaN, 'NaN'); + check(1, '1'); + check(1.1, '1.1'); + check('1.1', '♠1.1'); + check('Test', '♠Test'); + }); + + it('should hash objects with primitive representation accurately', function() { + check(/test/, '/test/'); + check(new Date(Date.parse('Fri, 15 Aug 1986 15:05:00 GMT')), '♣524502300000'); + }); + + it('should hash arrays accurately', function() { + check([], '♥'); + check([1, 2, 3], '♥1⁞2⁞3'); + }); + + it('should hash unrecognized objects accurately', function() { + check({}, '♦1'); + check(HashMap, '♦2'); + check(hashmap, '♦3'); + }); + + it('should not add any iterable property to objects', function() { + var obj = {}; + hashmap.hash(obj); + expect(obj).to.be.empty; + }); + + // TODO: expect two hashmaps to hash the same object to the same value + // TODO: test hash(1) !== hash('1') + }); describe('method chaining', function() { it('should return the instance on some methods', function() {