# 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 |
26 | goog.provide('goog.structs');
27 |
28 | goog.require('goog.array');
29 | goog.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 | */
41 | goog.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 | */
57 | goog.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 | */
82 | goog.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 | */
110 | goog.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 | */
129 | goog.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 | */
148 | goog.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 | */
176 | goog.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 | */
210 | goog.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 | */
260 | goog.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 | */
304 | goog.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 | */
338 | goog.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 | };