# lib/goog/structs/map.js

    1// Copyright 2006 The Closure Library Authors. All Rights Reserved.2//3// Licensed under the Apache License, Version 2.0 (the "License");4// you may not use this file except in compliance with the License.5// You may obtain a copy of the License at6//7//      http://www.apache.org/licenses/LICENSE-2.08//9// Unless required by applicable law or agreed to in writing, software10// distributed under the License is distributed on an "AS-IS" BASIS,11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.12// See the License for the specific language governing permissions and13// limitations under the License.1415/**16 * @fileoverview Datastructure: Hash Map.17 *18 * @author arv@google.com (Erik Arvidsson)19 * @author jonp@google.com (Jon Perlow) Optimized for IE620 *21 * This file contains an implementation of a Map structure. It implements a lot22 * of the methods used in goog.structs so those functions work on hashes. This23 * is best suited for complex key types. For simple keys such as numbers and24 * strings, and where special names like __proto__ are not a concern, consider25 * using the lighter-weight utilities in goog.object.26 */272829goog.provide('goog.structs.Map');3031goog.require('goog.iter.Iterator');32goog.require('goog.iter.StopIteration');33goog.require('goog.object');34353637/**38 * Class for Hash Map datastructure.39 * @param {*=} opt_map Map or Object to initialize the map with.40 * @param {...*} var_args If 2 or more arguments are present then they41 *     will be used as key-value pairs.42 * @constructor43 */44goog.structs.Map = function(opt_map, var_args) {4546  /**47   * Underlying JS object used to implement the map.48   * @type {!Object}49   * @private50   */51  this.map_ = {};5253  /**54   * An array of keys. This is necessary for two reasons:55   *   1. Iterating the keys using for (var key in this.map_) allocates an56   *      object for every key in IE which is really bad for IE6 GC perf.57   *   2. Without a side data structure, we would need to escape all the keys58   *      as that would be the only way we could tell during iteration if the59   *      key was an internal key or a property of the object.60   *61   * This array can contain deleted keys so it's necessary to check the map62   * as well to see if the key is still in the map (this doesn't require a63   * memory allocation in IE).64   * @type {!Array.<string>}65   * @private66   */67  this.keys_ = [];6869  var argLength = arguments.length;7071  if (argLength > 1) {72    if (argLength % 2) {73      throw Error('Uneven number of arguments');74    }75    for (var i = 0; i < argLength; i += 2) {76      this.set(arguments[i], arguments[i + 1]);77    }78  } else if (opt_map) {79    this.addAll(/** @type {Object} */ (opt_map));80  }81};828384/**85 * The number of key value pairs in the map.86 * @private87 * @type {number}88 */89goog.structs.Map.prototype.count_ = 0;909192/**93 * Version used to detect changes while iterating.94 * @private95 * @type {number}96 */97goog.structs.Map.prototype.version_ = 0;9899100/**101 * @return {number} The number of key-value pairs in the map.102 */103goog.structs.Map.prototype.getCount = function() {104  return this.count_;105};106107108/**109 * Returns the values of the map.110 * @return {!Array} The values in the map.111 */112goog.structs.Map.prototype.getValues = function() {113  this.cleanupKeysArray_();114115  var rv = [];116  for (var i = 0; i < this.keys_.length; i++) {117    var key = this.keys_[i];118    rv.push(this.map_[key]);119  }120  return rv;121};122123124/**125 * Returns the keys of the map.126 * @return {!Array.<string>} Array of string values.127 */128goog.structs.Map.prototype.getKeys = function() {129  this.cleanupKeysArray_();130  return /** @type {!Array.<string>} */ (this.keys_.concat());131};132133134/**135 * Whether the map contains the given key.136 * @param {*} key The key to check for.137 * @return {boolean} Whether the map contains the key.138 */139goog.structs.Map.prototype.containsKey = function(key) {140  return goog.structs.Map.hasKey_(this.map_, key);141};142143144/**145 * Whether the map contains the given value. This is O(n).146 * @param {*} val The value to check for.147 * @return {boolean} Whether the map contains the value.148 */149goog.structs.Map.prototype.containsValue = function(val) {150  for (var i = 0; i < this.keys_.length; i++) {151    var key = this.keys_[i];152    if (goog.structs.Map.hasKey_(this.map_, key) && this.map_[key] == val) {153      return true;154    }155  }156  return false;157};158159160/**161 * Whether this map is equal to the argument map.162 * @param {goog.structs.Map} otherMap The map against which to test equality.163 * @param {function(?, ?) : boolean=} opt_equalityFn Optional equality function164 *     to test equality of values. If not specified, this will test whether165 *     the values contained in each map are identical objects.166 * @return {boolean} Whether the maps are equal.167 */168goog.structs.Map.prototype.equals = function(otherMap, opt_equalityFn) {169  if (this === otherMap) {170    return true;171  }172173  if (this.count_ != otherMap.getCount()) {174    return false;175  }176177  var equalityFn = opt_equalityFn || goog.structs.Map.defaultEquals;178179  this.cleanupKeysArray_();180  for (var key, i = 0; key = this.keys_[i]; i++) {181    if (!equalityFn(this.get(key), otherMap.get(key))) {182      return false;183    }184  }185186  return true;187};188189190/**191 * Default equality test for values.192 * @param {*} a The first value.193 * @param {*} b The second value.194 * @return {boolean} Whether a and b reference the same object.195 */196goog.structs.Map.defaultEquals = function(a, b) {197  return a === b;198};199200201/**202 * @return {boolean} Whether the map is empty.203 */204goog.structs.Map.prototype.isEmpty = function() {205  return this.count_ == 0;206};207208209/**210 * Removes all key-value pairs from the map.211 */212goog.structs.Map.prototype.clear = function() {213  this.map_ = {};214  this.keys_.length = 0;215  this.count_ = 0;216  this.version_ = 0;217};218219220/**221 * Removes a key-value pair based on the key. This is O(logN) amortized due to222 * updating the keys array whenever the count becomes half the size of the keys223 * in the keys array.224 * @param {*} key  The key to remove.225 * @return {boolean} Whether object was removed.226 */227goog.structs.Map.prototype.remove = function(key) {228  if (goog.structs.Map.hasKey_(this.map_, key)) {229    delete this.map_[key];230    this.count_--;231    this.version_++;232233    // clean up the keys array if the threshhold is hit234    if (this.keys_.length > 2 * this.count_) {235      this.cleanupKeysArray_();236    }237238    return true;239  }240  return false;241};242243244/**245 * Cleans up the temp keys array by removing entries that are no longer in the246 * map.247 * @private248 */249goog.structs.Map.prototype.cleanupKeysArray_ = function() {250  if (this.count_ != this.keys_.length) {251    // First remove keys that are no longer in the map.252    var srcIndex = 0;253    var destIndex = 0;254    while (srcIndex < this.keys_.length) {255      var key = this.keys_[srcIndex];256      if (goog.structs.Map.hasKey_(this.map_, key)) {257        this.keys_[destIndex++] = key;258      }259      srcIndex++;260    }261    this.keys_.length = destIndex;262  }263264  if (this.count_ != this.keys_.length) {265    // If the count still isn't correct, that means we have duplicates. This can266    // happen when the same key is added and removed multiple times. Now we have267    // to allocate one extra Object to remove the duplicates. This could have268    // been done in the first pass, but in the common case, we can avoid269    // allocating an extra object by only doing this when necessary.270    var seen = {};271    var srcIndex = 0;272    var destIndex = 0;273    while (srcIndex < this.keys_.length) {274      var key = this.keys_[srcIndex];275      if (!(goog.structs.Map.hasKey_(seen, key))) {276        this.keys_[destIndex++] = key;277        seen[key] = 1;278      }279      srcIndex++;280    }281    this.keys_.length = destIndex;282  }283};284285286/**287 * Returns the value for the given key.  If the key is not found and the default288 * value is not given this will return {@code undefined}.289 * @param {*} key The key to get the value for.290 * @param {*=} opt_val The value to return if no item is found for the given291 *     key, defaults to undefined.292 * @return {*} The value for the given key.293 */294goog.structs.Map.prototype.get = function(key, opt_val) {295  if (goog.structs.Map.hasKey_(this.map_, key)) {296    return this.map_[key];297  }298  return opt_val;299};300301302/**303 * Adds a key-value pair to the map.304 * @param {*} key The key.305 * @param {*} value The value to add.306 * @return {*} Some subclasses return a value.307 */308goog.structs.Map.prototype.set = function(key, value) {309  if (!(goog.structs.Map.hasKey_(this.map_, key))) {310    this.count_++;311    this.keys_.push(key);312    // Only change the version if we add a new key.313    this.version_++;314  }315  this.map_[key] = value;316};317318319/**320 * Adds multiple key-value pairs from another goog.structs.Map or Object.321 * @param {Object} map  Object containing the data to add.322 */323goog.structs.Map.prototype.addAll = function(map) {324  var keys, values;325  if (map instanceof goog.structs.Map) {326    keys = map.getKeys();327    values = map.getValues();328  } else {329    keys = goog.object.getKeys(map);330    values = goog.object.getValues(map);331  }332  // we could use goog.array.forEach here but I don't want to introduce that333  // dependency just for this.334  for (var i = 0; i < keys.length; i++) {335    this.set(keys[i], values[i]);336  }337};338339340/**341 * Clones a map and returns a new map.342 * @return {!goog.structs.Map} A new map with the same key-value pairs.343 */344goog.structs.Map.prototype.clone = function() {345  return new goog.structs.Map(this);346};347348349/**350 * Returns a new map in which all the keys and values are interchanged351 * (keys become values and values become keys). If multiple keys map to the352 * same value, the chosen transposed value is implementation-dependent.353 *354 * It acts very similarly to {goog.object.transpose(Object)}.355 *356 * @return {!goog.structs.Map} The transposed map.357 */358goog.structs.Map.prototype.transpose = function() {359  var transposed = new goog.structs.Map();360  for (var i = 0; i < this.keys_.length; i++) {361    var key = this.keys_[i];362    var value = this.map_[key];363    transposed.set(value, key);364  }365366  return transposed;367};368369370/**371 * @return {!Object} Object representation of the map.372 */373goog.structs.Map.prototype.toObject = function() {374  this.cleanupKeysArray_();375  var obj = {};376  for (var i = 0; i < this.keys_.length; i++) {377    var key = this.keys_[i];378    obj[key] = this.map_[key];379  }380  return obj;381};382383384/**385 * Returns an iterator that iterates over the keys in the map.  Removal of keys386 * while iterating might have undesired side effects.387 * @return {!goog.iter.Iterator} An iterator over the keys in the map.388 */389goog.structs.Map.prototype.getKeyIterator = function() {390  return this.__iterator__(true);391};392393394/**395 * Returns an iterator that iterates over the values in the map.  Removal of396 * keys while iterating might have undesired side effects.397 * @return {!goog.iter.Iterator} An iterator over the values in the map.398 */399goog.structs.Map.prototype.getValueIterator = function() {400  return this.__iterator__(false);401};402403404/**405 * Returns an iterator that iterates over the values or the keys in the map.406 * This throws an exception if the map was mutated since the iterator was407 * created.408 * @param {boolean=} opt_keys True to iterate over the keys. False to iterate409 *     over the values.  The default value is false.410 * @return {!goog.iter.Iterator} An iterator over the values or keys in the map.411 */412goog.structs.Map.prototype.__iterator__ = function(opt_keys) {413  // Clean up keys to minimize the risk of iterating over dead keys.414  this.cleanupKeysArray_();415416  var i = 0;417  var keys = this.keys_;418  var map = this.map_;419  var version = this.version_;420  var selfObj = this;421422  var newIter = new goog.iter.Iterator;423  newIter.next = function() {424    while (true) {425      if (version != selfObj.version_) {426        throw Error('The map has changed since the iterator was created');427      }428      if (i >= keys.length) {429        throw goog.iter.StopIteration;430      }431      var key = keys[i++];432      return opt_keys ? key : map[key];433    }434  };435  return newIter;436};437438439/**440 * Safe way to test for hasOwnProperty.  It even allows testing for441 * 'hasOwnProperty'.442 * @param {Object} obj The object to test for presence of the given key.443 * @param {*} key The key to check for.444 * @return {boolean} Whether the object has the key.445 * @private446 */447goog.structs.Map.hasKey_ = function(obj, key) {448  return Object.prototype.hasOwnProperty.call(obj, key);449};