# 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 at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // 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 and
13 | // limitations under the License.
14 |
15 | /**
16 | * @fileoverview Datastructure: Hash Map.
17 | *
18 | * @author arv@google.com (Erik Arvidsson)
19 | *
20 | * This file contains an implementation of a Map structure. It implements a lot
21 | * of the methods used in goog.structs so those functions work on hashes. This
22 | * is best suited for complex key types. For simple keys such as numbers and
23 | * strings consider using the lighter-weight utilities in goog.object.
24 | */
25 |
26 |
27 | goog.provide('goog.structs.Map');
28 |
29 | goog.require('goog.iter.Iterator');
30 | goog.require('goog.iter.StopIteration');
31 | goog.require('goog.object');
32 |
33 |
34 |
35 | /**
36 | * Class for Hash Map datastructure.
37 | * @param {*=} opt_map Map or Object to initialize the map with.
38 | * @param {...*} var_args If 2 or more arguments are present then they
39 | * will be used as key-value pairs.
40 | * @constructor
41 | * @template K, V
42 | */
43 | goog.structs.Map = function(opt_map, var_args) {
44 |
45 | /**
46 | * Underlying JS object used to implement the map.
47 | * @private {!Object}
48 | */
49 | this.map_ = {};
50 |
51 | /**
52 | * An array of keys. This is necessary for two reasons:
53 | * 1. Iterating the keys using for (var key in this.map_) allocates an
54 | * object for every key in IE which is really bad for IE6 GC perf.
55 | * 2. Without a side data structure, we would need to escape all the keys
56 | * as that would be the only way we could tell during iteration if the
57 | * key was an internal key or a property of the object.
58 | *
59 | * This array can contain deleted keys so it's necessary to check the map
60 | * as well to see if the key is still in the map (this doesn't require a
61 | * memory allocation in IE).
62 | * @private {!Array<string>}
63 | */
64 | this.keys_ = [];
65 |
66 | /**
67 | * The number of key value pairs in the map.
68 | * @private {number}
69 | */
70 | this.count_ = 0;
71 |
72 | /**
73 | * Version used to detect changes while iterating.
74 | * @private {number}
75 | */
76 | this.version_ = 0;
77 |
78 | var argLength = arguments.length;
79 |
80 | if (argLength > 1) {
81 | if (argLength % 2) {
82 | throw Error('Uneven number of arguments');
83 | }
84 | for (var i = 0; i < argLength; i += 2) {
85 | this.set(arguments[i], arguments[i + 1]);
86 | }
87 | } else if (opt_map) {
88 | this.addAll(/** @type {Object} */ (opt_map));
89 | }
90 | };
91 |
92 |
93 | /**
94 | * @return {number} The number of key-value pairs in the map.
95 | */
96 | goog.structs.Map.prototype.getCount = function() {
97 | return this.count_;
98 | };
99 |
100 |
101 | /**
102 | * Returns the values of the map.
103 | * @return {!Array<V>} The values in the map.
104 | */
105 | goog.structs.Map.prototype.getValues = function() {
106 | this.cleanupKeysArray_();
107 |
108 | var rv = [];
109 | for (var i = 0; i < this.keys_.length; i++) {
110 | var key = this.keys_[i];
111 | rv.push(this.map_[key]);
112 | }
113 | return rv;
114 | };
115 |
116 |
117 | /**
118 | * Returns the keys of the map.
119 | * @return {!Array<string>} Array of string values.
120 | */
121 | goog.structs.Map.prototype.getKeys = function() {
122 | this.cleanupKeysArray_();
123 | return /** @type {!Array<string>} */ (this.keys_.concat());
124 | };
125 |
126 |
127 | /**
128 | * Whether the map contains the given key.
129 | * @param {*} key The key to check for.
130 | * @return {boolean} Whether the map contains the key.
131 | */
132 | goog.structs.Map.prototype.containsKey = function(key) {
133 | return goog.structs.Map.hasKey_(this.map_, key);
134 | };
135 |
136 |
137 | /**
138 | * Whether the map contains the given value. This is O(n).
139 | * @param {V} val The value to check for.
140 | * @return {boolean} Whether the map contains the value.
141 | */
142 | goog.structs.Map.prototype.containsValue = function(val) {
143 | for (var i = 0; i < this.keys_.length; i++) {
144 | var key = this.keys_[i];
145 | if (goog.structs.Map.hasKey_(this.map_, key) && this.map_[key] == val) {
146 | return true;
147 | }
148 | }
149 | return false;
150 | };
151 |
152 |
153 | /**
154 | * Whether this map is equal to the argument map.
155 | * @param {goog.structs.Map} otherMap The map against which to test equality.
156 | * @param {function(V, V): boolean=} opt_equalityFn Optional equality function
157 | * to test equality of values. If not specified, this will test whether
158 | * the values contained in each map are identical objects.
159 | * @return {boolean} Whether the maps are equal.
160 | */
161 | goog.structs.Map.prototype.equals = function(otherMap, opt_equalityFn) {
162 | if (this === otherMap) {
163 | return true;
164 | }
165 |
166 | if (this.count_ != otherMap.getCount()) {
167 | return false;
168 | }
169 |
170 | var equalityFn = opt_equalityFn || goog.structs.Map.defaultEquals;
171 |
172 | this.cleanupKeysArray_();
173 | for (var key, i = 0; key = this.keys_[i]; i++) {
174 | if (!equalityFn(this.get(key), otherMap.get(key))) {
175 | return false;
176 | }
177 | }
178 |
179 | return true;
180 | };
181 |
182 |
183 | /**
184 | * Default equality test for values.
185 | * @param {*} a The first value.
186 | * @param {*} b The second value.
187 | * @return {boolean} Whether a and b reference the same object.
188 | */
189 | goog.structs.Map.defaultEquals = function(a, b) {
190 | return a === b;
191 | };
192 |
193 |
194 | /**
195 | * @return {boolean} Whether the map is empty.
196 | */
197 | goog.structs.Map.prototype.isEmpty = function() {
198 | return this.count_ == 0;
199 | };
200 |
201 |
202 | /**
203 | * Removes all key-value pairs from the map.
204 | */
205 | goog.structs.Map.prototype.clear = function() {
206 | this.map_ = {};
207 | this.keys_.length = 0;
208 | this.count_ = 0;
209 | this.version_ = 0;
210 | };
211 |
212 |
213 | /**
214 | * Removes a key-value pair based on the key. This is O(logN) amortized due to
215 | * updating the keys array whenever the count becomes half the size of the keys
216 | * in the keys array.
217 | * @param {*} key The key to remove.
218 | * @return {boolean} Whether object was removed.
219 | */
220 | goog.structs.Map.prototype.remove = function(key) {
221 | if (goog.structs.Map.hasKey_(this.map_, key)) {
222 | delete this.map_[key];
223 | this.count_--;
224 | this.version_++;
225 |
226 | // clean up the keys array if the threshhold is hit
227 | if (this.keys_.length > 2 * this.count_) {
228 | this.cleanupKeysArray_();
229 | }
230 |
231 | return true;
232 | }
233 | return false;
234 | };
235 |
236 |
237 | /**
238 | * Cleans up the temp keys array by removing entries that are no longer in the
239 | * map.
240 | * @private
241 | */
242 | goog.structs.Map.prototype.cleanupKeysArray_ = function() {
243 | if (this.count_ != this.keys_.length) {
244 | // First remove keys that are no longer in the map.
245 | var srcIndex = 0;
246 | var destIndex = 0;
247 | while (srcIndex < this.keys_.length) {
248 | var key = this.keys_[srcIndex];
249 | if (goog.structs.Map.hasKey_(this.map_, key)) {
250 | this.keys_[destIndex++] = key;
251 | }
252 | srcIndex++;
253 | }
254 | this.keys_.length = destIndex;
255 | }
256 |
257 | if (this.count_ != this.keys_.length) {
258 | // If the count still isn't correct, that means we have duplicates. This can
259 | // happen when the same key is added and removed multiple times. Now we have
260 | // to allocate one extra Object to remove the duplicates. This could have
261 | // been done in the first pass, but in the common case, we can avoid
262 | // allocating an extra object by only doing this when necessary.
263 | var seen = {};
264 | var srcIndex = 0;
265 | var destIndex = 0;
266 | while (srcIndex < this.keys_.length) {
267 | var key = this.keys_[srcIndex];
268 | if (!(goog.structs.Map.hasKey_(seen, key))) {
269 | this.keys_[destIndex++] = key;
270 | seen[key] = 1;
271 | }
272 | srcIndex++;
273 | }
274 | this.keys_.length = destIndex;
275 | }
276 | };
277 |
278 |
279 | /**
280 | * Returns the value for the given key. If the key is not found and the default
281 | * value is not given this will return {@code undefined}.
282 | * @param {*} key The key to get the value for.
283 | * @param {DEFAULT=} opt_val The value to return if no item is found for the
284 | * given key, defaults to undefined.
285 | * @return {V|DEFAULT} The value for the given key.
286 | * @template DEFAULT
287 | */
288 | goog.structs.Map.prototype.get = function(key, opt_val) {
289 | if (goog.structs.Map.hasKey_(this.map_, key)) {
290 | return this.map_[key];
291 | }
292 | return opt_val;
293 | };
294 |
295 |
296 | /**
297 | * Adds a key-value pair to the map.
298 | * @param {*} key The key.
299 | * @param {V} value The value to add.
300 | * @return {*} Some subclasses return a value.
301 | */
302 | goog.structs.Map.prototype.set = function(key, value) {
303 | if (!(goog.structs.Map.hasKey_(this.map_, key))) {
304 | this.count_++;
305 | // TODO(johnlenz): This class lies, it claims to return an array of string
306 | // keys, but instead returns the original object used.
307 | this.keys_.push(/** @type {?} */ (key));
308 | // Only change the version if we add a new key.
309 | this.version_++;
310 | }
311 | this.map_[key] = value;
312 | };
313 |
314 |
315 | /**
316 | * Adds multiple key-value pairs from another goog.structs.Map or Object.
317 | * @param {Object} map Object containing the data to add.
318 | */
319 | goog.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 that
329 | // dependency just for this.
330 | for (var i = 0; i < keys.length; i++) {
331 | this.set(keys[i], values[i]);
332 | }
333 | };
334 |
335 |
336 | /**
337 | * Calls the given function on each entry in the map.
338 | * @param {function(this:T, V, K, goog.structs.Map<K,V>)} f
339 | * @param {T=} opt_obj The value of "this" inside f.
340 | * @template T
341 | */
342 | goog.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 | };
350 |
351 |
352 | /**
353 | * Clones a map and returns a new map.
354 | * @return {!goog.structs.Map} A new map with the same key-value pairs.
355 | */
356 | goog.structs.Map.prototype.clone = function() {
357 | return new goog.structs.Map(this);
358 | };
359 |
360 |
361 | /**
362 | * Returns a new map in which all the keys and values are interchanged
363 | * (keys become values and values become keys). If multiple keys map to the
364 | * 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 | */
370 | goog.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 | }
377 |
378 | return transposed;
379 | };
380 |
381 |
382 | /**
383 | * @return {!Object} Object representation of the map.
384 | */
385 | goog.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 | };
394 |
395 |
396 | /**
397 | * Returns an iterator that iterates over the keys in the map. Removal of keys
398 | * while iterating might have undesired side effects.
399 | * @return {!goog.iter.Iterator} An iterator over the keys in the map.
400 | */
401 | goog.structs.Map.prototype.getKeyIterator = function() {
402 | return this.__iterator__(true);
403 | };
404 |
405 |
406 | /**
407 | * Returns an iterator that iterates over the values in the map. Removal of
408 | * keys while iterating might have undesired side effects.
409 | * @return {!goog.iter.Iterator} An iterator over the values in the map.
410 | */
411 | goog.structs.Map.prototype.getValueIterator = function() {
412 | return this.__iterator__(false);
413 | };
414 |
415 |
416 | /**
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 was
419 | * created.
420 | * @param {boolean=} opt_keys True to iterate over the keys. False to iterate
421 | * over the values. The default value is false.
422 | * @return {!goog.iter.Iterator} An iterator over the values or keys in the map.
423 | */
424 | goog.structs.Map.prototype.__iterator__ = function(opt_keys) {
425 | // Clean up keys to minimize the risk of iterating over dead keys.
426 | this.cleanupKeysArray_();
427 |
428 | var i = 0;
429 | var version = this.version_;
430 | var selfObj = this;
431 |
432 | var newIter = new goog.iter.Iterator;
433 | newIter.next = function() {
434 | if (version != selfObj.version_) {
435 | throw Error('The map has changed since the iterator was created');
436 | }
437 | if (i >= selfObj.keys_.length) {
438 | throw goog.iter.StopIteration;
439 | }
440 | var key = selfObj.keys_[i++];
441 | return opt_keys ? key : selfObj.map_[key];
442 | };
443 | return newIter;
444 | };
445 |
446 |
447 | /**
448 | * Safe way to test for hasOwnProperty. It even allows testing for
449 | * 'hasOwnProperty'.
450 | * @param {Object} obj The object to test for presence of the given key.
451 | * @param {*} key The key to check for.
452 | * @return {boolean} Whether the object has the key.
453 | * @private
454 | */
455 | goog.structs.Map.hasKey_ = function(obj, key) {
456 | return Object.prototype.hasOwnProperty.call(obj, key);
457 | };