/** * 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; }));