Files
openlayers/float-no-zero/closure-library/closure/goog/iter/iter.js
2014-03-07 10:55:12 +01:00

703 lines
21 KiB
JavaScript

// Copyright 2007 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Python style iteration utilities.
* @author arv@google.com (Erik Arvidsson)
*/
goog.provide('goog.iter');
goog.provide('goog.iter.Iterator');
goog.provide('goog.iter.StopIteration');
goog.require('goog.array');
goog.require('goog.asserts');
// TODO(nnaze): Add more functions from Python's itertools.
// http://docs.python.org/library/itertools.html
/**
* @typedef {goog.iter.Iterator|{length:number}|{__iterator__}}
*/
goog.iter.Iterable;
// For script engines that already support iterators.
if ('StopIteration' in goog.global) {
/**
* Singleton Error object that is used to terminate iterations.
* @type {Error}
*/
goog.iter.StopIteration = goog.global['StopIteration'];
} else {
/**
* Singleton Error object that is used to terminate iterations.
* @type {Error}
* @suppress {duplicate}
*/
goog.iter.StopIteration = Error('StopIteration');
}
/**
* Class/interface for iterators. An iterator needs to implement a {@code next}
* method and it needs to throw a {@code goog.iter.StopIteration} when the
* iteration passes beyond the end. Iterators have no {@code hasNext} method.
* It is recommended to always use the helper functions to iterate over the
* iterator or in case you are only targeting JavaScript 1.7 for in loops.
* @constructor
*/
goog.iter.Iterator = function() {};
/**
* Returns the next value of the iteration. This will throw the object
* {@see goog.iter#StopIteration} when the iteration passes the end.
* @return {*} Any object or value.
*/
goog.iter.Iterator.prototype.next = function() {
throw goog.iter.StopIteration;
};
/**
* Returns the {@code Iterator} object itself. This is used to implement
* the iterator protocol in JavaScript 1.7
* @param {boolean=} opt_keys Whether to return the keys or values. Default is
* to only return the values. This is being used by the for-in loop (true)
* and the for-each-in loop (false). Even though the param gives a hint
* about what the iterator will return there is no guarantee that it will
* return the keys when true is passed.
* @return {!goog.iter.Iterator} The object itself.
*/
goog.iter.Iterator.prototype.__iterator__ = function(opt_keys) {
return this;
};
/**
* Returns an iterator that knows how to iterate over the values in the object.
* @param {goog.iter.Iterable} iterable If the object is an iterator it
* will be returned as is. If the object has a {@code __iterator__} method
* that will be called to get the value iterator. If the object is an
* array-like object we create an iterator for that.
* @return {!goog.iter.Iterator} An iterator that knows how to iterate over the
* values in {@code iterable}.
*/
goog.iter.toIterator = function(iterable) {
if (iterable instanceof goog.iter.Iterator) {
return iterable;
}
if (typeof iterable.__iterator__ == 'function') {
return iterable.__iterator__(false);
}
if (goog.isArrayLike(iterable)) {
var i = 0;
var newIter = new goog.iter.Iterator;
newIter.next = function() {
while (true) {
if (i >= iterable.length) {
throw goog.iter.StopIteration;
}
// Don't include deleted elements.
if (!(i in iterable)) {
i++;
continue;
}
return iterable[i++];
}
};
return newIter;
}
// TODO(arv): Should we fall back on goog.structs.getValues()?
throw Error('Not implemented');
};
/**
* Calls a function for each element in the iterator with the element of the
* iterator passed as argument.
*
* @param {goog.iter.Iterable} iterable The iterator to iterate
* over. If the iterable is an object {@code toIterator} will be called on
* it.
* @param {function(this:T,?,?,?):?} f The function to call for every
* element. This function
* takes 3 arguments (the element, undefined, and the iterator) and the
* return value is irrelevant. The reason for passing undefined as the
* second argument is so that the same function can be used in
* {@see goog.array#forEach} as well as others.
* @param {T=} opt_obj The object to be used as the value of 'this' within
* {@code f}.
* @template T
*/
goog.iter.forEach = function(iterable, f, opt_obj) {
if (goog.isArrayLike(iterable)) {
/** @preserveTry */
try {
// NOTES: this passes the index number to the second parameter
// of the callback contrary to the documentation above.
goog.array.forEach(/** @type {goog.array.ArrayLike} */(iterable), f,
opt_obj);
} catch (ex) {
if (ex !== goog.iter.StopIteration) {
throw ex;
}
}
} else {
iterable = goog.iter.toIterator(iterable);
/** @preserveTry */
try {
while (true) {
f.call(opt_obj, iterable.next(), undefined, iterable);
}
} catch (ex) {
if (ex !== goog.iter.StopIteration) {
throw ex;
}
}
}
};
/**
* Calls a function for every element in the iterator, and if the function
* returns true adds the element to a new iterator.
*
* @param {goog.iter.Iterable} iterable The iterator to iterate over.
* @param {function(this:T,?,undefined,?):boolean} f The function to call for
* every element. This function
* takes 3 arguments (the element, undefined, and the iterator) and should
* return a boolean. If the return value is true the element will be
* included in the returned iteror. If it is false the element is not
* included.
* @param {T=} opt_obj The object to be used as the value of 'this' within
* {@code f}.
* @return {!goog.iter.Iterator} A new iterator in which only elements that
* passed the test are present.
* @template T
*/
goog.iter.filter = function(iterable, f, opt_obj) {
var iterator = goog.iter.toIterator(iterable);
var newIter = new goog.iter.Iterator;
newIter.next = function() {
while (true) {
var val = iterator.next();
if (f.call(opt_obj, val, undefined, iterator)) {
return val;
}
}
};
return newIter;
};
/**
* Creates a new iterator that returns the values in a range. This function
* can take 1, 2 or 3 arguments:
* <pre>
* range(5) same as range(0, 5, 1)
* range(2, 5) same as range(2, 5, 1)
* </pre>
*
* @param {number} startOrStop The stop value if only one argument is provided.
* The start value if 2 or more arguments are provided. If only one
* argument is used the start value is 0.
* @param {number=} opt_stop The stop value. If left out then the first
* argument is used as the stop value.
* @param {number=} opt_step The number to increment with between each call to
* next. This can be negative.
* @return {!goog.iter.Iterator} A new iterator that returns the values in the
* range.
*/
goog.iter.range = function(startOrStop, opt_stop, opt_step) {
var start = 0;
var stop = startOrStop;
var step = opt_step || 1;
if (arguments.length > 1) {
start = startOrStop;
stop = opt_stop;
}
if (step == 0) {
throw Error('Range step argument must not be zero');
}
var newIter = new goog.iter.Iterator;
newIter.next = function() {
if (step > 0 && start >= stop || step < 0 && start <= stop) {
throw goog.iter.StopIteration;
}
var rv = start;
start += step;
return rv;
};
return newIter;
};
/**
* Joins the values in a iterator with a delimiter.
* @param {goog.iter.Iterable} iterable The iterator to get the values from.
* @param {string} deliminator The text to put between the values.
* @return {string} The joined value string.
*/
goog.iter.join = function(iterable, deliminator) {
return goog.iter.toArray(iterable).join(deliminator);
};
/**
* For every element in the iterator call a function and return a new iterator
* with that value.
*
* @param {goog.iter.Iterable} iterable The iterator to iterate over.
* @param {function(this:T,?,undefined,?):?} f The function to call for every
* element. This function
* takes 3 arguments (the element, undefined, and the iterator) and should
* return a new value.
* @param {T=} opt_obj The object to be used as the value of 'this' within
* {@code f}.
* @return {!goog.iter.Iterator} A new iterator that returns the results of
* applying the function to each element in the original iterator.
* @template T
*/
goog.iter.map = function(iterable, f, opt_obj) {
var iterator = goog.iter.toIterator(iterable);
var newIter = new goog.iter.Iterator;
newIter.next = function() {
while (true) {
var val = iterator.next();
return f.call(opt_obj, val, undefined, iterator);
}
};
return newIter;
};
/**
* Passes every element of an iterator into a function and accumulates the
* result.
*
* @param {goog.iter.Iterable} iterable The iterator to iterate over.
* @param {function(this:T,V,?):V} f The function to call for every
* element. This function takes 2 arguments (the function's previous result
* or the initial value, and the value of the current element).
* function(previousValue, currentElement) : newValue.
* @param {V} val The initial value to pass into the function on the first call.
* @param {T=} opt_obj The object to be used as the value of 'this'
* within f.
* @return {V} Result of evaluating f repeatedly across the values of
* the iterator.
* @template T,V
*/
goog.iter.reduce = function(iterable, f, val, opt_obj) {
var rval = val;
goog.iter.forEach(iterable, function(val) {
rval = f.call(opt_obj, rval, val);
});
return rval;
};
/**
* Goes through the values in the iterator. Calls f for each these and if any of
* them returns true, this returns true (without checking the rest). If all
* return false this will return false.
*
* @param {goog.iter.Iterable} iterable The iterator object.
* @param {function(this:T,?,undefined,?):boolean} f The function to call for
* every value. This function
* takes 3 arguments (the value, undefined, and the iterator) and should
* return a boolean.
* @param {T=} opt_obj The object to be used as the value of 'this' within
* {@code f}.
* @return {boolean} true if any value passes the test.
* @template T
*/
goog.iter.some = function(iterable, f, opt_obj) {
iterable = goog.iter.toIterator(iterable);
/** @preserveTry */
try {
while (true) {
if (f.call(opt_obj, iterable.next(), undefined, iterable)) {
return true;
}
}
} catch (ex) {
if (ex !== goog.iter.StopIteration) {
throw ex;
}
}
return false;
};
/**
* Goes through the values in the iterator. Calls f for each these and if any of
* them returns false this returns false (without checking the rest). If all
* return true this will return true.
*
* @param {goog.iter.Iterable} iterable The iterator object.
* @param {function(this:T,?,undefined,?):boolean} f The function to call for
* every value. This function
* takes 3 arguments (the value, undefined, and the iterator) and should
* return a boolean.
* @param {T=} opt_obj The object to be used as the value of 'this' within
* {@code f}.
* @return {boolean} true if every value passes the test.
* @template T
*/
goog.iter.every = function(iterable, f, opt_obj) {
iterable = goog.iter.toIterator(iterable);
/** @preserveTry */
try {
while (true) {
if (!f.call(opt_obj, iterable.next(), undefined, iterable)) {
return false;
}
}
} catch (ex) {
if (ex !== goog.iter.StopIteration) {
throw ex;
}
}
return true;
};
/**
* Takes zero or more iterators and returns one iterator that will iterate over
* them in the order chained.
* @param {...goog.iter.Iterator} var_args Any number of iterator objects.
* @return {!goog.iter.Iterator} Returns a new iterator that will iterate over
* all the given iterators' contents.
*/
goog.iter.chain = function(var_args) {
var args = arguments;
var length = args.length;
var i = 0;
var newIter = new goog.iter.Iterator;
/**
* @return {*} The next item in the iteration.
* @this {goog.iter.Iterator}
*/
newIter.next = function() {
/** @preserveTry */
try {
if (i >= length) {
throw goog.iter.StopIteration;
}
var current = goog.iter.toIterator(args[i]);
return current.next();
} catch (ex) {
if (ex !== goog.iter.StopIteration || i >= length) {
throw ex;
} else {
// In case we got a StopIteration increment counter and try again.
i++;
return this.next();
}
}
};
return newIter;
};
/**
* Builds a new iterator that iterates over the original, but skips elements as
* long as a supplied function returns true.
* @param {goog.iter.Iterable} iterable The iterator object.
* @param {function(this:T,?,undefined,?):boolean} f The function to call for
* every value. This function
* takes 3 arguments (the value, undefined, and the iterator) and should
* return a boolean.
* @param {T=} opt_obj The object to be used as the value of 'this' within
* {@code f}.
* @return {!goog.iter.Iterator} A new iterator that drops elements from the
* original iterator as long as {@code f} is true.
* @template T
*/
goog.iter.dropWhile = function(iterable, f, opt_obj) {
var iterator = goog.iter.toIterator(iterable);
var newIter = new goog.iter.Iterator;
var dropping = true;
newIter.next = function() {
while (true) {
var val = iterator.next();
if (dropping && f.call(opt_obj, val, undefined, iterator)) {
continue;
} else {
dropping = false;
}
return val;
}
};
return newIter;
};
/**
* Builds a new iterator that iterates over the original, but only as long as a
* supplied function returns true.
* @param {goog.iter.Iterable} iterable The iterator object.
* @param {function(this:T,?,undefined,?):boolean} f The function to call for
* every value. This function
* takes 3 arguments (the value, undefined, and the iterator) and should
* return a boolean.
* @param {T=} opt_obj This is used as the 'this' object in f when called.
* @return {!goog.iter.Iterator} A new iterator that keeps elements in the
* original iterator as long as the function is true.
* @template T
*/
goog.iter.takeWhile = function(iterable, f, opt_obj) {
var iterator = goog.iter.toIterator(iterable);
var newIter = new goog.iter.Iterator;
var taking = true;
newIter.next = function() {
while (true) {
if (taking) {
var val = iterator.next();
if (f.call(opt_obj, val, undefined, iterator)) {
return val;
} else {
taking = false;
}
} else {
throw goog.iter.StopIteration;
}
}
};
return newIter;
};
/**
* Converts the iterator to an array
* @param {goog.iter.Iterable} iterable The iterator to convert to an array.
* @return {!Array} An array of the elements the iterator iterates over.
*/
goog.iter.toArray = function(iterable) {
// Fast path for array-like.
if (goog.isArrayLike(iterable)) {
return goog.array.toArray(/** @type {!goog.array.ArrayLike} */(iterable));
}
iterable = goog.iter.toIterator(iterable);
var array = [];
goog.iter.forEach(iterable, function(val) {
array.push(val);
});
return array;
};
/**
* Iterates over 2 iterators and returns true if they contain the same sequence
* of elements and have the same length.
* @param {goog.iter.Iterable} iterable1 The first iterable object.
* @param {goog.iter.Iterable} iterable2 The second iterable object.
* @return {boolean} true if the iterators contain the same sequence of
* elements and have the same length.
*/
goog.iter.equals = function(iterable1, iterable2) {
iterable1 = goog.iter.toIterator(iterable1);
iterable2 = goog.iter.toIterator(iterable2);
var b1, b2;
/** @preserveTry */
try {
while (true) {
b1 = b2 = false;
var val1 = iterable1.next();
b1 = true;
var val2 = iterable2.next();
b2 = true;
if (val1 != val2) {
return false;
}
}
} catch (ex) {
if (ex !== goog.iter.StopIteration) {
throw ex;
} else {
if (b1 && !b2) {
// iterable1 done but iterable2 is not done.
return false;
}
if (!b2) {
/** @preserveTry */
try {
// iterable2 not done?
val2 = iterable2.next();
// iterable2 not done but iterable1 is done
return false;
} catch (ex1) {
if (ex1 !== goog.iter.StopIteration) {
throw ex1;
}
// iterable2 done as well... They are equal
return true;
}
}
}
}
return false;
};
/**
* Advances the iterator to the next position, returning the given default value
* instead of throwing an exception if the iterator has no more entries.
* @param {goog.iter.Iterable} iterable The iterable object.
* @param {*} defaultValue The value to return if the iterator is empty.
* @return {*} The next item in the iteration, or defaultValue if the iterator
* was empty.
*/
goog.iter.nextOrValue = function(iterable, defaultValue) {
try {
return goog.iter.toIterator(iterable).next();
} catch (e) {
if (e != goog.iter.StopIteration) {
throw e;
}
return defaultValue;
}
};
/**
* Cartesian product of zero or more sets. Gives an iterator that gives every
* combination of one element chosen from each set. For example,
* ([1, 2], [3, 4]) gives ([1, 3], [1, 4], [2, 3], [2, 4]).
* @see http://docs.python.org/library/itertools.html#itertools.product
* @param {...!goog.array.ArrayLike.<*>} var_args Zero or more sets, as arrays.
* @return {!goog.iter.Iterator} An iterator that gives each n-tuple (as an
* array).
*/
goog.iter.product = function(var_args) {
var someArrayEmpty = goog.array.some(arguments, function(arr) {
return !arr.length;
});
// An empty set in a cartesian product gives an empty set.
if (someArrayEmpty || !arguments.length) {
return new goog.iter.Iterator();
}
var iter = new goog.iter.Iterator();
var arrays = arguments;
// The first indicies are [0, 0, ...]
var indicies = goog.array.repeat(0, arrays.length);
iter.next = function() {
if (indicies) {
var retVal = goog.array.map(indicies, function(valueIndex, arrayIndex) {
return arrays[arrayIndex][valueIndex];
});
// Generate the next-largest indicies for the next call.
// Increase the rightmost index. If it goes over, increase the next
// rightmost (like carry-over addition).
for (var i = indicies.length - 1; i >= 0; i--) {
// Assertion prevents compiler warning below.
goog.asserts.assert(indicies);
if (indicies[i] < arrays[i].length - 1) {
indicies[i]++;
break;
}
// We're at the last indicies (the last element of every array), so
// the iteration is over on the next call.
if (i == 0) {
indicies = null;
break;
}
// Reset the index in this column and loop back to increment the
// next one.
indicies[i] = 0;
}
return retVal;
}
throw goog.iter.StopIteration;
};
return iter;
};
/**
* Create an iterator to cycle over the iterable's elements indefinitely.
* For example, ([1, 2, 3]) would return : 1, 2, 3, 1, 2, 3, ...
* @see: http://docs.python.org/library/itertools.html#itertools.cycle.
* @param {!goog.iter.Iterable} iterable The iterable object.
* @return {!goog.iter.Iterator} An iterator that iterates indefinitely over
* the values in {@code iterable}.
*/
goog.iter.cycle = function(iterable) {
var baseIterator = goog.iter.toIterator(iterable);
// We maintain a cache to store the iterable elements as we iterate
// over them. The cache is used to return elements once we have
// iterated over the iterable once.
var cache = [];
var cacheIndex = 0;
var iter = new goog.iter.Iterator();
// This flag is set after the iterable is iterated over once
var useCache = false;
iter.next = function() {
var returnElement = null;
// Pull elements off the original iterator if not using cache
if (!useCache) {
try {
// Return the element from the iterable
returnElement = baseIterator.next();
cache.push(returnElement);
return returnElement;
} catch (e) {
// If an exception other than StopIteration is thrown
// or if there are no elements to iterate over (the iterable was empty)
// throw an exception
if (e != goog.iter.StopIteration || goog.array.isEmpty(cache)) {
throw e;
}
// set useCache to true after we know that a 'StopIteration' exception
// was thrown and the cache is not empty (to handle the 'empty iterable'
// use case)
useCache = true;
}
}
returnElement = cache[cacheIndex];
cacheIndex = (cacheIndex + 1) % cache.length;
return returnElement;
};
return iter;
};