Path: blob/trunk/third_party/closure/goog/structs/map.js
4501 views
/**1* @license2* Copyright The Closure Library Authors.3* SPDX-License-Identifier: Apache-2.04*/56/**7* @fileoverview Datastructure: Hash Map.8*9*10* This file contains an implementation of a Map structure. It implements a lot11* of the methods used in goog.structs so those functions work on hashes. This12* is best suited for complex key types. For simple keys such as numbers and13* strings consider using the lighter-weight utilities in goog.object.14* @deprecated goog.structs.Map is deprecated in favour of ES6 Maps.15*/161718goog.provide('goog.structs.Map');1920goog.require('goog.collections.iters');21goog.require('goog.iter');22goog.require('goog.iter.Iterator');23goog.require('goog.iter.es6');24252627/**28* Class for Hash Map datastructure.29* @param {*=} opt_map Map or Object to initialize the map with.30* @param {...*} var_args If 2 or more arguments are present then they31* will be used as key-value pairs.32* @constructor33* @final34* @template K, V35* @deprecated This type is misleading: use ES6 Map instead.36*/37goog.structs.Map = function(opt_map, var_args) {38'use strict';39/**40* Underlying JS object used to implement the map.41* @private {!Object}42*/43this.map_ = {};4445/**46* An array of keys. This is necessary for two reasons:47* 1. Iterating the keys using for (var key in this.map_) allocates an48* object for every key in IE which is really bad for IE6 GC perf.49* 2. Without a side data structure, we would need to escape all the keys50* as that would be the only way we could tell during iteration if the51* key was an internal key or a property of the object.52*53* This array can contain deleted keys so it's necessary to check the map54* as well to see if the key is still in the map (this doesn't require a55* memory allocation in IE).56* @private {!Array<string>}57*/58this.keys_ = [];5960/**61* The number of key value pairs in the map.62* @const {number}63*/64this.size = 0;6566/**67* Version used to detect changes while iterating.68* @private {number}69*/70this.version_ = 0;7172var argLength = arguments.length;7374if (argLength > 1) {75if (argLength % 2) {76throw new Error('Uneven number of arguments');77}78for (var i = 0; i < argLength; i += 2) {79this.set(arguments[i], arguments[i + 1]);80}81} else if (opt_map) {82this.addAll(/** @type {!Object} */ (opt_map));83}84};858687/**88* @return {number} The number of key-value pairs in the map.89* @deprecated Use the `size` property instead, for alignment with ES6 Map.90*/91goog.structs.Map.prototype.getCount = function() {92'use strict';93return this.size;94};959697/**98* Returns the values of the map.99* @return {!Array<V>} The values in the map.100* @deprecated Use `Array.from(map.values())` instead, for alignment with ES6101* Map.102*/103goog.structs.Map.prototype.getValues = function() {104'use strict';105this.cleanupKeysArray_();106107var rv = [];108for (var i = 0; i < this.keys_.length; i++) {109var key = this.keys_[i];110rv.push(this.map_[key]);111}112return rv;113};114115116/**117* Returns the keys of the map.118* @return {!Array<string>} Array of string values.119* @deprecated Use `Array.from(map.keys())` instead, for alignment with ES6 Map.120*/121goog.structs.Map.prototype.getKeys = function() {122'use strict';123this.cleanupKeysArray_();124return /** @type {!Array<string>} */ (this.keys_.concat());125};126127128/**129* Whether the map contains the given key.130* @param {*} key The key to check for.131* @return {boolean} Whether the map contains the key.132* @deprecated Use `has` instead, for alignment with ES6 Map.133*/134goog.structs.Map.prototype.containsKey = function(key) {135'use strict';136return this.has(key);137};138139/**140* Whether the map contains the given key.141* @param {*} key The key to check for.142* @return {boolean} Whether the map contains the key.143*/144goog.structs.Map.prototype.has = function(key) {145'use strict';146return goog.structs.Map.hasKey_(this.map_, key);147};148149150/**151* Whether the map contains the given value. This is O(n).152* @param {V} val The value to check for.153* @return {boolean} Whether the map contains the value.154*/155goog.structs.Map.prototype.containsValue = function(val) {156'use strict';157for (var i = 0; i < this.keys_.length; i++) {158var key = this.keys_[i];159if (goog.structs.Map.hasKey_(this.map_, key) && this.map_[key] == val) {160return true;161}162}163return false;164};165166167/**168* Whether this map is equal to the argument map.169* @param {goog.structs.Map} otherMap The map against which to test equality.170* @param {function(V, V): boolean=} opt_equalityFn Optional equality function171* to test equality of values. If not specified, this will test whether172* the values contained in each map are identical objects.173* @return {boolean} Whether the maps are equal.174* @deprecated Use goog.collections.maps.equals(thisMap, otherMap,175* opt_equalityFn) instead, for alignment with ES6 Map.176*/177goog.structs.Map.prototype.equals = function(otherMap, opt_equalityFn) {178'use strict';179if (this === otherMap) {180return true;181}182183if (this.size != otherMap.getCount()) {184return false;185}186187var equalityFn = opt_equalityFn || goog.structs.Map.defaultEquals;188189this.cleanupKeysArray_();190for (var key, i = 0; key = this.keys_[i]; i++) {191if (!equalityFn(this.get(key), otherMap.get(key))) {192return false;193}194}195196return true;197};198199200/**201* Default equality test for values.202* @param {*} a The first value.203* @param {*} b The second value.204* @return {boolean} Whether a and b reference the same object.205*/206goog.structs.Map.defaultEquals = function(a, b) {207'use strict';208return a === b;209};210211212/**213* @return {boolean} Whether the map is empty.214* @deprecated Use the size property and compare against 0, for alignment with215* ES6 Map.216*/217goog.structs.Map.prototype.isEmpty = function() {218'use strict';219return this.size == 0;220};221222223/**224* Removes all key-value pairs from the map.225*/226goog.structs.Map.prototype.clear = function() {227'use strict';228this.map_ = {};229this.keys_.length = 0;230this.setSizeInternal_(0);231this.version_ = 0;232};233234235236/**237* Removes a key-value pair based on the key. This is O(logN) amortized due to238* updating the keys array whenever the count becomes half the size of the keys239* in the keys array.240* @param {*} key The key to remove.241* @return {boolean} Whether object was removed.242* @deprecated Use `delete` instead, for alignment with ES6 Map.243*/244goog.structs.Map.prototype.remove = function(key) {245return this.delete(key);246};247248/**249* Removes a key-value pair based on the key. This is O(logN) amortized due250* to updating the keys array whenever the count becomes half the size of251* the keys in the keys array.252* @param {*} key The key to remove.253* @return {boolean} Whether object was removed.254*/255goog.structs.Map.prototype.delete = function(key) {256'use strict';257if (goog.structs.Map.hasKey_(this.map_, key)) {258delete this.map_[key];259this.setSizeInternal_(this.size - 1);260this.version_++;261262// clean up the keys array if the threshold is hit263if (this.keys_.length > 2 * this.size) {264this.cleanupKeysArray_();265}266267return true;268}269return false;270};271272273/**274* Cleans up the temp keys array by removing entries that are no longer in the275* map.276* @private277*/278goog.structs.Map.prototype.cleanupKeysArray_ = function() {279'use strict';280if (this.size != this.keys_.length) {281// First remove keys that are no longer in the map.282var srcIndex = 0;283var destIndex = 0;284while (srcIndex < this.keys_.length) {285var key = this.keys_[srcIndex];286if (goog.structs.Map.hasKey_(this.map_, key)) {287this.keys_[destIndex++] = key;288}289srcIndex++;290}291this.keys_.length = destIndex;292}293294if (this.size != this.keys_.length) {295// If the count still isn't correct, that means we have duplicates. This can296// happen when the same key is added and removed multiple times. Now we have297// to allocate one extra Object to remove the duplicates. This could have298// been done in the first pass, but in the common case, we can avoid299// allocating an extra object by only doing this when necessary.300var seen = {};301var srcIndex = 0;302var destIndex = 0;303while (srcIndex < this.keys_.length) {304var key = this.keys_[srcIndex];305if (!(goog.structs.Map.hasKey_(seen, key))) {306this.keys_[destIndex++] = key;307seen[key] = 1;308}309srcIndex++;310}311this.keys_.length = destIndex;312}313};314315316/**317* Returns the value for the given key. If the key is not found and the default318* value is not given this will return `undefined`.319* @param {*} key The key to get the value for.320* @param {DEFAULT=} opt_val The value to return if no item is found for the321* given key, defaults to undefined.322* @return {V|DEFAULT} The value for the given key.323* @template DEFAULT324*/325goog.structs.Map.prototype.get = function(key, opt_val) {326'use strict';327if (goog.structs.Map.hasKey_(this.map_, key)) {328return this.map_[key];329}330return opt_val;331};332333334/**335* Adds a key-value pair to the map.336* @param {*} key The key.337* @param {V} value The value to add.338*/339goog.structs.Map.prototype.set = function(key, value) {340'use strict';341if (!(goog.structs.Map.hasKey_(this.map_, key))) {342this.setSizeInternal_(this.size + 1);343// TODO(johnlenz): This class lies, it claims to return an array of string344// keys, but instead returns the original object used.345this.keys_.push(/** @type {?} */ (key));346// Only change the version if we add a new key.347this.version_++;348}349this.map_[key] = value;350};351352353/**354* Adds multiple key-value pairs from another goog.structs.Map or Object.355* @param {?Object} map Object containing the data to add.356* @deprecated Use goog.collections.maps.setAll(thisMap, map.entries()) if map357* is an ES6 or goog.structs Map, or358* goog.collections.maps.setAll(thisMap, Object.entries(map)) otherwise.359*/360goog.structs.Map.prototype.addAll = function(map) {361'use strict';362if (map instanceof goog.structs.Map) {363var keys = map.getKeys();364for (var i = 0; i < keys.length; i++) {365this.set(keys[i], map.get(keys[i]));366}367} else {368for (var key in map) {369this.set(key, map[key]);370}371}372};373374375/**376* Calls the given function on each entry in the map.377* @param {function(this:T, V, K, goog.structs.Map<K,V>)} f378* @param {T=} opt_obj The value of "this" inside f.379* @template T380* @deprecated Use ES6 Iteration instead.381*/382goog.structs.Map.prototype.forEach = function(f, opt_obj) {383'use strict';384var keys = this.getKeys();385for (var i = 0; i < keys.length; i++) {386var key = keys[i];387var value = this.get(key);388f.call(opt_obj, value, key, this);389}390};391392393/**394* Clones a map and returns a new map.395* @return {!goog.structs.Map} A new map with the same key-value pairs.396* @deprecated Use `new Map(thisMap.entries())` instead, for alignment with397* ES6 Map.398*/399goog.structs.Map.prototype.clone = function() {400'use strict';401return new goog.structs.Map(this);402};403404405/**406* Returns a new map in which all the keys and values are interchanged407* (keys become values and values become keys). If multiple keys map to the408* same value, the chosen transposed value is implementation-dependent.409*410* It acts very similarly to {goog.object.transpose(Object)}.411*412* @return {!goog.structs.Map} The transposed map.413* @deprecated Use goog.collections.maps.transpose instead, for alignment with414* ES6 Maps.415*/416goog.structs.Map.prototype.transpose = function() {417'use strict';418var transposed = new goog.structs.Map();419for (var i = 0; i < this.keys_.length; i++) {420var key = this.keys_[i];421var value = this.map_[key];422transposed.set(value, key);423}424425return transposed;426};427428429/**430* @return {!Object} Object representation of the map.431* @deprecated Use goog.collections.maps.toObject(thisMap) instead, for aligment432* with ES6 Maps.433*/434goog.structs.Map.prototype.toObject = function() {435'use strict';436this.cleanupKeysArray_();437var obj = {};438for (var i = 0; i < this.keys_.length; i++) {439var key = this.keys_[i];440obj[key] = this.map_[key];441}442return obj;443};444445446/**447* Returns an iterator that iterates over the keys in the map. Removal of keys448* while iterating might have undesired side effects.449* @return {!goog.iter.Iterator} An iterator over the keys in the map.450* @deprecated Use `keys()` with native iteration protocols, for alignment451* with ES6 Map.452*/453goog.structs.Map.prototype.getKeyIterator = function() {454'use strict';455return this.__iterator__(true);456};457458/**459* @return {!IteratorIterable<K>} An ES6 Iterator that iterates over the maps460* keys.461*/462goog.structs.Map.prototype.keys = function() {463'use strict';464return goog.iter.es6.ShimIterable.of(this.getKeyIterator()).toEs6();465};466467468/**469* Returns an iterator that iterates over the values in the map. Removal of470* keys while iterating might have undesired side effects.471* @return {!goog.iter.Iterator} An iterator over the values in the map.472* @deprecated Use `values()` with native iteration protocols, for alignment473* with ES6 Map.474*/475goog.structs.Map.prototype.getValueIterator = function() {476'use strict';477return this.__iterator__(false);478};479480/**481* @return {!IteratorIterable<V>} An ES6 Iterator that iterates over the maps482* values.483*/484goog.structs.Map.prototype.values = function() {485'use strict';486return goog.iter.es6.ShimIterable.of(this.getValueIterator()).toEs6();487};488489/**490* @return {!IteratorIterable<!Array<K|V>>} An iterator of entries in this map.491* The type is actually Array<[K,V]> but this is not representable in the492* Closure Type System.493*/494goog.structs.Map.prototype.entries = function() {495const self = this;496return goog.collections.iters.map(this.keys(), function(key) {497return [key, self.get(key)];498});499};500501/**502* Returns an iterator that iterates over the values or the keys in the map.503* This throws an exception if the map was mutated since the iterator was504* created.505* @param {boolean=} opt_keys True to iterate over the keys. False to iterate506* over the values. The default value is false.507* @return {!goog.iter.Iterator} An iterator over the values or keys in the map.508* @deprecated Call either `keys` or `values` and use native iteration, for509* alignment with ES6 Map.510*/511goog.structs.Map.prototype.__iterator__ = function(opt_keys) {512'use strict';513// Clean up keys to minimize the risk of iterating over dead keys.514this.cleanupKeysArray_();515516var i = 0;517var version = this.version_;518var selfObj = this;519520var newIter = new goog.iter.Iterator;521/**522* @return {!IIterableResult<K|V>}523* @override524*/525newIter.next = function() {526'use strict';527if (version != selfObj.version_) {528throw new Error('The map has changed since the iterator was created');529}530if (i >= selfObj.keys_.length) {531return goog.iter.ES6_ITERATOR_DONE;532}533var key = selfObj.keys_[i++];534return goog.iter.createEs6IteratorYield(opt_keys ? key : selfObj.map_[key]);535};536537return newIter;538};539540541/**542* Assigns to the size property to isolate supressions of const assignment to543* only where they are needed.544* @param {number} newSize The size to update to.545* @private546*/547goog.structs.Map.prototype.setSizeInternal_ = function(newSize) {548/** @suppress {const} */549this.size = newSize;550};551552553/**554* Safe way to test for hasOwnProperty. It even allows testing for555* 'hasOwnProperty'.556* @param {!Object} obj The object to test for presence of the given key.557* @param {*} key The key to check for.558* @return {boolean} Whether the object has the key.559* @private560*/561goog.structs.Map.hasKey_ = function(obj, key) {562'use strict';563return Object.prototype.hasOwnProperty.call(obj, key);564};565566567