703 lines
21 KiB
JavaScript
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;
|
|
};
|