angular-ui-form-validation
Version:
A plugin for performing validation in angularjs without writing lots of boilerplate code or duplicating logic.
1,665 lines (1,503 loc) • 217 kB
JavaScript
/*
* @name Lazy.js
*
* @fileOverview
* Lazy.js is a lazy evaluation library for JavaScript.
*
* This has been done before. For examples see:
*
* - [wu.js](http://fitzgen.github.io/wu.js/)
* - [Linq.js](http://linqjs.codeplex.com/)
* - [from.js](https://github.com/suckgamoni/fromjs/)
* - [IxJS](http://rx.codeplex.com/)
* - [sloth.js](http://rfw.name/sloth.js/)
*
* However, at least at present, Lazy.js is faster (on average) than any of
* those libraries. It is also more complete, with nearly all of the
* functionality of [Underscore](http://underscorejs.org/) and
* [Lo-Dash](http://lodash.com/).
*
* Finding your way around the code
* --------------------------------
*
* At the heart of Lazy.js is the {@link Sequence} object. You create an initial
* sequence using {@link Lazy}, which can accept an array, object, or string.
* You can then "chain" together methods from this sequence, creating a new
* sequence with each call.
*
* Here's an example:
*
* var data = getReallyBigArray();
*
* var statistics = Lazy(data)
* .map(transform)
* .filter(validate)
* .reduce(aggregate);
*
* {@link Sequence} is the foundation of other, more specific sequence types.
*
* An {@link ArrayLikeSequence} provides indexed access to its elements.
*
* An {@link ObjectLikeSequence} consists of key/value pairs.
*
* A {@link StringLikeSequence} is like a string (duh): actually, it is an
* {@link ArrayLikeSequence} whose elements happen to be characters.
*
* An {@link AsyncSequence} is special: it iterates over its elements
* asynchronously (so calling `each` generally begins an asynchronous loop and
* returns immediately).
*
* For more information
* --------------------
*
* I wrote a blog post that explains a little bit more about Lazy.js, which you
* can read [here](http://philosopherdeveloper.com/posts/introducing-lazy-js.html).
*
* You can also [create an issue on GitHub](https://github.com/dtao/lazy.js/issues)
* if you have any issues with the library. I work through them eventually.
*
* [@dtao](https://github.com/dtao)
*/
(function(context) {
/**
* Wraps an object and returns a {@link Sequence}. For `null` or `undefined`,
* simply returns an empty sequence (see {@link Lazy.strict} for a stricter
* implementation).
*
* - For **arrays**, Lazy will create a sequence comprising the elements in
* the array (an {@link ArrayLikeSequence}).
* - For **objects**, Lazy will create a sequence of key/value pairs
* (an {@link ObjectLikeSequence}).
* - For **strings**, Lazy will create a sequence of characters (a
* {@link StringLikeSequence}).
*
* @public
* @param {Array|Object|string} source An array, object, or string to wrap.
* @returns {Sequence} The wrapped lazy object.
*
* @exampleHelpers
* // Utility functions to provide to all examples
* function increment(x) { return x + 1; }
* function isEven(x) { return x % 2 === 0; }
* function isPositive(x) { return x > 0; }
* function isNegative(x) { return x < 0; }
*
* @examples
* Lazy([1, 2, 4]) // instanceof Lazy.ArrayLikeSequence
* Lazy({ foo: "bar" }) // instanceof Lazy.ObjectLikeSequence
* Lazy("hello, world!") // instanceof Lazy.StringLikeSequence
* Lazy() // sequence: []
* Lazy(null) // sequence: []
*/
function Lazy(source) {
if (source instanceof Array) {
return new ArrayWrapper(source);
} else if (typeof source === "string") {
return new StringWrapper(source);
} else if (source instanceof Sequence) {
return source;
}
if (Lazy.extensions) {
var extensions = Lazy.extensions, length = extensions.length, result;
while (!result && length--) {
result = extensions[length](source);
}
if (result) {
return result;
}
}
return new ObjectWrapper(source);
}
Lazy.VERSION = '0.3.2';
/*** Utility methods of questionable value ***/
Lazy.noop = function noop() {};
Lazy.identity = function identity(x) { return x; };
/**
* Provides a stricter version of {@link Lazy} which throws an error when
* attempting to wrap `null`, `undefined`, or numeric or boolean values as a
* sequence.
*
* @public
* @returns {Function} A stricter version of the {@link Lazy} helper function.
*
* @examples
* var Strict = Lazy.strict();
*
* Strict() // throws
* Strict(null) // throws
* Strict(true) // throws
* Strict(5) // throws
* Strict([1, 2, 3]) // instanceof Lazy.ArrayLikeSequence
* Strict({ foo: "bar" }) // instanceof Lazy.ObjectLikeSequence
* Strict("hello, world!") // instanceof Lazy.StringLikeSequence
*
* // Let's also ensure the static functions are still there.
* Strict.range(3) // sequence: [0, 1, 2]
* Strict.generate(Date.now) // instanceof Lazy.GeneratedSequence
*/
Lazy.strict = function strict() {
function StrictLazy(source) {
if (source == null) {
throw "You cannot wrap null or undefined using Lazy.";
}
if (typeof source === "number" || typeof source === "boolean") {
throw "You cannot wrap primitive values using Lazy.";
}
return Lazy(source);
};
Lazy(Lazy).each(function(property, name) {
StrictLazy[name] = property;
});
return StrictLazy;
};
/**
* The `Sequence` object provides a unified API encapsulating the notion of
* zero or more consecutive elements in a collection, stream, etc.
*
* Lazy evaluation
* ---------------
*
* Generally speaking, creating a sequence should not be an expensive operation,
* and should not iterate over an underlying source or trigger any side effects.
* This means that chaining together methods that return sequences incurs only
* the cost of creating the `Sequence` objects themselves and not the cost of
* iterating an underlying data source multiple times.
*
* The following code, for example, creates 4 sequences and does nothing with
* `source`:
*
* var seq = Lazy(source) // 1st sequence
* .map(func) // 2nd
* .filter(pred) // 3rd
* .reverse(); // 4th
*
* Lazy's convention is to hold off on iterating or otherwise *doing* anything
* (aside from creating `Sequence` objects) until you call `each`:
*
* seq.each(function(x) { });
*
* Defining custom sequences
* -------------------------
*
* Defining your own type of sequence is relatively simple:
*
* 1. Pass a *method name* and an object containing *function overrides* to
* {@link Sequence.define}. If the object includes a function called `init`,
* this function will be called upon initialization.
* 2. The object should include at least either a `getIterator` method or an
* `each` method. The former supports both asynchronous and synchronous
* iteration, but is slightly more cumbersome to implement. The latter
* supports synchronous iteration and can be automatically implemented in
* terms of the former. You can also implement both if you want, e.g. to
* optimize performance. For more info, see {@link Iterator} and
* {@link AsyncSequence}.
*
* As a trivial example, the following code defines a new method, `sample`,
* which randomly may or may not include each element from its parent.
*
* Lazy.Sequence.define("sample", {
* each: function(fn) {
* return this.parent.each(function(e) {
* // 50/50 chance of including this element.
* if (Math.random() > 0.5) {
* return fn(e);
* }
* });
* }
* });
*
* (Of course, the above could also easily have been implemented using
* {@link #filter} instead of creating a custom sequence. But I *did* say this
* was a trivial example, to be fair.)
*
* Now it will be possible to create this type of sequence from any parent
* sequence by calling the method name you specified. In other words, you can
* now do this:
*
* Lazy(arr).sample();
* Lazy(arr).map(func).sample();
* Lazy(arr).map(func).filter(pred).sample();
*
* Etc., etc.
*
* @public
* @constructor
*/
function Sequence() {}
/**
* Create a new constructor function for a type inheriting from `Sequence`.
*
* @public
* @param {string|Array.<string>} methodName The name(s) of the method(s) to be
* used for constructing the new sequence. The method will be attached to
* the `Sequence` prototype so that it can be chained with any other
* sequence methods, like {@link #map}, {@link #filter}, etc.
* @param {Object} overrides An object containing function overrides for this
* new sequence type. **Must** include either `getIterator` or `each` (or
* both). *May* include an `init` method as well. For these overrides,
* `this` will be the new sequence, and `this.parent` will be the base
* sequence from which the new sequence was constructed.
* @returns {Function} A constructor for a new type inheriting from `Sequence`.
*
* @examples
* // This sequence type logs every element to the specified logger as it
* // iterates over it.
* Lazy.Sequence.define("verbose", {
* init: function(logger) {
* this.logger = logger;
* },
*
* each: function(fn) {
* var logger = this.logger;
* return this.parent.each(function(e, i) {
* logger(e);
* return fn(e, i);
* });
* }
* });
*
* Lazy([1, 2, 3]).verbose(logger).each(Lazy.noop) // calls logger 3 times
*/
Sequence.define = function define(methodName, overrides) {
if (!overrides || (!overrides.getIterator && !overrides.each)) {
throw "A custom sequence must implement *at least* getIterator or each!";
}
return defineSequenceType(Sequence, methodName, overrides);
};
/**
* Gets the number of elements in the sequence. In some cases, this may
* require eagerly evaluating the sequence.
*
* @public
* @returns {number} The number of elements in the sequence.
*
* @examples
* Lazy([1, 2, 3]).size(); // => 3
* Lazy([1, 2]).map(Lazy.identity).size(); // => 2
* Lazy([1, 2, 3]).reject(isEven).size(); // => 2
* Lazy([1, 2, 3]).take(1).size(); // => 1
* Lazy({ foo: 1, bar: 2 }).size(); // => 2
* Lazy('hello').size(); // => 5
*/
Sequence.prototype.size = function size() {
return this.getIndex().length();
};
/**
* Creates an {@link Iterator} object with two methods, `moveNext` -- returning
* true or false -- and `current` -- returning the current value.
*
* This method is used when asynchronously iterating over sequences. Any type
* inheriting from `Sequence` must implement this method or it can't support
* asynchronous iteration.
*
* Note that **this method is not intended to be used directly by application
* code.** Rather, it is intended as a means for implementors to potentially
* define custom sequence types that support either synchronous or
* asynchronous iteration.
*
* @public
* @returns {Iterator} An iterator object.
*
* @examples
* var iterator = Lazy([1, 2]).getIterator();
*
* iterator.moveNext(); // => true
* iterator.current(); // => 1
* iterator.moveNext(); // => true
* iterator.current(); // => 2
* iterator.moveNext(); // => false
*/
Sequence.prototype.getIterator = function getIterator() {
return new Iterator(this);
};
/**
* Gets the root sequence underlying the current chain of sequences.
*/
Sequence.prototype.root = function root() {
return this.parent.root();
};
/**
* Evaluates the sequence and produces an appropriate value (an array in most
* cases, an object for {@link ObjectLikeSequence}s or a string for
* {@link StringLikeSequence}s).
*/
Sequence.prototype.value = function value() {
return this.toArray();
};
/**
* Applies the current transformation chain to a given source.
*
* @examples
* var sequence = Lazy([])
* .map(function(x) { return x * -1; })
* .filter(function(x) { return x % 2 === 0; });
*
* sequence.apply([1, 2, 3, 4]); // => [-2, -4]
*/
Sequence.prototype.apply = function apply(source) {
var root = this.root(),
previousSource = root.source,
result;
try {
root.source = source;
result = this.value();
} finally {
root.source = previousSource;
}
return result;
};
/**
* The Iterator object provides an API for iterating over a sequence.
*
* The purpose of the `Iterator` type is mainly to offer an agnostic way of
* iterating over a sequence -- either synchronous (i.e. with a `while` loop)
* or asynchronously (with recursive calls to either `setTimeout` or --- if
* available --- `setImmediate`). It is not intended to be used directly by
* application code.
*
* @public
* @constructor
* @param {Sequence} sequence The sequence to iterate over.
*/
function Iterator(sequence) {
this.sequence = sequence;
this.index = -1;
}
/**
* Gets the current item this iterator is pointing to.
*
* @public
* @returns {*} The current item.
*/
Iterator.prototype.current = function current() {
return this.cachedIndex && this.cachedIndex.get(this.index);
};
/**
* Moves the iterator to the next item in a sequence, if possible.
*
* @public
* @returns {boolean} True if the iterator is able to move to a new item, or else
* false.
*/
Iterator.prototype.moveNext = function moveNext() {
var cachedIndex = this.cachedIndex;
if (!cachedIndex) {
cachedIndex = this.cachedIndex = this.sequence.getIndex();
}
if (this.index >= cachedIndex.length() - 1) {
return false;
}
++this.index;
return true;
};
/**
* Creates an array snapshot of a sequence.
*
* Note that for indefinite sequences, this method may raise an exception or
* (worse) cause the environment to hang.
*
* @public
* @returns {Array} An array containing the current contents of the sequence.
*
* @examples
* Lazy([1, 2, 3]).toArray() // => [1, 2, 3]
*/
Sequence.prototype.toArray = function toArray() {
return this.reduce(function(arr, element) {
arr.push(element);
return arr;
}, []);
};
/**
* Provides an indexed view into the sequence.
*
* For sequences that are already indexed, this will simply return the
* sequence. For non-indexed sequences, this will eagerly evaluate the
* sequence and cache the result (so subsequent calls will not create
* additional arrays).
*
* @returns {ArrayLikeSequence} A sequence containing the current contents of
* the sequence.
*
* @examples
* Lazy([1, 2, 3]).filter(isEven) // instanceof Lazy.Sequence
* Lazy([1, 2, 3]).filter(isEven).getIndex() // instanceof Lazy.ArrayLikeSequence
*/
Sequence.prototype.getIndex = function getIndex() {
if (!this.cachedIndex) {
this.cachedIndex = new ArrayWrapper(this.toArray());
}
return this.cachedIndex;
};
/**
* Provides an indexed, memoized view into the sequence. This will cache the
* result whenever the sequence is first iterated, so that subsequent
* iterations will access the same element objects.
*
* @public
* @returns {ArrayLikeSequence} An indexed, memoized sequence containing this
* sequence's elements, cached after the first iteration.
*
* @example
* function createObject() { return new Object(); }
*
* var plain = Lazy.generate(createObject, 10),
* memoized = Lazy.generate(createObject, 10).memoize();
*
* plain.toArray()[0] === plain.toArray()[0]; // => false
* memoized.toArray()[0] === memoized.toArray()[0]; // => true
*/
Sequence.prototype.memoize = function memoize() {
return new MemoizedSequence(this);
};
/**
* @constructor
*/
function MemoizedSequence(parent) {
this.parent = parent;
}
// MemoizedSequence needs to have its prototype set up after ArrayLikeSequence
/**
* Creates an object from a sequence of key/value pairs.
*
* @public
* @returns {Object} An object with keys and values corresponding to the pairs
* of elements in the sequence.
*
* @examples
* var details = [
* ["first", "Dan"],
* ["last", "Tao"],
* ["age", 29]
* ];
*
* Lazy(details).toObject() // => { first: "Dan", last: "Tao", age: 29 }
*/
Sequence.prototype.toObject = function toObject() {
return this.reduce(function(object, pair) {
object[pair[0]] = pair[1];
return object;
}, {});
};
/**
* Iterates over this sequence and executes a function for every element.
*
* @public
* @aka forEach
* @param {Function} fn The function to call on each element in the sequence.
* Return false from the function to end the iteration.
*
* @examples
* Lazy([1, 2, 3, 4]).each(fn) // calls fn 4 times
*/
Sequence.prototype.each = function each(fn) {
var iterator = this.getIterator(),
i = -1;
while (iterator.moveNext()) {
if (fn(iterator.current(), ++i) === false) {
return false;
}
}
return true;
};
Sequence.prototype.forEach = function forEach(fn) {
return this.each(fn);
};
/**
* Creates a new sequence whose values are calculated by passing this sequence's
* elements through some mapping function.
*
* @public
* @aka collect
* @param {Function} mapFn The mapping function used to project this sequence's
* elements onto a new sequence.
* @returns {Sequence} The new sequence.
*
* @examples
* Lazy([]).map(increment) // sequence: []
* Lazy([1, 2, 3]).map(increment) // sequence: [2, 3, 4]
*
* @benchmarks
* function increment(x) { return x + 1; }
*
* var smArr = Lazy.range(10).toArray(),
* lgArr = Lazy.range(100).toArray();
*
* Lazy(smArr).map(increment).each(Lazy.noop) // lazy - 10 elements
* Lazy(lgArr).map(increment).each(Lazy.noop) // lazy - 100 elements
* _.each(_.map(smArr, increment), _.noop) // lodash - 10 elements
* _.each(_.map(lgArr, increment), _.noop) // lodash - 100 elements
*/
Sequence.prototype.map = function map(mapFn) {
return new MappedSequence(this, createCallback(mapFn));
};
Sequence.prototype.collect = function collect(mapFn) {
return this.map(mapFn);
};
/**
* @constructor
*/
function MappedSequence(parent, mapFn) {
this.parent = parent;
this.mapFn = mapFn;
}
MappedSequence.prototype = new Sequence();
MappedSequence.prototype.getIterator = function getIterator() {
return new MappingIterator(this.parent, this.mapFn);
};
MappedSequence.prototype.each = function each(fn) {
var mapFn = this.mapFn;
return this.parent.each(function(e, i) {
return fn(mapFn(e, i), i);
});
};
/**
* @constructor
*/
function MappingIterator(sequence, mapFn) {
this.iterator = sequence.getIterator();
this.mapFn = mapFn;
this.index = -1;
}
MappingIterator.prototype.current = function current() {
return this.mapFn(this.iterator.current(), this.index);
};
MappingIterator.prototype.moveNext = function moveNext() {
if (this.iterator.moveNext()) {
++this.index;
return true;
}
return false;
};
/**
* Creates a new sequence whose values are calculated by accessing the specified
* property from each element in this sequence.
*
* @public
* @param {string} propertyName The name of the property to access for every
* element in this sequence.
* @returns {Sequence} The new sequence.
*
* @examples
* var people = [
* { first: "Dan", last: "Tao" },
* { first: "Bob", last: "Smith" }
* ];
*
* Lazy(people).pluck("last") // sequence: ["Tao", "Smith"]
*/
Sequence.prototype.pluck = function pluck(property) {
return this.map(property);
};
/**
* Creates a new sequence whose values are calculated by invoking the specified
* function on each element in this sequence.
*
* @public
* @param {string} methodName The name of the method to invoke for every element
* in this sequence.
* @returns {Sequence} The new sequence.
*
* @examples
* function Person(first, last) {
* this.fullName = function fullName() {
* return first + " " + last;
* };
* }
*
* var people = [
* new Person("Dan", "Tao"),
* new Person("Bob", "Smith")
* ];
*
* Lazy(people).invoke("fullName") // sequence: ["Dan Tao", "Bob Smith"]
*/
Sequence.prototype.invoke = function invoke(methodName) {
return this.map(function(e) {
return e[methodName]();
});
};
/**
* Creates a new sequence whose values are the elements of this sequence which
* satisfy the specified predicate.
*
* @public
* @aka select
* @param {Function} filterFn The predicate to call on each element in this
* sequence, which returns true if the element should be included.
* @returns {Sequence} The new sequence.
*
* @examples
* var numbers = [1, 2, 3, 4, 5, 6];
*
* Lazy(numbers).filter(isEven) // sequence: [2, 4, 6]
*
* @benchmarks
* function isEven(x) { return x % 2 === 0; }
*
* var smArr = Lazy.range(10).toArray(),
* lgArr = Lazy.range(100).toArray();
*
* Lazy(smArr).filter(isEven).each(Lazy.noop) // lazy - 10 elements
* Lazy(lgArr).filter(isEven).each(Lazy.noop) // lazy - 100 elements
* _.each(_.filter(smArr, isEven), _.noop) // lodash - 10 elements
* _.each(_.filter(lgArr, isEven), _.noop) // lodash - 100 elements
*/
Sequence.prototype.filter = function filter(filterFn) {
return new FilteredSequence(this, createCallback(filterFn));
};
Sequence.prototype.select = function select(filterFn) {
return this.filter(filterFn);
};
/**
* @constructor
*/
function FilteredSequence(parent, filterFn) {
this.parent = parent;
this.filterFn = filterFn;
}
FilteredSequence.prototype = new Sequence();
FilteredSequence.prototype.getIterator = function getIterator() {
return new FilteringIterator(this.parent, this.filterFn);
};
FilteredSequence.prototype.each = function each(fn) {
var filterFn = this.filterFn;
return this.parent.each(function(e, i) {
if (filterFn(e, i)) {
return fn(e, i);
}
});
};
FilteredSequence.prototype.reverse = function reverse() {
return this.parent.reverse().filter(this.filterFn);
};
/**
* @constructor
*/
function FilteringIterator(sequence, filterFn) {
this.iterator = sequence.getIterator();
this.filterFn = filterFn;
this.index = 0;
}
FilteringIterator.prototype.current = function current() {
return this.value;
};
FilteringIterator.prototype.moveNext = function moveNext() {
var iterator = this.iterator,
filterFn = this.filterFn,
value;
while (iterator.moveNext()) {
value = iterator.current();
if (filterFn(value, this.index++)) {
this.value = value;
return true;
}
}
this.value = undefined;
return false;
};
/**
* Creates a new sequence whose values exclude the elements of this sequence
* identified by the specified predicate.
*
* @public
* @param {Function} rejectFn The predicate to call on each element in this
* sequence, which returns true if the element should be omitted.
* @returns {Sequence} The new sequence.
*
* @examples
* Lazy([1, 2, 3, 4, 5]).reject(isEven) // sequence: [1, 3, 5]
* Lazy([{ foo: 1 }, { bar: 2 }]).reject('foo') // sequence: [{ bar: 2 }]
* Lazy([{ foo: 1 }, { foo: 2 }]).reject({ foo: 2 }) // sequence: [{ foo: 1 }]
*/
Sequence.prototype.reject = function reject(rejectFn) {
rejectFn = createCallback(rejectFn);
return this.filter(function(e) { return !rejectFn(e); });
};
/**
* Creates a new sequence whose values have the specified type, as determined
* by the `typeof` operator.
*
* @public
* @param {string} type The type of elements to include from the underlying
* sequence, i.e. where `typeof [element] === [type]`.
* @returns {Sequence} The new sequence, comprising elements of the specified
* type.
*
* @examples
* Lazy([1, 2, 'foo', 'bar']).ofType('number') // sequence: [1, 2]
* Lazy([1, 2, 'foo', 'bar']).ofType('string') // sequence: ['foo', 'bar']
* Lazy([1, 2, 'foo', 'bar']).ofType('boolean') // sequence: []
*/
Sequence.prototype.ofType = function ofType(type) {
return this.filter(function(e) { return typeof e === type; });
};
/**
* Creates a new sequence whose values are the elements of this sequence with
* property names and values matching those of the specified object.
*
* @public
* @param {Object} properties The properties that should be found on every
* element that is to be included in this sequence.
* @returns {Sequence} The new sequence.
*
* @examples
* var people = [
* { first: "Dan", last: "Tao" },
* { first: "Bob", last: "Smith" }
* ];
*
* Lazy(people).where({ first: "Dan" }) // sequence: [{ first: "Dan", last: "Tao" }]
*
* @benchmarks
* var animals = ["dog", "cat", "mouse", "horse", "pig", "snake"];
*
* Lazy(animals).where({ length: 3 }).each(Lazy.noop) // lazy
* _.each(_.where(animals, { length: 3 }), _.noop) // lodash
*/
Sequence.prototype.where = function where(properties) {
return this.filter(properties);
};
/**
* Creates a new sequence with the same elements as this one, but to be iterated
* in the opposite order.
*
* Note that in some (but not all) cases, the only way to create such a sequence
* may require iterating the entire underlying source when `each` is called.
*
* @public
* @returns {Sequence} The new sequence.
*
* @examples
* Lazy([1, 2, 3]).reverse() // sequence: [3, 2, 1]
* Lazy([]).reverse() // sequence: []
*/
Sequence.prototype.reverse = function reverse() {
return new ReversedSequence(this);
};
/**
* @constructor
*/
function ReversedSequence(parent) {
this.parent = parent;
}
ReversedSequence.prototype = new Sequence();
ReversedSequence.prototype.getIterator = function getIterator() {
return new ReversedIterator(this.parent);
};
/**
* @constuctor
*/
function ReversedIterator(sequence) {
this.sequence = sequence;
}
ReversedIterator.prototype.current = function current() {
return this.sequence.getIndex().get(this.index);
};
ReversedIterator.prototype.moveNext = function moveNext() {
var indexed = this.sequence.getIndex(),
length = indexed.length();
if (typeof this.index === "undefined") {
this.index = length;
}
return (--this.index >= 0);
};
/**
* Creates a new sequence with all of the elements of this one, plus those of
* the given array(s).
*
* @public
* @param {...*} var_args One or more values (or arrays of values) to use for
* additional items after this sequence.
* @returns {Sequence} The new sequence.
*
* @examples
* var left = [1, 2, 3];
* var right = [4, 5, 6];
*
* Lazy(left).concat(right) // sequence: [1, 2, 3, 4, 5, 6]
* Lazy(left).concat(Lazy(right)) // sequence: [1, 2, 3, 4, 5, 6]
* Lazy(left).concat(right, [7, 8]) // sequence: [1, 2, 3, 4, 5, 6, 7, 8]
*/
Sequence.prototype.concat = function concat(var_args) {
return new ConcatenatedSequence(this, arraySlice.call(arguments, 0));
};
/**
* @constructor
*/
function ConcatenatedSequence(parent, arrays) {
this.parent = parent;
this.arrays = arrays;
}
ConcatenatedSequence.prototype = new Sequence();
ConcatenatedSequence.prototype.each = function each(fn) {
var done = false,
i = 0;
this.parent.each(function(e) {
if (fn(e, i++) === false) {
done = true;
return false;
}
});
if (!done) {
Lazy(this.arrays).flatten().each(function(e) {
if (fn(e, i++) === false) {
return false;
}
});
}
};
/**
* Creates a new sequence comprising the first N elements from this sequence, OR
* (if N is `undefined`) simply returns the first element of this sequence.
*
* @public
* @aka head, take
* @param {number=} count The number of elements to take from this sequence. If
* this value exceeds the length of the sequence, the resulting sequence
* will be essentially the same as this one.
* @returns {*} The new sequence (or the first element from this sequence if
* no count was given).
*
* @examples
* function powerOfTwo(exp) {
* return Math.pow(2, exp);
* }
*
* Lazy.generate(powerOfTwo).first() // => 1
* Lazy.generate(powerOfTwo).first(5) // sequence: [1, 2, 4, 8, 16]
* Lazy.generate(powerOfTwo).skip(2).first() // => 4
* Lazy.generate(powerOfTwo).skip(2).first(2) // sequence: [4, 8]
*/
Sequence.prototype.first = function first(count) {
if (typeof count === "undefined") {
return getFirst(this);
}
return new TakeSequence(this, count);
};
Sequence.prototype.head =
Sequence.prototype.take = function (count) {
return this.first(count);
};
/**
* @constructor
*/
function TakeSequence(parent, count) {
this.parent = parent;
this.count = count;
}
TakeSequence.prototype = new Sequence();
TakeSequence.prototype.getIterator = function getIterator() {
return new TakeIterator(this.parent, this.count);
};
TakeSequence.prototype.each = function each(fn) {
var count = this.count,
i = 0;
this.parent.each(function(e) {
var result;
if (i < count) { result = fn(e, i); }
if (++i >= count) { return false; }
return result;
});
};
/**
* @constructor
*/
function TakeIterator(sequence, count) {
this.iterator = sequence.getIterator();
this.count = count;
}
TakeIterator.prototype.current = function current() {
return this.iterator.current();
};
TakeIterator.prototype.moveNext = function moveNext() {
return ((--this.count >= 0) && this.iterator.moveNext());
};
/**
* Creates a new sequence comprising the elements from the head of this sequence
* that satisfy some predicate. Once an element is encountered that doesn't
* satisfy the predicate, iteration will stop.
*
* @public
* @param {Function} predicate
* @returns {Sequence} The new sequence
*
* @examples
* function lessThan(x) {
* return function(y) {
* return y < x;
* };
* }
*
* Lazy([1, 2, 3, 4]).takeWhile(lessThan(3)) // sequence: [1, 2]
* Lazy([1, 2, 3, 4]).takeWhile(lessThan(0)) // sequence: []
*/
Sequence.prototype.takeWhile = function takeWhile(predicate) {
return new TakeWhileSequence(this, predicate);
};
/**
* @constructor
*/
function TakeWhileSequence(parent, predicate) {
this.parent = parent;
this.predicate = predicate;
}
TakeWhileSequence.prototype = new Sequence();
TakeWhileSequence.prototype.each = function each(fn) {
var predicate = this.predicate;
this.parent.each(function(e) {
return predicate(e) && fn(e);
});
};
/**
* Creates a new sequence comprising all but the last N elements of this
* sequence.
*
* @public
* @param {number=} count The number of items to omit from the end of the
* sequence (defaults to 1).
* @returns {Sequence} The new sequence.
*
* @examples
* Lazy([1, 2, 3, 4]).initial() // sequence: [1, 2, 3]
* Lazy([1, 2, 3, 4]).initial(2) // sequence: [1, 2]
* Lazy([1, 2, 3]).filter(Lazy.identity).initial() // sequence: [1, 2]
*/
Sequence.prototype.initial = function initial(count) {
if (typeof count === "undefined") {
count = 1;
}
return this.take(this.getIndex().length() - count);
};
/**
* Creates a new sequence comprising the last N elements of this sequence, OR
* (if N is `undefined`) simply returns the last element of this sequence.
*
* @public
* @param {number=} count The number of items to take from the end of the
* sequence.
* @returns {*} The new sequence (or the last element from this sequence
* if no count was given).
*
* @examples
* Lazy([1, 2, 3]).last() // => 3
* Lazy([1, 2, 3]).last(2) // sequence: [2, 3]
* Lazy([1, 2, 3]).filter(isEven).last(2) // sequence: [2]
*/
Sequence.prototype.last = function last(count) {
if (typeof count === "undefined") {
return this.reverse().first();
}
return this.reverse().take(count).reverse();
};
/**
* Returns the first element in this sequence with property names and values
* matching those of the specified object.
*
* @public
* @param {Object} properties The properties that should be found on some
* element in this sequence.
* @returns {*} The found element, or `undefined` if none exists in this
* sequence.
*
* @examples
* var words = ["foo", "bar"];
*
* Lazy(words).findWhere({ 0: "f" }); // => "foo"
* Lazy(words).findWhere({ 0: "z" }); // => undefined
*/
Sequence.prototype.findWhere = function findWhere(properties) {
return this.where(properties).first();
};
/**
* Creates a new sequence comprising all but the first N elements of this
* sequence.
*
* @public
* @aka skip, tail, rest
* @param {number=} count The number of items to omit from the beginning of the
* sequence (defaults to 1).
* @returns {Sequence} The new sequence.
*
* @examples
* Lazy([1, 2, 3, 4]).rest() // sequence: [2, 3, 4]
* Lazy([1, 2, 3, 4]).rest(0) // sequence: [1, 2, 3, 4]
* Lazy([1, 2, 3, 4]).rest(2) // sequence: [3, 4]
* Lazy([1, 2, 3, 4]).rest(5) // sequence: []
*/
Sequence.prototype.rest = function rest(count) {
return new DropSequence(this, count);
};
Sequence.prototype.skip =
Sequence.prototype.tail =
Sequence.prototype.drop = function drop(count) {
return this.rest(count);
};
/**
* @constructor
*/
function DropSequence(parent, count) {
this.parent = parent;
this.count = typeof count === "number" ? count : 1;
}
DropSequence.prototype = new Sequence();
DropSequence.prototype.each = function each(fn) {
var count = this.count,
dropped = 0,
i = 0;
this.parent.each(function(e) {
if (dropped++ < count) { return; }
return fn(e, i++);
});
};
/**
* Creates a new sequence comprising the elements from this sequence *after*
* those that satisfy some predicate. The sequence starts with the first
* element that does not match the predicate.
*
* @public
* @aka skipWhile
* @param {Function} predicate
* @returns {Sequence} The new sequence
*/
Sequence.prototype.dropWhile = function dropWhile(predicate) {
return new DropWhileSequence(this, predicate);
};
Sequence.prototype.skipWhile = function skipWhile(predicate) {
return this.dropWhile(predicate);
};
/**
* @constructor
*/
function DropWhileSequence(parent, predicate) {
this.parent = parent;
this.predicate = predicate;
}
DropWhileSequence.prototype = new Sequence();
DropWhileSequence.prototype.each = function each(fn) {
var predicate = this.predicate,
done = false;
this.parent.each(function(e) {
if (!done) {
if (predicate(e)) {
return;
}
done = true;
}
return fn(e);
});
};
/**
* Creates a new sequence with the same elements as this one, but ordered
* according to the values returned by the specified function.
*
* @public
* @param {Function} sortFn The function to call on the elements in this
* sequence, in order to sort them.
* @returns {Sequence} The new sequence.
*
* @examples
* function population(country) {
* return country.pop;
* }
*
* function area(country) {
* return country.sqkm;
* }
*
* var countries = [
* { name: "USA", pop: 320000000, sqkm: 9600000 },
* { name: "Brazil", pop: 194000000, sqkm: 8500000 },
* { name: "Nigeria", pop: 174000000, sqkm: 924000 },
* { name: "China", pop: 1350000000, sqkm: 9700000 },
* { name: "Russia", pop: 143000000, sqkm: 17000000 },
* { name: "Australia", pop: 23000000, sqkm: 7700000 }
* ];
*
* Lazy(countries).sortBy(population).last(3).pluck('name') // sequence: ["Brazil", "USA", "China"]
* Lazy(countries).sortBy(area).last(3).pluck('name') // sequence: ["USA", "China", "Russia"]
*
* @benchmarks
* var randoms = Lazy.generate(Math.random).take(100).toArray();
*
* Lazy(randoms).sortBy(Lazy.identity).each(Lazy.noop) // lazy
* _.each(_.sortBy(randoms, Lazy.identity), _.noop) // lodash
*/
Sequence.prototype.sortBy = function sortBy(sortFn) {
return new SortedSequence(this, sortFn);
};
/**
* @constructor
*/
function SortedSequence(parent, sortFn) {
this.parent = parent;
this.sortFn = sortFn;
}
SortedSequence.prototype = new Sequence();
SortedSequence.prototype.each = function each(fn) {
var sortFn = createCallback(this.sortFn),
sorted = this.parent.toArray(),
i = -1;
sorted.sort(function(x, y) { return compare(x, y, sortFn); });
return forEach(sorted, fn);
};
/**
* Creates a new {@link ObjectLikeSequence} comprising the elements in this
* one, grouped together according to some key. The value associated with each
* key in the resulting object-like sequence is an array containing all of
* the elements in this sequence with that key.
*
* @public
* @param {Function|string} keyFn The function to call on the elements in this
* sequence to obtain a key by which to group them, or a string representing
* a parameter to read from all the elements in this sequence.
* @returns {Sequence} The new sequence.
*
* @examples
* function oddOrEven(x) {
* return x % 2 === 0 ? 'even' : 'odd';
* }
*
* var numbers = [1, 2, 3, 4, 5];
*
* Lazy(numbers).groupBy(oddOrEven) // sequence: { odd: [1, 3, 5], even: [2, 4] }
* Lazy(numbers).groupBy(oddOrEven).get("odd") // => [1, 3, 5]
* Lazy(numbers).groupBy(oddOrEven).get("foo") // => undefined
*/
Sequence.prototype.groupBy = function groupBy(keyFn) {
return new GroupedSequence(this, keyFn);
};
/**
* @constructor
*/
function GroupedSequence(parent, keyFn) {
this.parent = parent;
this.keyFn = keyFn;
}
// GroupedSequence must have its prototype set after ObjectLikeSequence has
// been fully initialized.
/**
* Creates a new {@link ObjectLikeSequence} containing the unique keys of all
* the elements in this sequence, each paired with the number of elements
* in this sequence having that key.
*
* @public
* @param {Function|string} keyFn The function to call on the elements in this
* sequence to obtain a key by which to count them, or a string representing
* a parameter to read from all the elements in this sequence.
* @returns {Sequence} The new sequence.
*
* @examples
* function oddOrEven(x) {
* return x % 2 === 0 ? 'even' : 'odd';
* }
*
* var numbers = [1, 2, 3, 4, 5];
*
* Lazy(numbers).countBy(oddOrEven) // sequence: { odd: 3, even: 2 }
* Lazy(numbers).countBy(oddOrEven).get("odd") // => 3
* Lazy(numbers).countBy(oddOrEven).get("foo") // => undefined
*/
Sequence.prototype.countBy = function countBy(keyFn) {
return new CountedSequence(this, keyFn);
};
/**
* @constructor
*/
function CountedSequence(parent, keyFn) {
this.parent = parent;
this.keyFn = keyFn;
}
// CountedSequence, like GroupedSequence, must have its prototype set after
// ObjectLikeSequence has been fully initialized.
/**
* Creates a new sequence with every unique element from this one appearing
* exactly once (i.e., with duplicates removed).
*
* @public
* @aka unique
* @returns {Sequence} The new sequence.
*
* @examples
* Lazy([1, 2, 2, 3, 3, 3]).uniq() // sequence: [1, 2, 3]
*
* @benchmarks
* function randomOf(array) {
* return function() {
* return array[Math.floor(Math.random() * array.length)];
* };
* }
*
* var mostUnique = Lazy.generate(randomOf(_.range(100)), 100).toArray(),
* someUnique = Lazy.generate(randomOf(_.range(50)), 100).toArray(),
* mostDupes = Lazy.generate(randomOf(_.range(5)), 100).toArray();
*
* Lazy(mostUnique).uniq().each(Lazy.noop) // lazy - mostly unique elements
* Lazy(someUnique).uniq().each(Lazy.noop) // lazy - some unique elements
* Lazy(mostDupes).uniq().each(Lazy.noop) // lazy - mostly duplicate elements
* _.each(_.uniq(mostUnique), _.noop) // lodash - mostly unique elements
* _.each(_.uniq(someUnique), _.noop) // lodash - some unique elements
* _.each(_.uniq(mostDupes), _.noop) // lodash - mostly duplicate elements
*/
Sequence.prototype.uniq = function uniq(keyFn) {
return new UniqueSequence(this, keyFn);
};
Sequence.prototype.unique = function unique(keyFn) {
return this.uniq(keyFn);
};
/**
* @constructor
*/
function UniqueSequence(parent, keyFn) {
this.parent = parent;
this.keyFn = keyFn;
}
UniqueSequence.prototype = new Sequence();
UniqueSequence.prototype.each = function each(fn) {
var cache = new Set(),
keyFn = this.keyFn,
i = 0;
if (keyFn) {
keyFn = createCallback(keyFn);
return this.parent.each(function(e) {
if (cache.add(keyFn(e))) {
return fn(e, i++);
}
});
} else {
return this.parent.each(function(e) {
if (cache.add(e)) {
return fn(e, i++);
}
});
}
};
/**
* Creates a new sequence by combining the elements from this sequence with
* corresponding elements from the specified array(s).
*
* @public
* @param {...Array} var_args One or more arrays of elements to combine with
* those of this sequence.
* @returns {Sequence} The new sequence.
*
* @examples
* Lazy([1, 2]).zip([3, 4]) // sequence: [[1, 3], [2, 4]]
*
* @benchmarks
* var smArrL = Lazy.range(10).toArray(),
* smArrR = Lazy.range(10, 20).toArray(),
* lgArrL = Lazy.range(100).toArray(),
* lgArrR = Lazy.range(100, 200).toArray();
*
* Lazy(smArrL).zip(smArrR).each(Lazy.noop) // lazy - zipping 10-element arrays
* Lazy(lgArrL).zip(lgArrR).each(Lazy.noop) // lazy - zipping 100-element arrays
* _.each(_.zip(smArrL, smArrR), _.noop) // lodash - zipping 10-element arrays
* _.each(_.zip(lgArrL, lgArrR), _.noop) // lodash - zipping 100-element arrays
*/
Sequence.prototype.zip = function zip(var_args) {
if (arguments.length === 1) {
return new SimpleZippedSequence(this, (/** @type {Array} */ var_args));
} else {
return new ZippedSequence(this, arraySlice.call(arguments, 0));
}
};
/**
* @constructor
*/
function ZippedSequence(parent, arrays) {
this.parent = parent;
this.arrays = arrays;
}
ZippedSequence.prototype = new Sequence();
ZippedSequence.prototype.each = function each(fn) {
var arrays = this.arrays,
i = 0;
this.parent.each(function(e) {
var group = [e];
for (var j = 0; j < arrays.length; ++j) {
if (arrays[j].length > i) {
group.push(arrays[j][i]);
}
}
return fn(group, i++);
});
};
/**
* Creates a new sequence with the same elements as this one, in a randomized
* order.
*
* @public
* @returns {Sequence} The new sequence.
*
* @examples
* Lazy([1, 2, 3, 4, 5]).shuffle() // the values [1, 2, 3, 4, 5] in any order
*/
Sequence.prototype.shuffle = function shuffle() {
return new ShuffledSequence(this);
};
/**
* @constructor
*/
function ShuffledSequence(parent) {
this.parent = parent;
}
ShuffledSequence.prototype = new Sequence();
ShuffledSequence.prototype.each = function each(fn) {
var shuffled = this.parent.toArray(),
floor = Math.floor,
random = Math.random,
j = 0;
for (var i = shuffled.length - 1; i > 0; --i) {
swap(shuffled, i, floor(random() * i) + 1);
if (fn(shuffled[i], j++) === false) {
return;
}
}
fn(shuffled[0], j);
};
/**
* Creates a new sequence with every element from this sequence, and with arrays
* exploded so that a sequence of arrays (of arrays) becomes a flat sequence of
* values.
*
* @public
* @returns {Sequence} The new sequence.
*
* @examples
* Lazy([1, [2, 3], [4, [5]]]).flatten() // sequence: [1, 2, 3, 4, 5]
* Lazy([1, Lazy([2, 3])]).flatten() // sequence: [1, 2, 3]
*/
Sequence.prototype.flatten = function flatten() {
return new FlattenedSequence(this);
};
/**
* @constructor
*/
function FlattenedSequence(parent) {
this.parent = parent;
}
FlattenedSequence.prototype = new Sequence();
FlattenedSequence.prototype.each = function each(fn) {
var index = 0;
return this.parent.each(function recurseVisitor(e) {
if (e instanceof Array) {
return forEach(e, recurseVisitor);
}
if (e instanceof Sequence) {
return e.each(recurseVisitor);
}
return fn(e, index++);
});
};
/**
* Creates a new sequence with the same elements as this one, except for all
* falsy values (`false`, `0`, `""`, `null`, and `undefined`).
*
* @public
* @returns {Sequence} The new sequence.
*
* @examples
* Lazy(["foo", null, "bar", undefined]).compact() // sequence: ["foo", "bar"]
*/
Sequence.prototype.compact = function compact() {
return this.filter(function(e) { return !!e; });
};
/**
* Creates a new sequence with all the elements of this sequence that are not
* also among the specified arguments.
*
* @public
* @aka difference
* @param {...*} var_args The values, or array(s) of values, to be excluded from the
* resulting sequence.
* @returns {Sequence} The new sequence.
*
* @examples
* Lazy([1, 2, 3, 4, 5]).without(2, 3) // sequence: [1, 4, 5]
* Lazy([1, 2, 3, 4, 5]).without([4, 5]) // sequence: [1, 2, 3]
*/
Sequence.prototype.without = function without(var_args) {
return new WithoutSequence(this, arraySlice.call(arguments, 0));
};
Sequence.prototype.difference = function difference(var_args) {
return this.without.apply(this, arguments);
};
/**
* @constructor
*/
function WithoutSequence(parent, values) {
this.parent = parent;
this.values = values;
}
WithoutSequence.prototype = new Sequence();
WithoutSequence.prototype.each = function each(fn) {
var set = createSet(this.values),
i = 0;
return this.parent.each(function(e) {
if (!set.contains(e)) {
return fn(e, i++);
}
});
};
/**
* Creates a new sequence with all the unique elements either in this sequence
* or among the specified arguments.
*
* @public
* @param {...*} var_args The values, or array(s) of values, to be additionally
* included in the resulting sequence.
* @returns {Sequence} The new sequence.
*
* @examples
* Lazy(["foo", "bar"]).union([]) // sequence: ["foo", "bar"]
* Lazy(["foo", "bar"]).union(["bar", "baz"]) // sequence: ["foo", "bar", "baz"]
*/
Sequence.prototype.union = function union(var_args) {
return this.concat(var_args).uniq();
};
/**
* Creates a new sequence with all the elements of this sequence that also
* appear among the specified arguments.
*
* @public
* @param {...*} var_args The values, or array(s) of values, in which elements
* from this sequence must also be included to end up in the resulting sequence.
* @returns {Sequence} The new sequence.
*
* @examples
* Lazy(["foo", "bar"]).intersection([]) // sequence: []
* Lazy(["foo", "bar"]).intersection(["bar", "baz"]) // sequence: ["bar"]
*/
Sequence.prototype.intersection = function intersection(var_args) {
if (arguments.length === 1 && arguments[0] instanceof Array) {
return new SimpleIntersectionSequence(this, (/** @type {Array} */ var_args));
} else {
return new IntersectionSequence(this, arraySlice.call(arguments, 0));
}
};
/**
* @constructor
*/
function IntersectionSequence(parent, arrays) {
this.parent = parent;
this.arrays = arrays;
}
IntersectionSequence.prototype = new Sequence();
IntersectionSequence.prototype.each = function each(fn) {
var sets = Lazy(this.arrays).map(function(values) {
return new UniqueMemoizer(Lazy(values).getIterator());
});
var setIterator = new UniqueMemoizer(sets.getIterator()),
i = 0;
return this.parent.each(function(e) {
var includedInAll = true;
setIterator.each(function(set) {
if (!set.contains(e)) {
includedInAll = false;
return false;
}
});
if (includedInAll) {
return fn(e, i++);
}