# 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 * @template K, V44 */45goog.structs.Map = function(opt_map, var_args) {4647  /**48   * Underlying JS object used to implement the map.49   * @private {!Object}50   */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   * @private {!Array.<string>}65   */66  this.keys_ = [];6768  /**69   * The number of key value pairs in the map.70   * @private {number}71   */72  this.count_ = 0;7374  /**75   * Version used to detect changes while iterating.76   * @private {number}77   */78  this.version_ = 0;7980  var argLength = arguments.length;8182  if (argLength > 1) {83    if (argLength % 2) {84      throw Error('Uneven number of arguments');85    }86    for (var i = 0; i < argLength; i += 2) {87      this.set(arguments[i], arguments[i + 1]);88    }89  } else if (opt_map) {90    this.addAll(/** @type {Object} */ (opt_map));91  }92};939495/**96 * @return {number} The number of key-value pairs in the map.97 */98goog.structs.Map.prototype.getCount = function() {99  return this.count_;100};101102103/**104 * Returns the values of the map.105 * @return {!Array.<V>} The values in the map.106 */107goog.structs.Map.prototype.getValues = function() {108  this.cleanupKeysArray_();109110  var rv = [];111  for (var i = 0; i < this.keys_.length; i++) {112    var key = this.keys_[i];113    rv.push(this.map_[key]);114  }115  return rv;116};117118119/**120 * Returns the keys of the map.121 * @return {!Array.<string>} Array of string values.122 */123goog.structs.Map.prototype.getKeys = function() {124  this.cleanupKeysArray_();125  return /** @type {!Array.<string>} */ (this.keys_.concat());126};127128129/**130 * Whether the map contains the given key.131 * @param {*} key The key to check for.132 * @return {boolean} Whether the map contains the key.133 */134goog.structs.Map.prototype.containsKey = function(key) {135  return goog.structs.Map.hasKey_(this.map_, key);136};137138139/**140 * Whether the map contains the given value. This is O(n).141 * @param {V} val The value to check for.142 * @return {boolean} Whether the map contains the value.143 */144goog.structs.Map.prototype.containsValue = function(val) {145  for (var i = 0; i < this.keys_.length; i++) {146    var key = this.keys_[i];147    if (goog.structs.Map.hasKey_(this.map_, key) && this.map_[key] == val) {148      return true;149    }150  }151  return false;152};153154155/**156 * Whether this map is equal to the argument map.157 * @param {goog.structs.Map} otherMap The map against which to test equality.158 * @param {function(V, V): boolean=} opt_equalityFn Optional equality function159 *     to test equality of values. If not specified, this will test whether160 *     the values contained in each map are identical objects.161 * @return {boolean} Whether the maps are equal.162 */163goog.structs.Map.prototype.equals = function(otherMap, opt_equalityFn) {164  if (this === otherMap) {165    return true;166  }167168  if (this.count_ != otherMap.getCount()) {169    return false;170  }171172  var equalityFn = opt_equalityFn || goog.structs.Map.defaultEquals;173174  this.cleanupKeysArray_();175  for (var key, i = 0; key = this.keys_[i]; i++) {176    if (!equalityFn(this.get(key), otherMap.get(key))) {177      return false;178    }179  }180181  return true;182};183184185/**186 * Default equality test for values.187 * @param {*} a The first value.188 * @param {*} b The second value.189 * @return {boolean} Whether a and b reference the same object.190 */191goog.structs.Map.defaultEquals = function(a, b) {192  return a === b;193};194195196/**197 * @return {boolean} Whether the map is empty.198 */199goog.structs.Map.prototype.isEmpty = function() {200  return this.count_ == 0;201};202203204/**205 * Removes all key-value pairs from the map.206 */207goog.structs.Map.prototype.clear = function() {208  this.map_ = {};209  this.keys_.length = 0;210  this.count_ = 0;211  this.version_ = 0;212};213214215/**216 * Removes a key-value pair based on the key. This is O(logN) amortized due to217 * updating the keys array whenever the count becomes half the size of the keys218 * in the keys array.219 * @param {*} key  The key to remove.220 * @return {boolean} Whether object was removed.221 */222goog.structs.Map.prototype.remove = function(key) {223  if (goog.structs.Map.hasKey_(this.map_, key)) {224    delete this.map_[key];225    this.count_--;226    this.version_++;227228    // clean up the keys array if the threshhold is hit229    if (this.keys_.length > 2 * this.count_) {230      this.cleanupKeysArray_();231    }232233    return true;234  }235  return false;236};237238239/**240 * Cleans up the temp keys array by removing entries that are no longer in the241 * map.242 * @private243 */244goog.structs.Map.prototype.cleanupKeysArray_ = function() {245  if (this.count_ != this.keys_.length) {246    // First remove keys that are no longer in the map.247    var srcIndex = 0;248    var destIndex = 0;249    while (srcIndex < this.keys_.length) {250      var key = this.keys_[srcIndex];251      if (goog.structs.Map.hasKey_(this.map_, key)) {252        this.keys_[destIndex++] = key;253      }254      srcIndex++;255    }256    this.keys_.length = destIndex;257  }258259  if (this.count_ != this.keys_.length) {260    // If the count still isn't correct, that means we have duplicates. This can261    // happen when the same key is added and removed multiple times. Now we have262    // to allocate one extra Object to remove the duplicates. This could have263    // been done in the first pass, but in the common case, we can avoid264    // allocating an extra object by only doing this when necessary.265    var seen = {};266    var srcIndex = 0;267    var destIndex = 0;268    while (srcIndex < this.keys_.length) {269      var key = this.keys_[srcIndex];270      if (!(goog.structs.Map.hasKey_(seen, key))) {271        this.keys_[destIndex++] = key;272        seen[key] = 1;273      }274      srcIndex++;275    }276    this.keys_.length = destIndex;277  }278};279280281/**282 * Returns the value for the given key.  If the key is not found and the default283 * value is not given this will return {@code undefined}.284 * @param {*} key The key to get the value for.285 * @param {DEFAULT=} opt_val The value to return if no item is found for the286 *     given key, defaults to undefined.287 * @return {V|DEFAULT} The value for the given key.288 * @template DEFAULT289 */290goog.structs.Map.prototype.get = function(key, opt_val) {291  if (goog.structs.Map.hasKey_(this.map_, key)) {292    return this.map_[key];293  }294  return opt_val;295};296297298/**299 * Adds a key-value pair to the map.300 * @param {*} key The key.301 * @param {V} value The value to add.302 * @return {*} Some subclasses return a value.303 */304goog.structs.Map.prototype.set = function(key, value) {305  if (!(goog.structs.Map.hasKey_(this.map_, key))) {306    this.count_++;307    this.keys_.push(key);308    // Only change the version if we add a new key.309    this.version_++;310  }311  this.map_[key] = value;312};313314315/**316 * Adds multiple key-value pairs from another goog.structs.Map or Object.317 * @param {Object} map  Object containing the data to add.318 */319goog.structs.Map.prototype.addAll = function(map) {320  var keys, values;321  if (map instanceof goog.structs.Map) {322    keys = map.getKeys();323    values = map.getValues();324  } else {325    keys = goog.object.getKeys(map);326    values = goog.object.getValues(map);327  }328  // we could use goog.array.forEach here but I don't want to introduce that329  // dependency just for this.330  for (var i = 0; i < keys.length; i++) {331    this.set(keys[i], values[i]);332  }333};334335336/**337 * Calls the given function on each entry in the map.338 * @param {function(this:T, V, K, goog.structs.Map.<K,V>)} f339 * @param {T=} opt_obj The value of "this" inside f.340 * @template T341 */342goog.structs.Map.prototype.forEach = function(f, opt_obj) {343  var keys = this.getKeys();344  for (var i = 0; i < keys.length; i++) {345    var key = keys[i];346    var value = this.get(key);347    f.call(opt_obj, value, key, this);348  }349};350351352/**353 * Clones a map and returns a new map.354 * @return {!goog.structs.Map} A new map with the same key-value pairs.355 */356goog.structs.Map.prototype.clone = function() {357  return new goog.structs.Map(this);358};359360361/**362 * Returns a new map in which all the keys and values are interchanged363 * (keys become values and values become keys). If multiple keys map to the364 * same value, the chosen transposed value is implementation-dependent.365 *366 * It acts very similarly to {goog.object.transpose(Object)}.367 *368 * @return {!goog.structs.Map} The transposed map.369 */370goog.structs.Map.prototype.transpose = function() {371  var transposed = new goog.structs.Map();372  for (var i = 0; i < this.keys_.length; i++) {373    var key = this.keys_[i];374    var value = this.map_[key];375    transposed.set(value, key);376  }377378  return transposed;379};380381382/**383 * @return {!Object} Object representation of the map.384 */385goog.structs.Map.prototype.toObject = function() {386  this.cleanupKeysArray_();387  var obj = {};388  for (var i = 0; i < this.keys_.length; i++) {389    var key = this.keys_[i];390    obj[key] = this.map_[key];391  }392  return obj;393};394395396/**397 * Returns an iterator that iterates over the keys in the map.  Removal of keys398 * while iterating might have undesired side effects.399 * @return {!goog.iter.Iterator} An iterator over the keys in the map.400 */401goog.structs.Map.prototype.getKeyIterator = function() {402  return this.__iterator__(true);403};404405406/**407 * Returns an iterator that iterates over the values in the map.  Removal of408 * keys while iterating might have undesired side effects.409 * @return {!goog.iter.Iterator} An iterator over the values in the map.410 */411goog.structs.Map.prototype.getValueIterator = function() {412  return this.__iterator__(false);413};414415416/**417 * Returns an iterator that iterates over the values or the keys in the map.418 * This throws an exception if the map was mutated since the iterator was419 * created.420 * @param {boolean=} opt_keys True to iterate over the keys. False to iterate421 *     over the values.  The default value is false.422 * @return {!goog.iter.Iterator} An iterator over the values or keys in the map.423 */424goog.structs.Map.prototype.__iterator__ = function(opt_keys) {425  // Clean up keys to minimize the risk of iterating over dead keys.426  this.cleanupKeysArray_();427428  var i = 0;429  var keys = this.keys_;430  var map = this.map_;431  var version = this.version_;432  var selfObj = this;433434  var newIter = new goog.iter.Iterator;435  newIter.next = function() {436    while (true) {437      if (version != selfObj.version_) {438        throw Error('The map has changed since the iterator was created');439      }440      if (i >= keys.length) {441        throw goog.iter.StopIteration;442      }443      var key = keys[i++];444      return opt_keys ? key : map[key];445    }446  };447  return newIter;448};449450451/**452 * Safe way to test for hasOwnProperty.  It even allows testing for453 * 'hasOwnProperty'.454 * @param {Object} obj The object to test for presence of the given key.455 * @param {*} key The key to check for.456 * @return {boolean} Whether the object has the key.457 * @private458 */459goog.structs.Map.hasKey_ = function(obj, key) {460  return Object.prototype.hasOwnProperty.call(obj, key);461};