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 * @param {S} col The collection-like object.
165 * @param {function(this:T,?,?,S):?} f The function to call for every value.
166 * This function takes
167 * 3 arguments (the value, the key or undefined if the collection has no
168 * notion of keys, and the collection) and the return value is irrelevant.
169 * @param {T=} opt_obj The object to be used as the value of 'this'
170 * within {@code f}.
171 * @template T,S
172 */
173goog.structs.forEach = function(col, f, opt_obj) {
174 if (typeof col.forEach == 'function') {
175 col.forEach(f, opt_obj);
176 } else if (goog.isArrayLike(col) || goog.isString(col)) {
177 goog.array.forEach(/** @type {Array} */ (col), f, opt_obj);
178 } else {
179 var keys = goog.structs.getKeys(col);
180 var values = goog.structs.getValues(col);
181 var l = values.length;
182 for (var i = 0; i < l; i++) {
183 f.call(opt_obj, values[i], keys && keys[i], col);
184 }
185 }
186};
187
188
189/**
190 * Calls a function for every value in the collection. When a call returns true,
191 * adds the value to a new collection (Array is returned by default).
192 *
193 * @param {S} col The collection-like object.
194 * @param {function(this:T,?,?,S):boolean} f The function to call for every
195 * value. This function takes
196 * 3 arguments (the value, the key or undefined if the collection has no
197 * notion of keys, and the collection) and should return a Boolean. If the
198 * return value is true the value is added to the result collection. If it
199 * is false the value is not included.
200 * @param {T=} opt_obj The object to be used as the value of 'this'
201 * within {@code f}.
202 * @return {!Object|!Array} A new collection where the passed values are
203 * present. If col is a key-less collection an array is returned. If col
204 * has keys and values a plain old JS object is returned.
205 * @template T,S
206 */
207goog.structs.filter = function(col, f, opt_obj) {
208 if (typeof col.filter == 'function') {
209 return col.filter(f, opt_obj);
210 }
211 if (goog.isArrayLike(col) || goog.isString(col)) {
212 return goog.array.filter(/** @type {!Array} */ (col), f, opt_obj);
213 }
214
215 var rv;
216 var keys = goog.structs.getKeys(col);
217 var values = goog.structs.getValues(col);
218 var l = values.length;
219 if (keys) {
220 rv = {};
221 for (var i = 0; i < l; i++) {
222 if (f.call(opt_obj, values[i], keys[i], col)) {
223 rv[keys[i]] = values[i];
224 }
225 }
226 } else {
227 // We should not use goog.array.filter here since we want to make sure that
228 // the index is undefined as well as make sure that col is passed to the
229 // function.
230 rv = [];
231 for (var i = 0; i < l; i++) {
232 if (f.call(opt_obj, values[i], undefined, col)) {
233 rv.push(values[i]);
234 }
235 }
236 }
237 return rv;
238};
239
240
241/**
242 * Calls a function for every value in the collection and adds the result into a
243 * new collection (defaults to creating a new Array).
244 *
245 * @param {S} col The collection-like object.
246 * @param {function(this:T,?,?,S):V} f The function to call for every value.
247 * This function takes 3 arguments (the value, the key or undefined if the
248 * collection has no notion of keys, and the collection) and should return
249 * something. The result will be used as the value in the new collection.
250 * @param {T=} opt_obj The object to be used as the value of 'this'
251 * within {@code f}.
252 * @return {!Object.<V>|!Array.<V>} A new collection with the new values. If
253 * col is a key-less collection an array is returned. If col has keys and
254 * values a plain old JS object is returned.
255 * @template T,S,V
256 */
257goog.structs.map = function(col, f, opt_obj) {
258 if (typeof col.map == 'function') {
259 return col.map(f, opt_obj);
260 }
261 if (goog.isArrayLike(col) || goog.isString(col)) {
262 return goog.array.map(/** @type {!Array} */ (col), f, opt_obj);
263 }
264
265 var rv;
266 var keys = goog.structs.getKeys(col);
267 var values = goog.structs.getValues(col);
268 var l = values.length;
269 if (keys) {
270 rv = {};
271 for (var i = 0; i < l; i++) {
272 rv[keys[i]] = f.call(opt_obj, values[i], keys[i], col);
273 }
274 } else {
275 // We should not use goog.array.map here since we want to make sure that
276 // the index is undefined as well as make sure that col is passed to the
277 // function.
278 rv = [];
279 for (var i = 0; i < l; i++) {
280 rv[i] = f.call(opt_obj, values[i], undefined, col);
281 }
282 }
283 return rv;
284};
285
286
287/**
288 * Calls f for each value in a collection. If any call returns true this returns
289 * true (without checking the rest). If all returns false this returns false.
290 *
291 * @param {S} col The collection-like object.
292 * @param {function(this:T,?,?,S):boolean} f The function to call for every
293 * value. This function takes 3 arguments (the value, the key or undefined
294 * if the collection has no notion of keys, and the collection) and should
295 * return a boolean.
296 * @param {T=} opt_obj The object to be used as the value of 'this'
297 * within {@code f}.
298 * @return {boolean} True if any value passes the test.
299 * @template T,S
300 */
301goog.structs.some = function(col, f, opt_obj) {
302 if (typeof col.some == 'function') {
303 return col.some(f, opt_obj);
304 }
305 if (goog.isArrayLike(col) || goog.isString(col)) {
306 return goog.array.some(/** @type {!Array} */ (col), f, opt_obj);
307 }
308 var keys = goog.structs.getKeys(col);
309 var values = goog.structs.getValues(col);
310 var l = values.length;
311 for (var i = 0; i < l; i++) {
312 if (f.call(opt_obj, values[i], keys && keys[i], col)) {
313 return true;
314 }
315 }
316 return false;
317};
318
319
320/**
321 * Calls f for each value in a collection. If all calls return true this return
322 * true this returns true. If any returns false this returns false at this point
323 * and does not continue to check the remaining values.
324 *
325 * @param {S} col The collection-like object.
326 * @param {function(this:T,?,?,S):boolean} f The function to call for every
327 * value. This function takes 3 arguments (the value, the key or
328 * undefined if the collection has no notion of keys, and the collection)
329 * and should return a boolean.
330 * @param {T=} opt_obj The object to be used as the value of 'this'
331 * within {@code f}.
332 * @return {boolean} True if all key-value pairs pass the test.
333 * @template T,S
334 */
335goog.structs.every = function(col, f, opt_obj) {
336 if (typeof col.every == 'function') {
337 return col.every(f, opt_obj);
338 }
339 if (goog.isArrayLike(col) || goog.isString(col)) {
340 return goog.array.every(/** @type {!Array} */ (col), f, opt_obj);
341 }
342 var keys = goog.structs.getKeys(col);
343 var values = goog.structs.getValues(col);
344 var l = values.length;
345 for (var i = 0; i < l; i++) {
346 if (!f.call(opt_obj, values[i], keys && keys[i], col)) {
347 return false;
348 }
349 }
350 return true;
351};