lib/goog/array/array.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 Utilities for manipulating arrays.
17 *
18 * @author arv@google.com (Erik Arvidsson)
19 */
20
21
22goog.provide('goog.array');
23goog.provide('goog.array.ArrayLike');
24
25goog.require('goog.asserts');
26
27
28/**
29 * @define {boolean} NATIVE_ARRAY_PROTOTYPES indicates whether the code should
30 * rely on Array.prototype functions, if available.
31 *
32 * The Array.prototype functions can be defined by external libraries like
33 * Prototype and setting this flag to false forces closure to use its own
34 * goog.array implementation.
35 *
36 * If your javascript can be loaded by a third party site and you are wary about
37 * relying on the prototype functions, specify
38 * "--define goog.NATIVE_ARRAY_PROTOTYPES=false" to the JSCompiler.
39 *
40 * Setting goog.TRUSTED_SITE to false will automatically set
41 * NATIVE_ARRAY_PROTOTYPES to false.
42 */
43goog.define('goog.NATIVE_ARRAY_PROTOTYPES', goog.TRUSTED_SITE);
44
45
46/**
47 * @define {boolean} If true, JSCompiler will use the native implementation of
48 * array functions where appropriate (e.g., {@code Array#filter}) and remove the
49 * unused pure JS implementation.
50 */
51goog.define('goog.array.ASSUME_NATIVE_FUNCTIONS', false);
52
53
54/**
55 * @typedef {Array|NodeList|Arguments|{length: number}}
56 */
57goog.array.ArrayLike;
58
59
60/**
61 * Returns the last element in an array without removing it.
62 * Same as goog.array.last.
63 * @param {Array<T>|goog.array.ArrayLike} array The array.
64 * @return {T} Last item in array.
65 * @template T
66 */
67goog.array.peek = function(array) {
68 return array[array.length - 1];
69};
70
71
72/**
73 * Returns the last element in an array without removing it.
74 * Same as goog.array.peek.
75 * @param {Array<T>|goog.array.ArrayLike} array The array.
76 * @return {T} Last item in array.
77 * @template T
78 */
79goog.array.last = goog.array.peek;
80
81
82/**
83 * Reference to the original {@code Array.prototype}.
84 * @private
85 */
86goog.array.ARRAY_PROTOTYPE_ = Array.prototype;
87
88
89// NOTE(arv): Since most of the array functions are generic it allows you to
90// pass an array-like object. Strings have a length and are considered array-
91// like. However, the 'in' operator does not work on strings so we cannot just
92// use the array path even if the browser supports indexing into strings. We
93// therefore end up splitting the string.
94
95
96/**
97 * Returns the index of the first element of an array with a specified value, or
98 * -1 if the element is not present in the array.
99 *
100 * See {@link http://tinyurl.com/developer-mozilla-org-array-indexof}
101 *
102 * @param {Array<T>|goog.array.ArrayLike} arr The array to be searched.
103 * @param {T} obj The object for which we are searching.
104 * @param {number=} opt_fromIndex The index at which to start the search. If
105 * omitted the search starts at index 0.
106 * @return {number} The index of the first matching array element.
107 * @template T
108 */
109goog.array.indexOf = goog.NATIVE_ARRAY_PROTOTYPES &&
110 (goog.array.ASSUME_NATIVE_FUNCTIONS ||
111 goog.array.ARRAY_PROTOTYPE_.indexOf) ?
112 function(arr, obj, opt_fromIndex) {
113 goog.asserts.assert(arr.length != null);
114
115 return goog.array.ARRAY_PROTOTYPE_.indexOf.call(arr, obj, opt_fromIndex);
116 } :
117 function(arr, obj, opt_fromIndex) {
118 var fromIndex = opt_fromIndex == null ?
119 0 : (opt_fromIndex < 0 ?
120 Math.max(0, arr.length + opt_fromIndex) : opt_fromIndex);
121
122 if (goog.isString(arr)) {
123 // Array.prototype.indexOf uses === so only strings should be found.
124 if (!goog.isString(obj) || obj.length != 1) {
125 return -1;
126 }
127 return arr.indexOf(obj, fromIndex);
128 }
129
130 for (var i = fromIndex; i < arr.length; i++) {
131 if (i in arr && arr[i] === obj)
132 return i;
133 }
134 return -1;
135 };
136
137
138/**
139 * Returns the index of the last element of an array with a specified value, or
140 * -1 if the element is not present in the array.
141 *
142 * See {@link http://tinyurl.com/developer-mozilla-org-array-lastindexof}
143 *
144 * @param {!Array<T>|!goog.array.ArrayLike} arr The array to be searched.
145 * @param {T} obj The object for which we are searching.
146 * @param {?number=} opt_fromIndex The index at which to start the search. If
147 * omitted the search starts at the end of the array.
148 * @return {number} The index of the last matching array element.
149 * @template T
150 */
151goog.array.lastIndexOf = goog.NATIVE_ARRAY_PROTOTYPES &&
152 (goog.array.ASSUME_NATIVE_FUNCTIONS ||
153 goog.array.ARRAY_PROTOTYPE_.lastIndexOf) ?
154 function(arr, obj, opt_fromIndex) {
155 goog.asserts.assert(arr.length != null);
156
157 // Firefox treats undefined and null as 0 in the fromIndex argument which
158 // leads it to always return -1
159 var fromIndex = opt_fromIndex == null ? arr.length - 1 : opt_fromIndex;
160 return goog.array.ARRAY_PROTOTYPE_.lastIndexOf.call(arr, obj, fromIndex);
161 } :
162 function(arr, obj, opt_fromIndex) {
163 var fromIndex = opt_fromIndex == null ? arr.length - 1 : opt_fromIndex;
164
165 if (fromIndex < 0) {
166 fromIndex = Math.max(0, arr.length + fromIndex);
167 }
168
169 if (goog.isString(arr)) {
170 // Array.prototype.lastIndexOf uses === so only strings should be found.
171 if (!goog.isString(obj) || obj.length != 1) {
172 return -1;
173 }
174 return arr.lastIndexOf(obj, fromIndex);
175 }
176
177 for (var i = fromIndex; i >= 0; i--) {
178 if (i in arr && arr[i] === obj)
179 return i;
180 }
181 return -1;
182 };
183
184
185/**
186 * Calls a function for each element in an array. Skips holes in the array.
187 * See {@link http://tinyurl.com/developer-mozilla-org-array-foreach}
188 *
189 * @param {Array<T>|goog.array.ArrayLike} arr Array or array like object over
190 * which to iterate.
191 * @param {?function(this: S, T, number, ?): ?} f The function to call for every
192 * element. This function takes 3 arguments (the element, the index and the
193 * array). The return value is ignored.
194 * @param {S=} opt_obj The object to be used as the value of 'this' within f.
195 * @template T,S
196 */
197goog.array.forEach = goog.NATIVE_ARRAY_PROTOTYPES &&
198 (goog.array.ASSUME_NATIVE_FUNCTIONS ||
199 goog.array.ARRAY_PROTOTYPE_.forEach) ?
200 function(arr, f, opt_obj) {
201 goog.asserts.assert(arr.length != null);
202
203 goog.array.ARRAY_PROTOTYPE_.forEach.call(arr, f, opt_obj);
204 } :
205 function(arr, f, opt_obj) {
206 var l = arr.length; // must be fixed during loop... see docs
207 var arr2 = goog.isString(arr) ? arr.split('') : arr;
208 for (var i = 0; i < l; i++) {
209 if (i in arr2) {
210 f.call(opt_obj, arr2[i], i, arr);
211 }
212 }
213 };
214
215
216/**
217 * Calls a function for each element in an array, starting from the last
218 * element rather than the first.
219 *
220 * @param {Array<T>|goog.array.ArrayLike} arr Array or array
221 * like object over which to iterate.
222 * @param {?function(this: S, T, number, ?): ?} f The function to call for every
223 * element. This function
224 * takes 3 arguments (the element, the index and the array). The return
225 * value is ignored.
226 * @param {S=} opt_obj The object to be used as the value of 'this'
227 * within f.
228 * @template T,S
229 */
230goog.array.forEachRight = function(arr, f, opt_obj) {
231 var l = arr.length; // must be fixed during loop... see docs
232 var arr2 = goog.isString(arr) ? arr.split('') : arr;
233 for (var i = l - 1; i >= 0; --i) {
234 if (i in arr2) {
235 f.call(opt_obj, arr2[i], i, arr);
236 }
237 }
238};
239
240
241/**
242 * Calls a function for each element in an array, and if the function returns
243 * true adds the element to a new array.
244 *
245 * See {@link http://tinyurl.com/developer-mozilla-org-array-filter}
246 *
247 * @param {Array<T>|goog.array.ArrayLike} arr Array or array
248 * like object over which to iterate.
249 * @param {?function(this:S, T, number, ?):boolean} f The function to call for
250 * every element. This function
251 * takes 3 arguments (the element, the index and the array) and must
252 * return a Boolean. If the return value is true the element is added to the
253 * result array. If it is false the element is not included.
254 * @param {S=} opt_obj The object to be used as the value of 'this'
255 * within f.
256 * @return {!Array<T>} a new array in which only elements that passed the test
257 * are present.
258 * @template T,S
259 */
260goog.array.filter = goog.NATIVE_ARRAY_PROTOTYPES &&
261 (goog.array.ASSUME_NATIVE_FUNCTIONS ||
262 goog.array.ARRAY_PROTOTYPE_.filter) ?
263 function(arr, f, opt_obj) {
264 goog.asserts.assert(arr.length != null);
265
266 return goog.array.ARRAY_PROTOTYPE_.filter.call(arr, f, opt_obj);
267 } :
268 function(arr, f, opt_obj) {
269 var l = arr.length; // must be fixed during loop... see docs
270 var res = [];
271 var resLength = 0;
272 var arr2 = goog.isString(arr) ? arr.split('') : arr;
273 for (var i = 0; i < l; i++) {
274 if (i in arr2) {
275 var val = arr2[i]; // in case f mutates arr2
276 if (f.call(opt_obj, val, i, arr)) {
277 res[resLength++] = val;
278 }
279 }
280 }
281 return res;
282 };
283
284
285/**
286 * Calls a function for each element in an array and inserts the result into a
287 * new array.
288 *
289 * See {@link http://tinyurl.com/developer-mozilla-org-array-map}
290 *
291 * @param {Array<VALUE>|goog.array.ArrayLike} arr Array or array like object
292 * over which to iterate.
293 * @param {function(this:THIS, VALUE, number, ?): RESULT} f The function to call
294 * for every element. This function takes 3 arguments (the element,
295 * the index and the array) and should return something. The result will be
296 * inserted into a new array.
297 * @param {THIS=} opt_obj The object to be used as the value of 'this' within f.
298 * @return {!Array<RESULT>} a new array with the results from f.
299 * @template THIS, VALUE, RESULT
300 */
301goog.array.map = goog.NATIVE_ARRAY_PROTOTYPES &&
302 (goog.array.ASSUME_NATIVE_FUNCTIONS ||
303 goog.array.ARRAY_PROTOTYPE_.map) ?
304 function(arr, f, opt_obj) {
305 goog.asserts.assert(arr.length != null);
306
307 return goog.array.ARRAY_PROTOTYPE_.map.call(arr, f, opt_obj);
308 } :
309 function(arr, f, opt_obj) {
310 var l = arr.length; // must be fixed during loop... see docs
311 var res = new Array(l);
312 var arr2 = goog.isString(arr) ? arr.split('') : arr;
313 for (var i = 0; i < l; i++) {
314 if (i in arr2) {
315 res[i] = f.call(opt_obj, arr2[i], i, arr);
316 }
317 }
318 return res;
319 };
320
321
322/**
323 * Passes every element of an array into a function and accumulates the result.
324 *
325 * See {@link http://tinyurl.com/developer-mozilla-org-array-reduce}
326 *
327 * For example:
328 * var a = [1, 2, 3, 4];
329 * goog.array.reduce(a, function(r, v, i, arr) {return r + v;}, 0);
330 * returns 10
331 *
332 * @param {Array<T>|goog.array.ArrayLike} arr Array or array
333 * like object over which to iterate.
334 * @param {function(this:S, R, T, number, ?) : R} f The function to call for
335 * every element. This function
336 * takes 4 arguments (the function's previous result or the initial value,
337 * the value of the current array element, the current array index, and the
338 * array itself)
339 * function(previousValue, currentValue, index, array).
340 * @param {?} val The initial value to pass into the function on the first call.
341 * @param {S=} opt_obj The object to be used as the value of 'this'
342 * within f.
343 * @return {R} Result of evaluating f repeatedly across the values of the array.
344 * @template T,S,R
345 */
346goog.array.reduce = goog.NATIVE_ARRAY_PROTOTYPES &&
347 (goog.array.ASSUME_NATIVE_FUNCTIONS ||
348 goog.array.ARRAY_PROTOTYPE_.reduce) ?
349 function(arr, f, val, opt_obj) {
350 goog.asserts.assert(arr.length != null);
351 if (opt_obj) {
352 f = goog.bind(f, opt_obj);
353 }
354 return goog.array.ARRAY_PROTOTYPE_.reduce.call(arr, f, val);
355 } :
356 function(arr, f, val, opt_obj) {
357 var rval = val;
358 goog.array.forEach(arr, function(val, index) {
359 rval = f.call(opt_obj, rval, val, index, arr);
360 });
361 return rval;
362 };
363
364
365/**
366 * Passes every element of an array into a function and accumulates the result,
367 * starting from the last element and working towards the first.
368 *
369 * See {@link http://tinyurl.com/developer-mozilla-org-array-reduceright}
370 *
371 * For example:
372 * var a = ['a', 'b', 'c'];
373 * goog.array.reduceRight(a, function(r, v, i, arr) {return r + v;}, '');
374 * returns 'cba'
375 *
376 * @param {Array<T>|goog.array.ArrayLike} arr Array or array
377 * like object over which to iterate.
378 * @param {?function(this:S, R, T, number, ?) : R} f The function to call for
379 * every element. This function
380 * takes 4 arguments (the function's previous result or the initial value,
381 * the value of the current array element, the current array index, and the
382 * array itself)
383 * function(previousValue, currentValue, index, array).
384 * @param {?} val The initial value to pass into the function on the first call.
385 * @param {S=} opt_obj The object to be used as the value of 'this'
386 * within f.
387 * @return {R} Object returned as a result of evaluating f repeatedly across the
388 * values of the array.
389 * @template T,S,R
390 */
391goog.array.reduceRight = goog.NATIVE_ARRAY_PROTOTYPES &&
392 (goog.array.ASSUME_NATIVE_FUNCTIONS ||
393 goog.array.ARRAY_PROTOTYPE_.reduceRight) ?
394 function(arr, f, val, opt_obj) {
395 goog.asserts.assert(arr.length != null);
396 if (opt_obj) {
397 f = goog.bind(f, opt_obj);
398 }
399 return goog.array.ARRAY_PROTOTYPE_.reduceRight.call(arr, f, val);
400 } :
401 function(arr, f, val, opt_obj) {
402 var rval = val;
403 goog.array.forEachRight(arr, function(val, index) {
404 rval = f.call(opt_obj, rval, val, index, arr);
405 });
406 return rval;
407 };
408
409
410/**
411 * Calls f for each element of an array. If any call returns true, some()
412 * returns true (without checking the remaining elements). If all calls
413 * return false, some() returns false.
414 *
415 * See {@link http://tinyurl.com/developer-mozilla-org-array-some}
416 *
417 * @param {Array<T>|goog.array.ArrayLike} arr Array or array
418 * like object over which to iterate.
419 * @param {?function(this:S, T, number, ?) : boolean} f The function to call for
420 * for every element. This function takes 3 arguments (the element, the
421 * index and the array) and should return a boolean.
422 * @param {S=} opt_obj The object to be used as the value of 'this'
423 * within f.
424 * @return {boolean} true if any element passes the test.
425 * @template T,S
426 */
427goog.array.some = goog.NATIVE_ARRAY_PROTOTYPES &&
428 (goog.array.ASSUME_NATIVE_FUNCTIONS ||
429 goog.array.ARRAY_PROTOTYPE_.some) ?
430 function(arr, f, opt_obj) {
431 goog.asserts.assert(arr.length != null);
432
433 return goog.array.ARRAY_PROTOTYPE_.some.call(arr, f, opt_obj);
434 } :
435 function(arr, f, opt_obj) {
436 var l = arr.length; // must be fixed during loop... see docs
437 var arr2 = goog.isString(arr) ? arr.split('') : arr;
438 for (var i = 0; i < l; i++) {
439 if (i in arr2 && f.call(opt_obj, arr2[i], i, arr)) {
440 return true;
441 }
442 }
443 return false;
444 };
445
446
447/**
448 * Call f for each element of an array. If all calls return true, every()
449 * returns true. If any call returns false, every() returns false and
450 * does not continue to check the remaining elements.
451 *
452 * See {@link http://tinyurl.com/developer-mozilla-org-array-every}
453 *
454 * @param {Array<T>|goog.array.ArrayLike} arr Array or array
455 * like object over which to iterate.
456 * @param {?function(this:S, T, number, ?) : boolean} f The function to call for
457 * for every element. This function takes 3 arguments (the element, the
458 * index and the array) and should return a boolean.
459 * @param {S=} opt_obj The object to be used as the value of 'this'
460 * within f.
461 * @return {boolean} false if any element fails the test.
462 * @template T,S
463 */
464goog.array.every = goog.NATIVE_ARRAY_PROTOTYPES &&
465 (goog.array.ASSUME_NATIVE_FUNCTIONS ||
466 goog.array.ARRAY_PROTOTYPE_.every) ?
467 function(arr, f, opt_obj) {
468 goog.asserts.assert(arr.length != null);
469
470 return goog.array.ARRAY_PROTOTYPE_.every.call(arr, f, opt_obj);
471 } :
472 function(arr, f, opt_obj) {
473 var l = arr.length; // must be fixed during loop... see docs
474 var arr2 = goog.isString(arr) ? arr.split('') : arr;
475 for (var i = 0; i < l; i++) {
476 if (i in arr2 && !f.call(opt_obj, arr2[i], i, arr)) {
477 return false;
478 }
479 }
480 return true;
481 };
482
483
484/**
485 * Counts the array elements that fulfill the predicate, i.e. for which the
486 * callback function returns true. Skips holes in the array.
487 *
488 * @param {!(Array<T>|goog.array.ArrayLike)} arr Array or array like object
489 * over which to iterate.
490 * @param {function(this: S, T, number, ?): boolean} f The function to call for
491 * every element. Takes 3 arguments (the element, the index and the array).
492 * @param {S=} opt_obj The object to be used as the value of 'this' within f.
493 * @return {number} The number of the matching elements.
494 * @template T,S
495 */
496goog.array.count = function(arr, f, opt_obj) {
497 var count = 0;
498 goog.array.forEach(arr, function(element, index, arr) {
499 if (f.call(opt_obj, element, index, arr)) {
500 ++count;
501 }
502 }, opt_obj);
503 return count;
504};
505
506
507/**
508 * Search an array for the first element that satisfies a given condition and
509 * return that element.
510 * @param {Array<T>|goog.array.ArrayLike} arr Array or array
511 * like object over which to iterate.
512 * @param {?function(this:S, T, number, ?) : boolean} f The function to call
513 * for every element. This function takes 3 arguments (the element, the
514 * index and the array) and should return a boolean.
515 * @param {S=} opt_obj An optional "this" context for the function.
516 * @return {T|null} The first array element that passes the test, or null if no
517 * element is found.
518 * @template T,S
519 */
520goog.array.find = function(arr, f, opt_obj) {
521 var i = goog.array.findIndex(arr, f, opt_obj);
522 return i < 0 ? null : goog.isString(arr) ? arr.charAt(i) : arr[i];
523};
524
525
526/**
527 * Search an array for the first element that satisfies a given condition and
528 * return its index.
529 * @param {Array<T>|goog.array.ArrayLike} arr Array or array
530 * like object over which to iterate.
531 * @param {?function(this:S, T, number, ?) : boolean} f The function to call for
532 * every element. This function
533 * takes 3 arguments (the element, the index and the array) and should
534 * return a boolean.
535 * @param {S=} opt_obj An optional "this" context for the function.
536 * @return {number} The index of the first array element that passes the test,
537 * or -1 if no element is found.
538 * @template T,S
539 */
540goog.array.findIndex = function(arr, f, opt_obj) {
541 var l = arr.length; // must be fixed during loop... see docs
542 var arr2 = goog.isString(arr) ? arr.split('') : arr;
543 for (var i = 0; i < l; i++) {
544 if (i in arr2 && f.call(opt_obj, arr2[i], i, arr)) {
545 return i;
546 }
547 }
548 return -1;
549};
550
551
552/**
553 * Search an array (in reverse order) for the last element that satisfies a
554 * given condition and return that element.
555 * @param {Array<T>|goog.array.ArrayLike} arr Array or array
556 * like object over which to iterate.
557 * @param {?function(this:S, T, number, ?) : boolean} f The function to call
558 * for every element. This function
559 * takes 3 arguments (the element, the index and the array) and should
560 * return a boolean.
561 * @param {S=} opt_obj An optional "this" context for the function.
562 * @return {T|null} The last array element that passes the test, or null if no
563 * element is found.
564 * @template T,S
565 */
566goog.array.findRight = function(arr, f, opt_obj) {
567 var i = goog.array.findIndexRight(arr, f, opt_obj);
568 return i < 0 ? null : goog.isString(arr) ? arr.charAt(i) : arr[i];
569};
570
571
572/**
573 * Search an array (in reverse order) for the last element that satisfies a
574 * given condition and return its index.
575 * @param {Array<T>|goog.array.ArrayLike} arr Array or array
576 * like object over which to iterate.
577 * @param {?function(this:S, T, number, ?) : boolean} f The function to call
578 * for every element. This function
579 * takes 3 arguments (the element, the index and the array) and should
580 * return a boolean.
581 * @param {S=} opt_obj An optional "this" context for the function.
582 * @return {number} The index of the last array element that passes the test,
583 * or -1 if no element is found.
584 * @template T,S
585 */
586goog.array.findIndexRight = function(arr, f, opt_obj) {
587 var l = arr.length; // must be fixed during loop... see docs
588 var arr2 = goog.isString(arr) ? arr.split('') : arr;
589 for (var i = l - 1; i >= 0; i--) {
590 if (i in arr2 && f.call(opt_obj, arr2[i], i, arr)) {
591 return i;
592 }
593 }
594 return -1;
595};
596
597
598/**
599 * Whether the array contains the given object.
600 * @param {goog.array.ArrayLike} arr The array to test for the presence of the
601 * element.
602 * @param {*} obj The object for which to test.
603 * @return {boolean} true if obj is present.
604 */
605goog.array.contains = function(arr, obj) {
606 return goog.array.indexOf(arr, obj) >= 0;
607};
608
609
610/**
611 * Whether the array is empty.
612 * @param {goog.array.ArrayLike} arr The array to test.
613 * @return {boolean} true if empty.
614 */
615goog.array.isEmpty = function(arr) {
616 return arr.length == 0;
617};
618
619
620/**
621 * Clears the array.
622 * @param {goog.array.ArrayLike} arr Array or array like object to clear.
623 */
624goog.array.clear = function(arr) {
625 // For non real arrays we don't have the magic length so we delete the
626 // indices.
627 if (!goog.isArray(arr)) {
628 for (var i = arr.length - 1; i >= 0; i--) {
629 delete arr[i];
630 }
631 }
632 arr.length = 0;
633};
634
635
636/**
637 * Pushes an item into an array, if it's not already in the array.
638 * @param {Array<T>} arr Array into which to insert the item.
639 * @param {T} obj Value to add.
640 * @template T
641 */
642goog.array.insert = function(arr, obj) {
643 if (!goog.array.contains(arr, obj)) {
644 arr.push(obj);
645 }
646};
647
648
649/**
650 * Inserts an object at the given index of the array.
651 * @param {goog.array.ArrayLike} arr The array to modify.
652 * @param {*} obj The object to insert.
653 * @param {number=} opt_i The index at which to insert the object. If omitted,
654 * treated as 0. A negative index is counted from the end of the array.
655 */
656goog.array.insertAt = function(arr, obj, opt_i) {
657 goog.array.splice(arr, opt_i, 0, obj);
658};
659
660
661/**
662 * Inserts at the given index of the array, all elements of another array.
663 * @param {goog.array.ArrayLike} arr The array to modify.
664 * @param {goog.array.ArrayLike} elementsToAdd The array of elements to add.
665 * @param {number=} opt_i The index at which to insert the object. If omitted,
666 * treated as 0. A negative index is counted from the end of the array.
667 */
668goog.array.insertArrayAt = function(arr, elementsToAdd, opt_i) {
669 goog.partial(goog.array.splice, arr, opt_i, 0).apply(null, elementsToAdd);
670};
671
672
673/**
674 * Inserts an object into an array before a specified object.
675 * @param {Array<T>} arr The array to modify.
676 * @param {T} obj The object to insert.
677 * @param {T=} opt_obj2 The object before which obj should be inserted. If obj2
678 * is omitted or not found, obj is inserted at the end of the array.
679 * @template T
680 */
681goog.array.insertBefore = function(arr, obj, opt_obj2) {
682 var i;
683 if (arguments.length == 2 || (i = goog.array.indexOf(arr, opt_obj2)) < 0) {
684 arr.push(obj);
685 } else {
686 goog.array.insertAt(arr, obj, i);
687 }
688};
689
690
691/**
692 * Removes the first occurrence of a particular value from an array.
693 * @param {Array<T>|goog.array.ArrayLike} arr Array from which to remove
694 * value.
695 * @param {T} obj Object to remove.
696 * @return {boolean} True if an element was removed.
697 * @template T
698 */
699goog.array.remove = function(arr, obj) {
700 var i = goog.array.indexOf(arr, obj);
701 var rv;
702 if ((rv = i >= 0)) {
703 goog.array.removeAt(arr, i);
704 }
705 return rv;
706};
707
708
709/**
710 * Removes from an array the element at index i
711 * @param {goog.array.ArrayLike} arr Array or array like object from which to
712 * remove value.
713 * @param {number} i The index to remove.
714 * @return {boolean} True if an element was removed.
715 */
716goog.array.removeAt = function(arr, i) {
717 goog.asserts.assert(arr.length != null);
718
719 // use generic form of splice
720 // splice returns the removed items and if successful the length of that
721 // will be 1
722 return goog.array.ARRAY_PROTOTYPE_.splice.call(arr, i, 1).length == 1;
723};
724
725
726/**
727 * Removes the first value that satisfies the given condition.
728 * @param {Array<T>|goog.array.ArrayLike} arr Array or array
729 * like object over which to iterate.
730 * @param {?function(this:S, T, number, ?) : boolean} f The function to call
731 * for every element. This function
732 * takes 3 arguments (the element, the index and the array) and should
733 * return a boolean.
734 * @param {S=} opt_obj An optional "this" context for the function.
735 * @return {boolean} True if an element was removed.
736 * @template T,S
737 */
738goog.array.removeIf = function(arr, f, opt_obj) {
739 var i = goog.array.findIndex(arr, f, opt_obj);
740 if (i >= 0) {
741 goog.array.removeAt(arr, i);
742 return true;
743 }
744 return false;
745};
746
747
748/**
749 * Removes all values that satisfy the given condition.
750 * @param {Array<T>|goog.array.ArrayLike} arr Array or array
751 * like object over which to iterate.
752 * @param {?function(this:S, T, number, ?) : boolean} f The function to call
753 * for every element. This function
754 * takes 3 arguments (the element, the index and the array) and should
755 * return a boolean.
756 * @param {S=} opt_obj An optional "this" context for the function.
757 * @return {number} The number of items removed
758 * @template T,S
759 */
760goog.array.removeAllIf = function(arr, f, opt_obj) {
761 var removedCount = 0;
762 goog.array.forEachRight(arr, function(val, index) {
763 if (f.call(opt_obj, val, index, arr)) {
764 if (goog.array.removeAt(arr, index)) {
765 removedCount++;
766 }
767 }
768 });
769 return removedCount;
770};
771
772
773/**
774 * Returns a new array that is the result of joining the arguments. If arrays
775 * are passed then their items are added, however, if non-arrays are passed they
776 * will be added to the return array as is.
777 *
778 * Note that ArrayLike objects will be added as is, rather than having their
779 * items added.
780 *
781 * goog.array.concat([1, 2], [3, 4]) -> [1, 2, 3, 4]
782 * goog.array.concat(0, [1, 2]) -> [0, 1, 2]
783 * goog.array.concat([1, 2], null) -> [1, 2, null]
784 *
785 * There is bug in all current versions of IE (6, 7 and 8) where arrays created
786 * in an iframe become corrupted soon (not immediately) after the iframe is
787 * destroyed. This is common if loading data via goog.net.IframeIo, for example.
788 * This corruption only affects the concat method which will start throwing
789 * Catastrophic Errors (#-2147418113).
790 *
791 * See http://endoflow.com/scratch/corrupted-arrays.html for a test case.
792 *
793 * Internally goog.array should use this, so that all methods will continue to
794 * work on these broken array objects.
795 *
796 * @param {...*} var_args Items to concatenate. Arrays will have each item
797 * added, while primitives and objects will be added as is.
798 * @return {!Array<?>} The new resultant array.
799 */
800goog.array.concat = function(var_args) {
801 return goog.array.ARRAY_PROTOTYPE_.concat.apply(
802 goog.array.ARRAY_PROTOTYPE_, arguments);
803};
804
805
806/**
807 * Returns a new array that contains the contents of all the arrays passed.
808 * @param {...!Array<T>} var_args
809 * @return {!Array<T>}
810 * @template T
811 */
812goog.array.join = function(var_args) {
813 return goog.array.ARRAY_PROTOTYPE_.concat.apply(
814 goog.array.ARRAY_PROTOTYPE_, arguments);
815};
816
817
818/**
819 * Converts an object to an array.
820 * @param {Array<T>|goog.array.ArrayLike} object The object to convert to an
821 * array.
822 * @return {!Array<T>} The object converted into an array. If object has a
823 * length property, every property indexed with a non-negative number
824 * less than length will be included in the result. If object does not
825 * have a length property, an empty array will be returned.
826 * @template T
827 */
828goog.array.toArray = function(object) {
829 var length = object.length;
830
831 // If length is not a number the following it false. This case is kept for
832 // backwards compatibility since there are callers that pass objects that are
833 // not array like.
834 if (length > 0) {
835 var rv = new Array(length);
836 for (var i = 0; i < length; i++) {
837 rv[i] = object[i];
838 }
839 return rv;
840 }
841 return [];
842};
843
844
845/**
846 * Does a shallow copy of an array.
847 * @param {Array<T>|goog.array.ArrayLike} arr Array or array-like object to
848 * clone.
849 * @return {!Array<T>} Clone of the input array.
850 * @template T
851 */
852goog.array.clone = goog.array.toArray;
853
854
855/**
856 * Extends an array with another array, element, or "array like" object.
857 * This function operates 'in-place', it does not create a new Array.
858 *
859 * Example:
860 * var a = [];
861 * goog.array.extend(a, [0, 1]);
862 * a; // [0, 1]
863 * goog.array.extend(a, 2);
864 * a; // [0, 1, 2]
865 *
866 * @param {Array<VALUE>} arr1 The array to modify.
867 * @param {...(Array<VALUE>|VALUE)} var_args The elements or arrays of elements
868 * to add to arr1.
869 * @template VALUE
870 */
871goog.array.extend = function(arr1, var_args) {
872 for (var i = 1; i < arguments.length; i++) {
873 var arr2 = arguments[i];
874 if (goog.isArrayLike(arr2)) {
875 var len1 = arr1.length || 0;
876 var len2 = arr2.length || 0;
877 arr1.length = len1 + len2;
878 for (var j = 0; j < len2; j++) {
879 arr1[len1 + j] = arr2[j];
880 }
881 } else {
882 arr1.push(arr2);
883 }
884 }
885};
886
887
888/**
889 * Adds or removes elements from an array. This is a generic version of Array
890 * splice. This means that it might work on other objects similar to arrays,
891 * such as the arguments object.
892 *
893 * @param {Array<T>|goog.array.ArrayLike} arr The array to modify.
894 * @param {number|undefined} index The index at which to start changing the
895 * array. If not defined, treated as 0.
896 * @param {number} howMany How many elements to remove (0 means no removal. A
897 * value below 0 is treated as zero and so is any other non number. Numbers
898 * are floored).
899 * @param {...T} var_args Optional, additional elements to insert into the
900 * array.
901 * @return {!Array<T>} the removed elements.
902 * @template T
903 */
904goog.array.splice = function(arr, index, howMany, var_args) {
905 goog.asserts.assert(arr.length != null);
906
907 return goog.array.ARRAY_PROTOTYPE_.splice.apply(
908 arr, goog.array.slice(arguments, 1));
909};
910
911
912/**
913 * Returns a new array from a segment of an array. This is a generic version of
914 * Array slice. This means that it might work on other objects similar to
915 * arrays, such as the arguments object.
916 *
917 * @param {Array<T>|goog.array.ArrayLike} arr The array from
918 * which to copy a segment.
919 * @param {number} start The index of the first element to copy.
920 * @param {number=} opt_end The index after the last element to copy.
921 * @return {!Array<T>} A new array containing the specified segment of the
922 * original array.
923 * @template T
924 */
925goog.array.slice = function(arr, start, opt_end) {
926 goog.asserts.assert(arr.length != null);
927
928 // passing 1 arg to slice is not the same as passing 2 where the second is
929 // null or undefined (in that case the second argument is treated as 0).
930 // we could use slice on the arguments object and then use apply instead of
931 // testing the length
932 if (arguments.length <= 2) {
933 return goog.array.ARRAY_PROTOTYPE_.slice.call(arr, start);
934 } else {
935 return goog.array.ARRAY_PROTOTYPE_.slice.call(arr, start, opt_end);
936 }
937};
938
939
940/**
941 * Removes all duplicates from an array (retaining only the first
942 * occurrence of each array element). This function modifies the
943 * array in place and doesn't change the order of the non-duplicate items.
944 *
945 * For objects, duplicates are identified as having the same unique ID as
946 * defined by {@link goog.getUid}.
947 *
948 * Alternatively you can specify a custom hash function that returns a unique
949 * value for each item in the array it should consider unique.
950 *
951 * Runtime: N,
952 * Worstcase space: 2N (no dupes)
953 *
954 * @param {Array<T>|goog.array.ArrayLike} arr The array from which to remove
955 * duplicates.
956 * @param {Array=} opt_rv An optional array in which to return the results,
957 * instead of performing the removal inplace. If specified, the original
958 * array will remain unchanged.
959 * @param {function(T):string=} opt_hashFn An optional function to use to
960 * apply to every item in the array. This function should return a unique
961 * value for each item in the array it should consider unique.
962 * @template T
963 */
964goog.array.removeDuplicates = function(arr, opt_rv, opt_hashFn) {
965 var returnArray = opt_rv || arr;
966 var defaultHashFn = function(item) {
967 // Prefix each type with a single character representing the type to
968 // prevent conflicting keys (e.g. true and 'true').
969 return goog.isObject(current) ? 'o' + goog.getUid(current) :
970 (typeof current).charAt(0) + current;
971 };
972 var hashFn = opt_hashFn || defaultHashFn;
973
974 var seen = {}, cursorInsert = 0, cursorRead = 0;
975 while (cursorRead < arr.length) {
976 var current = arr[cursorRead++];
977 var key = hashFn(current);
978 if (!Object.prototype.hasOwnProperty.call(seen, key)) {
979 seen[key] = true;
980 returnArray[cursorInsert++] = current;
981 }
982 }
983 returnArray.length = cursorInsert;
984};
985
986
987/**
988 * Searches the specified array for the specified target using the binary
989 * search algorithm. If no opt_compareFn is specified, elements are compared
990 * using <code>goog.array.defaultCompare</code>, which compares the elements
991 * using the built in < and > operators. This will produce the expected
992 * behavior for homogeneous arrays of String(s) and Number(s). The array
993 * specified <b>must</b> be sorted in ascending order (as defined by the
994 * comparison function). If the array is not sorted, results are undefined.
995 * If the array contains multiple instances of the specified target value, any
996 * of these instances may be found.
997 *
998 * Runtime: O(log n)
999 *
1000 * @param {Array<VALUE>|goog.array.ArrayLike} arr The array to be searched.
1001 * @param {TARGET} target The sought value.
1002 * @param {function(TARGET, VALUE): number=} opt_compareFn Optional comparison
1003 * function by which the array is ordered. Should take 2 arguments to
1004 * compare, and return a negative number, zero, or a positive number
1005 * depending on whether the first argument is less than, equal to, or
1006 * greater than the second.
1007 * @return {number} Lowest index of the target value if found, otherwise
1008 * (-(insertion point) - 1). The insertion point is where the value should
1009 * be inserted into arr to preserve the sorted property. Return value >= 0
1010 * iff target is found.
1011 * @template TARGET, VALUE
1012 */
1013goog.array.binarySearch = function(arr, target, opt_compareFn) {
1014 return goog.array.binarySearch_(arr,
1015 opt_compareFn || goog.array.defaultCompare, false /* isEvaluator */,
1016 target);
1017};
1018
1019
1020/**
1021 * Selects an index in the specified array using the binary search algorithm.
1022 * The evaluator receives an element and determines whether the desired index
1023 * is before, at, or after it. The evaluator must be consistent (formally,
1024 * goog.array.map(goog.array.map(arr, evaluator, opt_obj), goog.math.sign)
1025 * must be monotonically non-increasing).
1026 *
1027 * Runtime: O(log n)
1028 *
1029 * @param {Array<VALUE>|goog.array.ArrayLike} arr The array to be searched.
1030 * @param {function(this:THIS, VALUE, number, ?): number} evaluator
1031 * Evaluator function that receives 3 arguments (the element, the index and
1032 * the array). Should return a negative number, zero, or a positive number
1033 * depending on whether the desired index is before, at, or after the
1034 * element passed to it.
1035 * @param {THIS=} opt_obj The object to be used as the value of 'this'
1036 * within evaluator.
1037 * @return {number} Index of the leftmost element matched by the evaluator, if
1038 * such exists; otherwise (-(insertion point) - 1). The insertion point is
1039 * the index of the first element for which the evaluator returns negative,
1040 * or arr.length if no such element exists. The return value is non-negative
1041 * iff a match is found.
1042 * @template THIS, VALUE
1043 */
1044goog.array.binarySelect = function(arr, evaluator, opt_obj) {
1045 return goog.array.binarySearch_(arr, evaluator, true /* isEvaluator */,
1046 undefined /* opt_target */, opt_obj);
1047};
1048
1049
1050/**
1051 * Implementation of a binary search algorithm which knows how to use both
1052 * comparison functions and evaluators. If an evaluator is provided, will call
1053 * the evaluator with the given optional data object, conforming to the
1054 * interface defined in binarySelect. Otherwise, if a comparison function is
1055 * provided, will call the comparison function against the given data object.
1056 *
1057 * This implementation purposefully does not use goog.bind or goog.partial for
1058 * performance reasons.
1059 *
1060 * Runtime: O(log n)
1061 *
1062 * @param {Array<VALUE>|goog.array.ArrayLike} arr The array to be searched.
1063 * @param {function(TARGET, VALUE): number|
1064 * function(this:THIS, VALUE, number, ?): number} compareFn Either an
1065 * evaluator or a comparison function, as defined by binarySearch
1066 * and binarySelect above.
1067 * @param {boolean} isEvaluator Whether the function is an evaluator or a
1068 * comparison function.
1069 * @param {TARGET=} opt_target If the function is a comparison function, then
1070 * this is the target to binary search for.
1071 * @param {THIS=} opt_selfObj If the function is an evaluator, this is an
1072 * optional this object for the evaluator.
1073 * @return {number} Lowest index of the target value if found, otherwise
1074 * (-(insertion point) - 1). The insertion point is where the value should
1075 * be inserted into arr to preserve the sorted property. Return value >= 0
1076 * iff target is found.
1077 * @template THIS, VALUE, TARGET
1078 * @private
1079 */
1080goog.array.binarySearch_ = function(arr, compareFn, isEvaluator, opt_target,
1081 opt_selfObj) {
1082 var left = 0; // inclusive
1083 var right = arr.length; // exclusive
1084 var found;
1085 while (left < right) {
1086 var middle = (left + right) >> 1;
1087 var compareResult;
1088 if (isEvaluator) {
1089 compareResult = compareFn.call(opt_selfObj, arr[middle], middle, arr);
1090 } else {
1091 compareResult = compareFn(opt_target, arr[middle]);
1092 }
1093 if (compareResult > 0) {
1094 left = middle + 1;
1095 } else {
1096 right = middle;
1097 // We are looking for the lowest index so we can't return immediately.
1098 found = !compareResult;
1099 }
1100 }
1101 // left is the index if found, or the insertion point otherwise.
1102 // ~left is a shorthand for -left - 1.
1103 return found ? left : ~left;
1104};
1105
1106
1107/**
1108 * Sorts the specified array into ascending order. If no opt_compareFn is
1109 * specified, elements are compared using
1110 * <code>goog.array.defaultCompare</code>, which compares the elements using
1111 * the built in < and > operators. This will produce the expected behavior
1112 * for homogeneous arrays of String(s) and Number(s), unlike the native sort,
1113 * but will give unpredictable results for heterogenous lists of strings and
1114 * numbers with different numbers of digits.
1115 *
1116 * This sort is not guaranteed to be stable.
1117 *
1118 * Runtime: Same as <code>Array.prototype.sort</code>
1119 *
1120 * @param {Array<T>} arr The array to be sorted.
1121 * @param {?function(T,T):number=} opt_compareFn Optional comparison
1122 * function by which the
1123 * array is to be ordered. Should take 2 arguments to compare, and return a
1124 * negative number, zero, or a positive number depending on whether the
1125 * first argument is less than, equal to, or greater than the second.
1126 * @template T
1127 */
1128goog.array.sort = function(arr, opt_compareFn) {
1129 // TODO(arv): Update type annotation since null is not accepted.
1130 arr.sort(opt_compareFn || goog.array.defaultCompare);
1131};
1132
1133
1134/**
1135 * Sorts the specified array into ascending order in a stable way. If no
1136 * opt_compareFn is specified, elements are compared using
1137 * <code>goog.array.defaultCompare</code>, which compares the elements using
1138 * the built in < and > operators. This will produce the expected behavior
1139 * for homogeneous arrays of String(s) and Number(s).
1140 *
1141 * Runtime: Same as <code>Array.prototype.sort</code>, plus an additional
1142 * O(n) overhead of copying the array twice.
1143 *
1144 * @param {Array<T>} arr The array to be sorted.
1145 * @param {?function(T, T): number=} opt_compareFn Optional comparison function
1146 * by which the array is to be ordered. Should take 2 arguments to compare,
1147 * and return a negative number, zero, or a positive number depending on
1148 * whether the first argument is less than, equal to, or greater than the
1149 * second.
1150 * @template T
1151 */
1152goog.array.stableSort = function(arr, opt_compareFn) {
1153 for (var i = 0; i < arr.length; i++) {
1154 arr[i] = {index: i, value: arr[i]};
1155 }
1156 var valueCompareFn = opt_compareFn || goog.array.defaultCompare;
1157 function stableCompareFn(obj1, obj2) {
1158 return valueCompareFn(obj1.value, obj2.value) || obj1.index - obj2.index;
1159 };
1160 goog.array.sort(arr, stableCompareFn);
1161 for (var i = 0; i < arr.length; i++) {
1162 arr[i] = arr[i].value;
1163 }
1164};
1165
1166
1167/**
1168 * Sort the specified array into ascending order based on item keys
1169 * returned by the specified key function.
1170 * If no opt_compareFn is specified, the keys are compared in ascending order
1171 * using <code>goog.array.defaultCompare</code>.
1172 *
1173 * Runtime: O(S(f(n)), where S is runtime of <code>goog.array.sort</code>
1174 * and f(n) is runtime of the key function.
1175 *
1176 * @param {Array<T>} arr The array to be sorted.
1177 * @param {function(T): K} keyFn Function taking array element and returning
1178 * a key used for sorting this element.
1179 * @param {?function(K, K): number=} opt_compareFn Optional comparison function
1180 * by which the keys are to be ordered. Should take 2 arguments to compare,
1181 * and return a negative number, zero, or a positive number depending on
1182 * whether the first argument is less than, equal to, or greater than the
1183 * second.
1184 * @template T
1185 * @template K
1186 */
1187goog.array.sortByKey = function(arr, keyFn, opt_compareFn) {
1188 var keyCompareFn = opt_compareFn || goog.array.defaultCompare;
1189 goog.array.sort(arr, function(a, b) {
1190 return keyCompareFn(keyFn(a), keyFn(b));
1191 });
1192};
1193
1194
1195/**
1196 * Sorts an array of objects by the specified object key and compare
1197 * function. If no compare function is provided, the key values are
1198 * compared in ascending order using <code>goog.array.defaultCompare</code>.
1199 * This won't work for keys that get renamed by the compiler. So use
1200 * {'foo': 1, 'bar': 2} rather than {foo: 1, bar: 2}.
1201 * @param {Array<Object>} arr An array of objects to sort.
1202 * @param {string} key The object key to sort by.
1203 * @param {Function=} opt_compareFn The function to use to compare key
1204 * values.
1205 */
1206goog.array.sortObjectsByKey = function(arr, key, opt_compareFn) {
1207 goog.array.sortByKey(arr,
1208 function(obj) { return obj[key]; },
1209 opt_compareFn);
1210};
1211
1212
1213/**
1214 * Tells if the array is sorted.
1215 * @param {!Array<T>} arr The array.
1216 * @param {?function(T,T):number=} opt_compareFn Function to compare the
1217 * array elements.
1218 * Should take 2 arguments to compare, and return a negative number, zero,
1219 * or a positive number depending on whether the first argument is less
1220 * than, equal to, or greater than the second.
1221 * @param {boolean=} opt_strict If true no equal elements are allowed.
1222 * @return {boolean} Whether the array is sorted.
1223 * @template T
1224 */
1225goog.array.isSorted = function(arr, opt_compareFn, opt_strict) {
1226 var compare = opt_compareFn || goog.array.defaultCompare;
1227 for (var i = 1; i < arr.length; i++) {
1228 var compareResult = compare(arr[i - 1], arr[i]);
1229 if (compareResult > 0 || compareResult == 0 && opt_strict) {
1230 return false;
1231 }
1232 }
1233 return true;
1234};
1235
1236
1237/**
1238 * Compares two arrays for equality. Two arrays are considered equal if they
1239 * have the same length and their corresponding elements are equal according to
1240 * the comparison function.
1241 *
1242 * @param {goog.array.ArrayLike} arr1 The first array to compare.
1243 * @param {goog.array.ArrayLike} arr2 The second array to compare.
1244 * @param {Function=} opt_equalsFn Optional comparison function.
1245 * Should take 2 arguments to compare, and return true if the arguments
1246 * are equal. Defaults to {@link goog.array.defaultCompareEquality} which
1247 * compares the elements using the built-in '===' operator.
1248 * @return {boolean} Whether the two arrays are equal.
1249 */
1250goog.array.equals = function(arr1, arr2, opt_equalsFn) {
1251 if (!goog.isArrayLike(arr1) || !goog.isArrayLike(arr2) ||
1252 arr1.length != arr2.length) {
1253 return false;
1254 }
1255 var l = arr1.length;
1256 var equalsFn = opt_equalsFn || goog.array.defaultCompareEquality;
1257 for (var i = 0; i < l; i++) {
1258 if (!equalsFn(arr1[i], arr2[i])) {
1259 return false;
1260 }
1261 }
1262 return true;
1263};
1264
1265
1266/**
1267 * 3-way array compare function.
1268 * @param {!Array<VALUE>|!goog.array.ArrayLike} arr1 The first array to
1269 * compare.
1270 * @param {!Array<VALUE>|!goog.array.ArrayLike} arr2 The second array to
1271 * compare.
1272 * @param {function(VALUE, VALUE): number=} opt_compareFn Optional comparison
1273 * function by which the array is to be ordered. Should take 2 arguments to
1274 * compare, and return a negative number, zero, or a positive number
1275 * depending on whether the first argument is less than, equal to, or
1276 * greater than the second.
1277 * @return {number} Negative number, zero, or a positive number depending on
1278 * whether the first argument is less than, equal to, or greater than the
1279 * second.
1280 * @template VALUE
1281 */
1282goog.array.compare3 = function(arr1, arr2, opt_compareFn) {
1283 var compare = opt_compareFn || goog.array.defaultCompare;
1284 var l = Math.min(arr1.length, arr2.length);
1285 for (var i = 0; i < l; i++) {
1286 var result = compare(arr1[i], arr2[i]);
1287 if (result != 0) {
1288 return result;
1289 }
1290 }
1291 return goog.array.defaultCompare(arr1.length, arr2.length);
1292};
1293
1294
1295/**
1296 * Compares its two arguments for order, using the built in < and >
1297 * operators.
1298 * @param {VALUE} a The first object to be compared.
1299 * @param {VALUE} b The second object to be compared.
1300 * @return {number} A negative number, zero, or a positive number as the first
1301 * argument is less than, equal to, or greater than the second,
1302 * respectively.
1303 * @template VALUE
1304 */
1305goog.array.defaultCompare = function(a, b) {
1306 return a > b ? 1 : a < b ? -1 : 0;
1307};
1308
1309
1310/**
1311 * Compares its two arguments for inverse order, using the built in < and >
1312 * operators.
1313 * @param {VALUE} a The first object to be compared.
1314 * @param {VALUE} b The second object to be compared.
1315 * @return {number} A negative number, zero, or a positive number as the first
1316 * argument is greater than, equal to, or less than the second,
1317 * respectively.
1318 * @template VALUE
1319 */
1320goog.array.inverseDefaultCompare = function(a, b) {
1321 return -goog.array.defaultCompare(a, b);
1322};
1323
1324
1325/**
1326 * Compares its two arguments for equality, using the built in === operator.
1327 * @param {*} a The first object to compare.
1328 * @param {*} b The second object to compare.
1329 * @return {boolean} True if the two arguments are equal, false otherwise.
1330 */
1331goog.array.defaultCompareEquality = function(a, b) {
1332 return a === b;
1333};
1334
1335
1336/**
1337 * Inserts a value into a sorted array. The array is not modified if the
1338 * value is already present.
1339 * @param {Array<VALUE>|goog.array.ArrayLike} array The array to modify.
1340 * @param {VALUE} value The object to insert.
1341 * @param {function(VALUE, VALUE): number=} opt_compareFn Optional comparison
1342 * function by which the array is ordered. Should take 2 arguments to
1343 * compare, and return a negative number, zero, or a positive number
1344 * depending on whether the first argument is less than, equal to, or
1345 * greater than the second.
1346 * @return {boolean} True if an element was inserted.
1347 * @template VALUE
1348 */
1349goog.array.binaryInsert = function(array, value, opt_compareFn) {
1350 var index = goog.array.binarySearch(array, value, opt_compareFn);
1351 if (index < 0) {
1352 goog.array.insertAt(array, value, -(index + 1));
1353 return true;
1354 }
1355 return false;
1356};
1357
1358
1359/**
1360 * Removes a value from a sorted array.
1361 * @param {!Array<VALUE>|!goog.array.ArrayLike} array The array to modify.
1362 * @param {VALUE} value The object to remove.
1363 * @param {function(VALUE, VALUE): number=} opt_compareFn Optional comparison
1364 * function by which the array is ordered. Should take 2 arguments to
1365 * compare, and return a negative number, zero, or a positive number
1366 * depending on whether the first argument is less than, equal to, or
1367 * greater than the second.
1368 * @return {boolean} True if an element was removed.
1369 * @template VALUE
1370 */
1371goog.array.binaryRemove = function(array, value, opt_compareFn) {
1372 var index = goog.array.binarySearch(array, value, opt_compareFn);
1373 return (index >= 0) ? goog.array.removeAt(array, index) : false;
1374};
1375
1376
1377/**
1378 * Splits an array into disjoint buckets according to a splitting function.
1379 * @param {Array<T>} array The array.
1380 * @param {function(this:S, T,number,Array<T>):?} sorter Function to call for
1381 * every element. This takes 3 arguments (the element, the index and the
1382 * array) and must return a valid object key (a string, number, etc), or
1383 * undefined, if that object should not be placed in a bucket.
1384 * @param {S=} opt_obj The object to be used as the value of 'this' within
1385 * sorter.
1386 * @return {!Object} An object, with keys being all of the unique return values
1387 * of sorter, and values being arrays containing the items for
1388 * which the splitter returned that key.
1389 * @template T,S
1390 */
1391goog.array.bucket = function(array, sorter, opt_obj) {
1392 var buckets = {};
1393
1394 for (var i = 0; i < array.length; i++) {
1395 var value = array[i];
1396 var key = sorter.call(opt_obj, value, i, array);
1397 if (goog.isDef(key)) {
1398 // Push the value to the right bucket, creating it if necessary.
1399 var bucket = buckets[key] || (buckets[key] = []);
1400 bucket.push(value);
1401 }
1402 }
1403
1404 return buckets;
1405};
1406
1407
1408/**
1409 * Creates a new object built from the provided array and the key-generation
1410 * function.
1411 * @param {Array<T>|goog.array.ArrayLike} arr Array or array like object over
1412 * which to iterate whose elements will be the values in the new object.
1413 * @param {?function(this:S, T, number, ?) : string} keyFunc The function to
1414 * call for every element. This function takes 3 arguments (the element, the
1415 * index and the array) and should return a string that will be used as the
1416 * key for the element in the new object. If the function returns the same
1417 * key for more than one element, the value for that key is
1418 * implementation-defined.
1419 * @param {S=} opt_obj The object to be used as the value of 'this'
1420 * within keyFunc.
1421 * @return {!Object<T>} The new object.
1422 * @template T,S
1423 */
1424goog.array.toObject = function(arr, keyFunc, opt_obj) {
1425 var ret = {};
1426 goog.array.forEach(arr, function(element, index) {
1427 ret[keyFunc.call(opt_obj, element, index, arr)] = element;
1428 });
1429 return ret;
1430};
1431
1432
1433/**
1434 * Creates a range of numbers in an arithmetic progression.
1435 *
1436 * Range takes 1, 2, or 3 arguments:
1437 * <pre>
1438 * range(5) is the same as range(0, 5, 1) and produces [0, 1, 2, 3, 4]
1439 * range(2, 5) is the same as range(2, 5, 1) and produces [2, 3, 4]
1440 * range(-2, -5, -1) produces [-2, -3, -4]
1441 * range(-2, -5, 1) produces [], since stepping by 1 wouldn't ever reach -5.
1442 * </pre>
1443 *
1444 * @param {number} startOrEnd The starting value of the range if an end argument
1445 * is provided. Otherwise, the start value is 0, and this is the end value.
1446 * @param {number=} opt_end The optional end value of the range.
1447 * @param {number=} opt_step The step size between range values. Defaults to 1
1448 * if opt_step is undefined or 0.
1449 * @return {!Array<number>} An array of numbers for the requested range. May be
1450 * an empty array if adding the step would not converge toward the end
1451 * value.
1452 */
1453goog.array.range = function(startOrEnd, opt_end, opt_step) {
1454 var array = [];
1455 var start = 0;
1456 var end = startOrEnd;
1457 var step = opt_step || 1;
1458 if (opt_end !== undefined) {
1459 start = startOrEnd;
1460 end = opt_end;
1461 }
1462
1463 if (step * (end - start) < 0) {
1464 // Sign mismatch: start + step will never reach the end value.
1465 return [];
1466 }
1467
1468 if (step > 0) {
1469 for (var i = start; i < end; i += step) {
1470 array.push(i);
1471 }
1472 } else {
1473 for (var i = start; i > end; i += step) {
1474 array.push(i);
1475 }
1476 }
1477 return array;
1478};
1479
1480
1481/**
1482 * Returns an array consisting of the given value repeated N times.
1483 *
1484 * @param {VALUE} value The value to repeat.
1485 * @param {number} n The repeat count.
1486 * @return {!Array<VALUE>} An array with the repeated value.
1487 * @template VALUE
1488 */
1489goog.array.repeat = function(value, n) {
1490 var array = [];
1491 for (var i = 0; i < n; i++) {
1492 array[i] = value;
1493 }
1494 return array;
1495};
1496
1497
1498/**
1499 * Returns an array consisting of every argument with all arrays
1500 * expanded in-place recursively.
1501 *
1502 * @param {...*} var_args The values to flatten.
1503 * @return {!Array<?>} An array containing the flattened values.
1504 */
1505goog.array.flatten = function(var_args) {
1506 var CHUNK_SIZE = 8192;
1507
1508 var result = [];
1509 for (var i = 0; i < arguments.length; i++) {
1510 var element = arguments[i];
1511 if (goog.isArray(element)) {
1512 for (var c = 0; c < element.length; c += CHUNK_SIZE) {
1513 var chunk = goog.array.slice(element, c, c + CHUNK_SIZE);
1514 var recurseResult = goog.array.flatten.apply(null, chunk);
1515 for (var r = 0; r < recurseResult.length; r++) {
1516 result.push(recurseResult[r]);
1517 }
1518 }
1519 } else {
1520 result.push(element);
1521 }
1522 }
1523 return result;
1524};
1525
1526
1527/**
1528 * Rotates an array in-place. After calling this method, the element at
1529 * index i will be the element previously at index (i - n) %
1530 * array.length, for all values of i between 0 and array.length - 1,
1531 * inclusive.
1532 *
1533 * For example, suppose list comprises [t, a, n, k, s]. After invoking
1534 * rotate(array, 1) (or rotate(array, -4)), array will comprise [s, t, a, n, k].
1535 *
1536 * @param {!Array<T>} array The array to rotate.
1537 * @param {number} n The amount to rotate.
1538 * @return {!Array<T>} The array.
1539 * @template T
1540 */
1541goog.array.rotate = function(array, n) {
1542 goog.asserts.assert(array.length != null);
1543
1544 if (array.length) {
1545 n %= array.length;
1546 if (n > 0) {
1547 goog.array.ARRAY_PROTOTYPE_.unshift.apply(array, array.splice(-n, n));
1548 } else if (n < 0) {
1549 goog.array.ARRAY_PROTOTYPE_.push.apply(array, array.splice(0, -n));
1550 }
1551 }
1552 return array;
1553};
1554
1555
1556/**
1557 * Moves one item of an array to a new position keeping the order of the rest
1558 * of the items. Example use case: keeping a list of JavaScript objects
1559 * synchronized with the corresponding list of DOM elements after one of the
1560 * elements has been dragged to a new position.
1561 * @param {!(Array|Arguments|{length:number})} arr The array to modify.
1562 * @param {number} fromIndex Index of the item to move between 0 and
1563 * {@code arr.length - 1}.
1564 * @param {number} toIndex Target index between 0 and {@code arr.length - 1}.
1565 */
1566goog.array.moveItem = function(arr, fromIndex, toIndex) {
1567 goog.asserts.assert(fromIndex >= 0 && fromIndex < arr.length);
1568 goog.asserts.assert(toIndex >= 0 && toIndex < arr.length);
1569 // Remove 1 item at fromIndex.
1570 var removedItems = goog.array.ARRAY_PROTOTYPE_.splice.call(arr, fromIndex, 1);
1571 // Insert the removed item at toIndex.
1572 goog.array.ARRAY_PROTOTYPE_.splice.call(arr, toIndex, 0, removedItems[0]);
1573 // We don't use goog.array.insertAt and goog.array.removeAt, because they're
1574 // significantly slower than splice.
1575};
1576
1577
1578/**
1579 * Creates a new array for which the element at position i is an array of the
1580 * ith element of the provided arrays. The returned array will only be as long
1581 * as the shortest array provided; additional values are ignored. For example,
1582 * the result of zipping [1, 2] and [3, 4, 5] is [[1,3], [2, 4]].
1583 *
1584 * This is similar to the zip() function in Python. See {@link
1585 * http://docs.python.org/library/functions.html#zip}
1586 *
1587 * @param {...!goog.array.ArrayLike} var_args Arrays to be combined.
1588 * @return {!Array<!Array<?>>} A new array of arrays created from
1589 * provided arrays.
1590 */
1591goog.array.zip = function(var_args) {
1592 if (!arguments.length) {
1593 return [];
1594 }
1595 var result = [];
1596 for (var i = 0; true; i++) {
1597 var value = [];
1598 for (var j = 0; j < arguments.length; j++) {
1599 var arr = arguments[j];
1600 // If i is larger than the array length, this is the shortest array.
1601 if (i >= arr.length) {
1602 return result;
1603 }
1604 value.push(arr[i]);
1605 }
1606 result.push(value);
1607 }
1608};
1609
1610
1611/**
1612 * Shuffles the values in the specified array using the Fisher-Yates in-place
1613 * shuffle (also known as the Knuth Shuffle). By default, calls Math.random()
1614 * and so resets the state of that random number generator. Similarly, may reset
1615 * the state of the any other specified random number generator.
1616 *
1617 * Runtime: O(n)
1618 *
1619 * @param {!Array<?>} arr The array to be shuffled.
1620 * @param {function():number=} opt_randFn Optional random function to use for
1621 * shuffling.
1622 * Takes no arguments, and returns a random number on the interval [0, 1).
1623 * Defaults to Math.random() using JavaScript's built-in Math library.
1624 */
1625goog.array.shuffle = function(arr, opt_randFn) {
1626 var randFn = opt_randFn || Math.random;
1627
1628 for (var i = arr.length - 1; i > 0; i--) {
1629 // Choose a random array index in [0, i] (inclusive with i).
1630 var j = Math.floor(randFn() * (i + 1));
1631
1632 var tmp = arr[i];
1633 arr[i] = arr[j];
1634 arr[j] = tmp;
1635 }
1636};
1637
1638
1639/**
1640 * Returns a new array of elements from arr, based on the indexes of elements
1641 * provided by index_arr. For example, the result of index copying
1642 * ['a', 'b', 'c'] with index_arr [1,0,0,2] is ['b', 'a', 'a', 'c'].
1643 *
1644 * @param {!Array<T>} arr The array to get a indexed copy from.
1645 * @param {!Array<number>} index_arr An array of indexes to get from arr.
1646 * @return {!Array<T>} A new array of elements from arr in index_arr order.
1647 * @template T
1648 */
1649goog.array.copyByIndex = function(arr, index_arr) {
1650 var result = [];
1651 goog.array.forEach(index_arr, function(index) {
1652 result.push(arr[index]);
1653 });
1654 return result;
1655};