lib/goog/structs/structs.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 Generics method for collection-like classes and objects.
17 *
18 * @author arv@google.com (Erik Arvidsson)
19 *
20 * This file contains functions to work with collections. It supports using
21 * Map, Set, Array and Object and other classes that implement collection-like
22 * methods.
23 */
24
25
26goog.provide('goog.structs');
27
28goog.require('goog.array');
29goog.require('goog.object');
30
31
32// We treat an object as a dictionary if it has getKeys or it is an object that
33// isn't arrayLike.
34
35
36/**
37 * Returns the number of values in the collection-like object.
38 * @param {Object} col The collection-like object.
39 * @return {number} The number of values in the collection-like object.
40 */
41goog.structs.getCount = function(col) {
42 if (typeof col.getCount == 'function') {
43 return col.getCount();
44 }
45 if (goog.isArrayLike(col) || goog.isString(col)) {
46 return col.length;
47 }
48 return goog.object.getCount(col);
49};
50
51
52/**
53 * Returns the values of the collection-like object.
54 * @param {Object} col The collection-like object.
55 * @return {!Array} The values in the collection-like object.
56 */
57goog.structs.getValues = function(col) {
58 if (typeof col.getValues == 'function') {
59 return col.getValues();
60 }
61 if (goog.isString(col)) {
62 return col.split('');
63 }
64 if (goog.isArrayLike(col)) {
65 var rv = [];
66 var l = col.length;
67 for (var i = 0; i < l; i++) {
68 rv.push(col[i]);
69 }
70 return rv;
71 }
72 return goog.object.getValues(col);
73};
74
75
76/**
77 * Returns the keys of the collection. Some collections have no notion of
78 * keys/indexes and this function will return undefined in those cases.
79 * @param {Object} col The collection-like object.
80 * @return {!Array|undefined} The keys in the collection.
81 */
82goog.structs.getKeys = function(col) {
83 if (typeof col.getKeys == 'function') {
84 return col.getKeys();
85 }
86 // if we have getValues but no getKeys we know this is a key-less collection
87 if (typeof col.getValues == 'function') {
88 return undefined;
89 }
90 if (goog.isArrayLike(col) || goog.isString(col)) {
91 var rv = [];
92 var l = col.length;
93 for (var i = 0; i < l; i++) {
94 rv.push(i);
95 }
96 return rv;
97 }
98
99 return goog.object.getKeys(col);
100};
101
102
103/**
104 * Whether the collection contains the given value. This is O(n) and uses
105 * equals (==) to test the existence.
106 * @param {Object} col The collection-like object.
107 * @param {*} val The value to check for.
108 * @return {boolean} True if the map contains the value.
109 */
110goog.structs.contains = function(col, val) {
111 if (typeof col.contains == 'function') {
112 return col.contains(val);
113 }
114 if (typeof col.containsValue == 'function') {
115 return col.containsValue(val);
116 }
117 if (goog.isArrayLike(col) || goog.isString(col)) {
118 return goog.array.contains(/** @type {Array} */ (col), val);
119 }
120 return goog.object.containsValue(col, val);
121};
122
123
124/**
125 * Whether the collection is empty.
126 * @param {Object} col The collection-like object.
127 * @return {boolean} True if empty.
128 */
129goog.structs.isEmpty = function(col) {
130 if (typeof col.isEmpty == 'function') {
131 return col.isEmpty();
132 }
133
134 // We do not use goog.string.isEmpty because here we treat the string as
135 // collection and as such even whitespace matters
136
137 if (goog.isArrayLike(col) || goog.isString(col)) {
138 return goog.array.isEmpty(/** @type {Array} */ (col));
139 }
140 return goog.object.isEmpty(col);
141};
142
143
144/**
145 * Removes all the elements from the collection.
146 * @param {Object} col The collection-like object.
147 */
148goog.structs.clear = function(col) {
149 // NOTE(arv): This should not contain strings because strings are immutable
150 if (typeof col.clear == 'function') {
151 col.clear();
152 } else if (goog.isArrayLike(col)) {
153 goog.array.clear(/** @type {goog.array.ArrayLike} */ (col));
154 } else {
155 goog.object.clear(col);
156 }
157};
158
159
160/**
161 * Calls a function for each value in a collection. The function takes
162 * three arguments; the value, the key and the collection.
163 *
164 * NOTE: This will be deprecated soon! Please use a more specific method if
165 * possible, e.g. goog.array.forEach, goog.object.forEach, etc.
166 *
167 * @param {S} col The collection-like object.
168 * @param {function(this:T,?,?,S):?} f The function to call for every value.
169 * This function takes
170 * 3 arguments (the value, the key or undefined if the collection has no
171 * notion of keys, and the collection) and the return value is irrelevant.
172 * @param {T=} opt_obj The object to be used as the value of 'this'
173 * within {@code f}.
174 * @template T,S
175 */
176goog.structs.forEach = function(col, f, opt_obj) {
177 if (typeof col.forEach == 'function') {
178 col.forEach(f, opt_obj);
179 } else if (goog.isArrayLike(col) || goog.isString(col)) {
180 goog.array.forEach(/** @type {Array} */ (col), f, opt_obj);
181 } else {
182 var keys = goog.structs.getKeys(col);
183 var values = goog.structs.getValues(col);
184 var l = values.length;
185 for (var i = 0; i < l; i++) {
186 f.call(opt_obj, values[i], keys && keys[i], col);
187 }
188 }
189};
190
191
192/**
193 * Calls a function for every value in the collection. When a call returns true,
194 * adds the value to a new collection (Array is returned by default).
195 *
196 * @param {S} col The collection-like object.
197 * @param {function(this:T,?,?,S):boolean} f The function to call for every
198 * value. This function takes
199 * 3 arguments (the value, the key or undefined if the collection has no
200 * notion of keys, and the collection) and should return a Boolean. If the
201 * return value is true the value is added to the result collection. If it
202 * is false the value is not included.
203 * @param {T=} opt_obj The object to be used as the value of 'this'
204 * within {@code f}.
205 * @return {!Object|!Array} A new collection where the passed values are
206 * present. If col is a key-less collection an array is returned. If col
207 * has keys and values a plain old JS object is returned.
208 * @template T,S
209 */
210goog.structs.filter = function(col, f, opt_obj) {
211 if (typeof col.filter == 'function') {
212 return col.filter(f, opt_obj);
213 }
214 if (goog.isArrayLike(col) || goog.isString(col)) {
215 return goog.array.filter(/** @type {!Array} */ (col), f, opt_obj);
216 }
217
218 var rv;
219 var keys = goog.structs.getKeys(col);
220 var values = goog.structs.getValues(col);
221 var l = values.length;
222 if (keys) {
223 rv = {};
224 for (var i = 0; i < l; i++) {
225 if (f.call(opt_obj, values[i], keys[i], col)) {
226 rv[keys[i]] = values[i];
227 }
228 }
229 } else {
230 // We should not use goog.array.filter here since we want to make sure that
231 // the index is undefined as well as make sure that col is passed to the
232 // function.
233 rv = [];
234 for (var i = 0; i < l; i++) {
235 if (f.call(opt_obj, values[i], undefined, col)) {
236 rv.push(values[i]);
237 }
238 }
239 }
240 return rv;
241};
242
243
244/**
245 * Calls a function for every value in the collection and adds the result into a
246 * new collection (defaults to creating a new Array).
247 *
248 * @param {S} col The collection-like object.
249 * @param {function(this:T,?,?,S):V} f The function to call for every value.
250 * This function takes 3 arguments (the value, the key or undefined if the
251 * collection has no notion of keys, and the collection) and should return
252 * something. The result will be used as the value in the new collection.
253 * @param {T=} opt_obj The object to be used as the value of 'this'
254 * within {@code f}.
255 * @return {!Object.<V>|!Array.<V>} A new collection with the new values. If
256 * col is a key-less collection an array is returned. If col has keys and
257 * values a plain old JS object is returned.
258 * @template T,S,V
259 */
260goog.structs.map = function(col, f, opt_obj) {
261 if (typeof col.map == 'function') {
262 return col.map(f, opt_obj);
263 }
264 if (goog.isArrayLike(col) || goog.isString(col)) {
265 return goog.array.map(/** @type {!Array} */ (col), f, opt_obj);
266 }
267
268 var rv;
269 var keys = goog.structs.getKeys(col);
270 var values = goog.structs.getValues(col);
271 var l = values.length;
272 if (keys) {
273 rv = {};
274 for (var i = 0; i < l; i++) {
275 rv[keys[i]] = f.call(opt_obj, values[i], keys[i], col);
276 }
277 } else {
278 // We should not use goog.array.map here since we want to make sure that
279 // the index is undefined as well as make sure that col is passed to the
280 // function.
281 rv = [];
282 for (var i = 0; i < l; i++) {
283 rv[i] = f.call(opt_obj, values[i], undefined, col);
284 }
285 }
286 return rv;
287};
288
289
290/**
291 * Calls f for each value in a collection. If any call returns true this returns
292 * true (without checking the rest). If all returns false this returns false.
293 *
294 * @param {S} col The collection-like object.
295 * @param {function(this:T,?,?,S):boolean} f The function to call for every
296 * value. This function takes 3 arguments (the value, the key or undefined
297 * if the collection has no notion of keys, and the collection) and should
298 * return a boolean.
299 * @param {T=} opt_obj The object to be used as the value of 'this'
300 * within {@code f}.
301 * @return {boolean} True if any value passes the test.
302 * @template T,S
303 */
304goog.structs.some = function(col, f, opt_obj) {
305 if (typeof col.some == 'function') {
306 return col.some(f, opt_obj);
307 }
308 if (goog.isArrayLike(col) || goog.isString(col)) {
309 return goog.array.some(/** @type {!Array} */ (col), f, opt_obj);
310 }
311 var keys = goog.structs.getKeys(col);
312 var values = goog.structs.getValues(col);
313 var l = values.length;
314 for (var i = 0; i < l; i++) {
315 if (f.call(opt_obj, values[i], keys && keys[i], col)) {
316 return true;
317 }
318 }
319 return false;
320};
321
322
323/**
324 * Calls f for each value in a collection. If all calls return true this return
325 * true this returns true. If any returns false this returns false at this point
326 * and does not continue to check the remaining values.
327 *
328 * @param {S} col The collection-like object.
329 * @param {function(this:T,?,?,S):boolean} f The function to call for every
330 * value. This function takes 3 arguments (the value, the key or
331 * undefined if the collection has no notion of keys, and the collection)
332 * and should return a boolean.
333 * @param {T=} opt_obj The object to be used as the value of 'this'
334 * within {@code f}.
335 * @return {boolean} True if all key-value pairs pass the test.
336 * @template T,S
337 */
338goog.structs.every = function(col, f, opt_obj) {
339 if (typeof col.every == 'function') {
340 return col.every(f, opt_obj);
341 }
342 if (goog.isArrayLike(col) || goog.isString(col)) {
343 return goog.array.every(/** @type {!Array} */ (col), f, opt_obj);
344 }
345 var keys = goog.structs.getKeys(col);
346 var values = goog.structs.getValues(col);
347 var l = values.length;
348 for (var i = 0; i < l; i++) {
349 if (!f.call(opt_obj, values[i], keys && keys[i], col)) {
350 return false;
351 }
352 }
353 return true;
354};