/*!Dinqyjs JavaScript Library v1.5.0 * http://dinqyjs.com/ * * Copyright (c) 2015 Garry Passarella * Released under the MIT license * http://dinqyjs.com/license * * Date: November 1st 2015 */ var Dinqyjs = (function() { "use strict"; var NULL = null, UNDEFINED = void 0, TRUE = true, FALSE = false, APPLY = "apply", ARRAY = Array, ARRAY_PROTOTYPE = ARRAY.prototype, CEIL = Math.ceil, CLONE = "clone", CONCAT = "concat", LENGTH = "length", INDEXOF = "indexOf", ISARRAY = "isArray", LASTINDEXOF = "lastIndexOf", PARSEINT = parseInt, POP = "pop", PUSH = "push", RANDOM = Math.random, REVERSE = "reverse", SLICE = "slice", SORT = "sort", SPLICE = "splice", _arrayElementCompare = function(element) { return function(other) { return element === other; }; }, _arrayMidpoint = function(arrayLength, evenResolver) { var index = arrayLength / 2; return PARSEINT(evenResolver > 0 ? CEIL(index) : index); }, //Array polyfills: _arrayIndexPolyfiller = function(name, increments) { if (!ARRAY_PROTOTYPE[name]) { ARRAY_PROTOTYPE[name] = function(element, start) { return _firstIndex(this, _arrayElementCompare(element), increments, start); }; } }, _boolOrNumber = function(x) { return typeof x == 'number' || typeof x == 'boolean'; }, _clone = function(obj) { return _wrap(obj._[CONCAT]()); }, _config = { ARRAY_PREALLOCATION: 64000 }, _crossJoinSelector = function(x, y) { var prop, joined = {}; if (_boolOrNumber(y)) { y = { right : y }; } for(prop in y) { joined[prop] = y[prop]; } if (_boolOrNumber(x)) { x = { left : x }; } for(prop in x) { joined[prop] = x[prop]; } return joined; }, _defaultSort = function(x, y) { return x > y ? 1 : (x < y ? -1 : 0); }, _differenceXY = function(inner, outer, predicate) { var usePredicate = _isFunction(predicate), i = 0, j, x, y, difference = [], outerLength; inner = _unwrap(_wrap(inner).distinct(predicate)); outer = _unwrap(outer); outerLength = outer[LENGTH]; while (i < inner[LENGTH]) { x = inner[i++]; j = 0; while (j < outerLength) { y = outer[j]; if (usePredicate && predicate(x, y) || !usePredicate && x === y) { break; } j++; } if (j === outerLength) { difference[PUSH](x); } } return _wrap(difference); }, _eachKeys = function(array, callback) { var thisElement, key; for (key in array) { thisElement = array[key]; if (!_isFunction(thisElement)) { callback(thisElement, key); } } }, _error = function(message) { throw new Error(message); }, _errorNoMatches = "Array contains no matching elements", _errorNotExactlyOneMatch = "Array does not contain exactly one matching element", _firstIndex = function(array, predicate, increments, startIndex, count) { var thisElement, thisLength = array[LENGTH]; increments = increments || 1; if (!_isFunction(predicate)) { return thisLength > 0 ? (increments > 0 ? 0 : thisLength - 1) : -1; } if (_isUndefined(startIndex)) { startIndex = increments > 0 ? 0 : thisLength - 1; } if (_isUndefined(count) || count > thisLength) { count = thisLength; } while (count > 0) { thisElement = array[startIndex]; if (predicate(thisElement)) { return startIndex; } startIndex += increments; count--; } return -1; }, _getTop1 = function(comparison, array, selector) { var thisElement, useSelector = _isFunction(selector), elementToCheckAgainst; for(var i = 0, thisLength = array[LENGTH], extreme = thisLength > 0 ? array[0] : NULL; i < thisLength; i++) { thisElement = array[i]; elementToCheckAgainst = (useSelector ? selector(thisElement) : thisElement); if (comparison ? elementToCheckAgainst <= extreme : elementToCheckAgainst >= extreme ) { extreme = thisElement; } } return extreme; }, _isFunction = function(obj) { return typeof obj == "function"; }, _isUndefined = function(obj) { return obj === UNDEFINED; }, _joinXY = function(innerElement, outerElement) { return { inner: innerElement, outer: outerElement }; }, _loop = function(array, callback, condition, returnOn) { for (var i = 0; i < array[LENGTH] && condition(array[i], i) != returnOn; i++) { callback(array[i], i); } }, _minitabVariation = function(q, n) { return 1 / 4 * (q * n + q); }, _multiply = function(runningTotal, value) { return runningTotal * value; }, _partition = function(array, keySelector, elementSelector, resultSelector) { var i = 0, thisElement, partitions = [], p, useElementSelector = _isFunction(elementSelector), useResultSelector = _isFunction(resultSelector); while (i < array[LENGTH]) { thisElement = array[i++]; p = keySelector(thisElement); if (useElementSelector) { thisElement = elementSelector(thisElement); } (partitions[p] = (p in partitions) ? partitions[p] : [])[PUSH](thisElement); } if (useResultSelector) { _useResultSelectorOnGroup(partitions, resultSelector); } return partitions; }, //This uses the minitab method to get quartiles _quartile = function(collection, q, selector) { var useSelector = _isFunction(selector), sorted, quartilePosition, lowerIndex, upperIndex, lowerElement, upperElement, lowerValue, upperValue, collectionLength = collection.count(); if (collectionLength < 1) { return UNDEFINED; } //Set up a clone of the array sorted = _unwrap(collection)[CONCAT](); _sortAndThenSortMore(sorted, selector); quartilePosition = _minitabVariation(q, collectionLength); lowerIndex = PARSEINT(quartilePosition); upperIndex = PARSEINT(CEIL(quartilePosition)); lowerValue = sorted[lowerIndex - 1]; upperValue = sorted[upperIndex - 1]; lowerElement = useSelector ? selector(lowerValue) : lowerValue; upperElement = useSelector ? selector(upperValue) : upperValue; return upperIndex > lowerIndex ? lowerElement : (lowerElement + upperElement) / 2; }, _randomForSorting = function() { return RANDOM() - 0.5; }, _sortAndThenSortMore = function(array, selectors) { if (_isUndefined(selectors) || selectors[LENGTH] < 1) { array[SORT](_defaultSort); return; } selectors = _isFunction(selectors) ? [ selectors ] : ARRAY_PROTOTYPE.slice.call(selectors); array[SORT](_sorterWithSelectors(selectors)); }, _sorterWithSelectors = function (selectors) { return function(x, y) { var s = 0, result = 0, xSelected, ySelected, direction, thisSelector, selectorsLength = selectors[LENGTH]; while (s < selectorsLength && result === 0) { thisSelector = selectors[s++]; if (!_isFunction(thisSelector)) { continue; } direction = 0; if (s < selectorsLength && typeof selectors[s] == "string" && selectors[s][INDEXOF]("des") === 0 ) { direction = 1; } xSelected = thisSelector(x); ySelected = thisSelector(y); if (xSelected < ySelected) { result = direction ? 1 : -1; } else if (xSelected > ySelected) { result = direction ? -1 : 1; } } return result; }; }, _total = function(array, summingFunction, selector) { var total = NULL, i = array[LENGTH] - 1, thisElement, usePredicate = _isFunction(selector), toAdd; while (i >= 0) { thisElement = array[i--]; toAdd = usePredicate ? selector(thisElement) : thisElement; total = (total === NULL ? toAdd : summingFunction(total, toAdd)); } return total; }, _unwrap = function(Collection) { return Collection._ || Collection; }, _useResultSelectorOnGroup = function(array, resultSelector) { var key, thisElement; for (key in array) { thisElement = array[key]; array[key] = resultSelector(thisElement, key); } return array; }, _wrap = function(array) { return array._ ? array : new Dinqyjs.Collection(array); }; _arrayIndexPolyfiller(INDEXOF, 1); _arrayIndexPolyfiller(LASTINDEXOF, -1); if (!ARRAY_PROTOTYPE.map) { ARRAY_PROTOTYPE.map = function(callback) { var thisLength = this[LENGTH], results = new ARRAY(thisLength > _config.ARRAY_PREALLOCATION ? 0 : thisLength), i = 0; if (!_isFunction(callback)) { return UNDEFINED; } while (i < thisLength) { results[i] = callback(this[i++]); } return results; }; } if (!ARRAY[ISARRAY]) { ARRAY[ISARRAY] = function(arg) { return arg.constructor === ARRAY; }; } return { Collection : (function() { function Collection(array) { this._ = array || []; } Collection.associative = function(object) { for (var i in object) { if (!_isFunction(object[i])) { return +object[LENGTH] === 0; } } return FALSE; }; Collection.configure = function(key, value) { return (_config[key] = (arguments[LENGTH] < 2 ? _config[key] : value)); }; Collection.transpose = function(/*collections or arrays*/) { var a, args = arguments, thisArray, thisArrayLength, results = []; for(var i = 0, argsLength = arguments[LENGTH]; i < argsLength; i++) { thisArray = _unwrap(args[i]); thisArrayLength = thisArray[LENGTH]; while (results[LENGTH] < thisArrayLength) { results[PUSH]([]); } a = 0; while (a < thisArrayLength) { results[a][PUSH](thisArray[a++]); } } return _wrap(results); }; Collection.prototype = { all: function(predicate) { var all = TRUE, i = 0; while (all && i < this._[LENGTH]) { all &= predicate(this._[i++]); } return all ? TRUE : FALSE; }, any: function(predicate) { return _firstIndex(this._, predicate) >= 0; }, ascending: function(/*selectors*/) { _sortAndThenSortMore(this._, arguments); return this; }, atRandom: function() { return this._[Math.floor(RANDOM() * this._[LENGTH])]; }, average: function(selector) { var thisLength = this._[LENGTH]; return thisLength ? this.sum(selector) / thisLength : 0; }, clear: function() { this._[SPLICE](0, this._[LENGTH]); }, clearWhere: function(selector) { var me = this._; for(var i = me[LENGTH] - 1; i >= 0; i--) { if (selector(me[i])) { me[SPLICE](i, 1); } } return this; }, clone: function() { return _clone(this); }, concat: function() { return _wrap(ARRAY_PROTOTYPE[CONCAT][APPLY](this._, arguments)); }, contains: function(item) { return this._[INDEXOF](item) > -1; }, count: function(element) { var i = 0, me = this._, arrayLength = me[LENGTH], count = arrayLength; if (!_isUndefined(element)) { count = 0; while (i < arrayLength) { if (me[i++] === element) { count++; } } } return count; }, crossJoin: function(otherSet, selector) { var i, me = this._, iEnd = me[LENGTH], j, jEnd = otherSet.length, result = []; if(_isUndefined(selector)) { selector = _crossJoinSelector; } otherSet = _unwrap(otherSet); for(i = 0; i < iEnd; i++) { for(j = 0; j < jEnd; j++) { result.push(selector(me[i], otherSet[j])); } } return _wrap(result); }, descending: function() { _sortAndThenSortMore(this._, arguments); return this[REVERSE](); }, difference: function(other, predicate) { var thisUnwrapped = this._; other = _unwrap(other); return _differenceXY(thisUnwrapped, other, predicate) .union(_differenceXY(other, thisUnwrapped, predicate)); }, distinct: function(selector) { var usePredicate = _isFunction(selector), distinct = [], x, y, i, j, thisLength, me = this._; for(i = 0, thisLength = me[LENGTH]; i < thisLength; i++) { x = me[i]; if (distinct[INDEXOF](x) == -1) { for(j = 0; j < thisLength; j++) { y = me[j]; if (usePredicate && selector(x, y) || !usePredicate && x === y) { distinct[PUSH](x); break; } } } } return _wrap(distinct); }, doUntil: function(callback, stoppingCondition) { _loop(this._, callback, stoppingCondition, 1); }, doWhile: function(callback, condition) { _loop(this._, callback, condition, 0); }, each: function(callback) { var i = 0, me = this._, thisLength = me[LENGTH]; if (thisLength === +thisLength && !Collection.associative(this._)) { while (i < thisLength) { callback(me[i], i++); } return; } _eachKeys(me, callback); }, element: function(index, item) { return (this._[index] = arguments[LENGTH] > 1 ? item : this._[index]); }, equalTo: function(other, predicate) { other = _unwrap(other); var me = this._, thisLength = me[LENGTH], i = thisLength - 1, useFunction = _isFunction(predicate), thisElement, otherElement; if (thisLength != other[LENGTH]) { return FALSE; } while (i >= 0) { thisElement = me[i]; otherElement = other[i--]; if (useFunction ? !predicate(thisElement, otherElement) : thisElement !== otherElement ) { return FALSE; } } return TRUE; }, findIndex: function(predicate, startIndex, count) { return _isFunction(predicate) ? _firstIndex(this._, predicate, 1, startIndex, count) : -1; }, findLastIndex: function(predicate, startIndex, count) { return _isFunction(predicate) ? _firstIndex(this._, predicate, -1, startIndex, count) : -1; }, first: function(predicate) { var firstIndex = _firstIndex(this._, predicate); if (firstIndex >= 0) { return this._[firstIndex]; } _error(_errorNoMatches); }, flatten: function() { var i = 0, flattened = _wrap([]), thisElement, me = this._; while (i < me[LENGTH]) { thisElement = me[i++]; Collection.prototype[PUSH][APPLY]( flattened, ARRAY[ISARRAY](thisElement) ? thisElement : [ thisElement ] ); } return flattened; }, groupBy: function(keySelector, elementSelector, resultSelector) { var partition = _partition(this._, keySelector, elementSelector, resultSelector), key, me = this._; me[SPLICE](0, me[LENGTH]); for (key in partition) { me[key] = partition[key]; } return this; }, indexOf: function() { return ARRAY_PROTOTYPE[INDEXOF][APPLY](this._, arguments); }, innerJoin: function(other, predicate, joinedObjectCreator) { var joined = [], me = this._, innerElement, outerElement, i = 0, j; other = _unwrap(other); if (!ARRAY[ISARRAY](other)) { return this[CLONE](); } if (!_isFunction(joinedObjectCreator)) { joinedObjectCreator = _joinXY; } while (i < me[LENGTH]) { innerElement = me[i++]; j = 0; while (j < other[LENGTH]) { outerElement = other[j++]; if (predicate(innerElement, outerElement)) { joined[PUSH](joinedObjectCreator(innerElement, outerElement)); } } } return _wrap(joined); }, insert: function(index, element) { this._[SPLICE](index, 0, element); }, insertRange: function(index, elements) { ARRAY_PROTOTYPE[SPLICE][APPLY](this._, [ index, 0 ][CONCAT]( arguments[LENGTH] < 3 ? _unwrap(elements) : ARRAY_PROTOTYPE.slice.call(arguments, 1) ) ); }, interquartileRange: function(selector) { return this.upperquartile(selector) - this.lowerquartile(selector); }, intersect: function(other, predicate) { var usePredicate = _isFunction(predicate), intersection = [], x, y, i = 0, j, thisDistinct = _unwrap(this.distinct(predicate)), otherDistinct = _unwrap(_wrap(other).distinct(predicate)); while (i < thisDistinct[LENGTH]) { x = thisDistinct[i++]; j = 0; while (j < otherDistinct[LENGTH]) { y = otherDistinct[j++]; if (usePredicate && predicate(x, y) || !usePredicate && x === y ) { intersection[PUSH](x); break; } } } return _wrap(intersection); }, join: function() { return ARRAY_PROTOTYPE.join[APPLY](this._, arguments); }, keys: function() { var keys = [], key, me = this._; for (key in me) { if (!_isFunction(me[key])) { keys[PUSH](key); } } return keys; }, last: function(predicate) { var me = this._, firstIndex = _firstIndex(me, predicate, -1); if (firstIndex >= 0) { return me[firstIndex]; } _error(_errorNoMatches); }, lastIndexOf: function() { return ARRAY_PROTOTYPE[LASTINDEXOF][APPLY](this._, arguments); }, lowerquartile: function(selector) { return _quartile(this, 1, selector); }, map: function(selector) { var result = void 0; if (_isFunction(selector)) { var results = []; this.each(function (e) { results[PUSH](selector(e)); }); result = _wrap(results); } return result; }, max: function(selector) { return _getTop1(0, this._, selector); }, median: function(/*selector*/) { var middleFloor, middleCeil, sorted; sorted = this[CLONE](); _sortAndThenSortMore(_unwrap(sorted), arguments); middleFloor = sorted.middle(); middleCeil = sorted.middle(1); return middleFloor < middleCeil ? (middleFloor + middleCeil) / 2 : middleFloor; }, middle: function(evenResolver) { return this._[_arrayMidpoint(this._[LENGTH] - 1, evenResolver)]; }, min: function(selector) { return _getTop1(1, this._, selector); }, mode: function(selector) { var highestCount = 0, highestElements = [], lastElement, currentCount, selection, i = 0, element; selection = _unwrap((_isFunction(selector) ? this.map(selector) : this[CLONE]()) .ascending()); while (i < selection[LENGTH]) { element = selection[i++]; currentCount = (element === lastElement ? currentCount + 1 : 0); if (currentCount === highestCount) { highestElements[PUSH](element); } else if (currentCount > highestCount) { highestElements = [element]; highestCount++; } lastElement = element; } return highestElements; }, multiply: function(selector) { return this._[LENGTH] > 0 ? _total(this._, _multiply, selector) : 0; }, none: function(predicate) { return !this.any(predicate); }, orderBy: function(/*selectors*/) { _sortAndThenSortMore(this._, arguments); }, outerJoin: function(other, predicate, joinedObjectCreator) { var joined = [], innerElement, outerElement, outerNotFound, i = 0, j, usePredicate = _isFunction(predicate); other = _unwrap(other); if (!ARRAY[ISARRAY](other)) { return this[CLONE](); } if (!_isFunction(joinedObjectCreator)) { joinedObjectCreator = _joinXY; } while (i < this._[LENGTH]) { innerElement = this._[i++]; outerNotFound = 1; j = 0; while (j < other[LENGTH]) { outerElement = other[j++]; if (usePredicate && predicate(innerElement, outerElement) || !usePredicate && innerElement === outerElement ) { outerNotFound = 0; joined[PUSH](joinedObjectCreator(innerElement, outerElement)); } } if (outerNotFound) { joined[PUSH](joinedObjectCreator(innerElement, NULL)); } } return _wrap(joined); }, pack: function() { var me = this._, thisElement; for(var i = me[LENGTH] - 1; i >= 0; i--) { thisElement = me[i]; if (_isUndefined(thisElement) || thisElement === NULL) { me[SPLICE](i, 1); } } return this; }, partition: function(keySelector, elementSelector, resultSelector) { return _wrap(_partition(this._, keySelector, elementSelector, resultSelector)); }, pop: function() { return ARRAY_PROTOTYPE[POP][APPLY](this._, arguments); }, push: function() { return ARRAY_PROTOTYPE[PUSH][APPLY](this._, arguments); }, pushRepeatedly: function(element, times) { for (var i = 0; i < times; i++) { this[PUSH](element); } return this; }, range: function(startBefore, endBefore) { return _wrap(this._[SLICE](startBefore, endBefore)); }, raw: function() { return this._; }, remove: function(elements) { var index, i; if(arguments.length > 1) { elements = ARRAY_PROTOTYPE.slice.call(arguments); } else if (!ARRAY[ISARRAY](elements)) { elements = [elements]; } for(i = 0; i < elements.length; i++) { index = this._.indexOf(elements[i]); if(index > -1) { this.removeAt(index); } } }, removeAt: function(index) { this._[SPLICE](index, 1); }, removeRange: function(start, count) { this._[SPLICE](start, count); }, reverse: function() { return _wrap(ARRAY_PROTOTYPE[REVERSE][APPLY](this._, arguments)); }, shift: function() { return ARRAY_PROTOTYPE.shift[APPLY](this._, arguments); }, shuffle: function() { this._[SORT](_randomForSorting); return this; }, single: function(predicate) { var matches = _unwrap(this); if (_isFunction(predicate)) { matches = _unwrap(_wrap(this).where(predicate)); } if (matches[LENGTH] != 1) { _error(_errorNotExactlyOneMatch); } return matches[0]; }, skip: function(count) { return _wrap(this._[SLICE](count)); }, sort: function() { ARRAY_PROTOTYPE[SORT][APPLY](this._, arguments); return this; }, sum: function(selector) { return this._[LENGTH] > 0 ? _total(this._, function(runningTotal, value) { return runningTotal + value; }, selector) : 0; }, take: function(count) { return _wrap(this._[SLICE](0, count)); }, toString: function() { return ARRAY_PROTOTYPE.toString[APPLY](this._); }, union: function(other) { var unioned = this[CLONE](); ARRAY_PROTOTYPE[PUSH][APPLY](_unwrap(unioned), _unwrap(other)); return unioned; }, unshift: function() { return ARRAY_PROTOTYPE.unshift[APPLY](this._, arguments); }, upperquartile: function(selector) { return _quartile(this, 3, selector); }, valueOf: function() { return ARRAY_PROTOTYPE.valueOf[APPLY](this._, arguments); }, where: function(predicate) { var matches = [], thisElement, i = 0, me = this._; while (i < me[LENGTH]) { thisElement = me[i++]; if (predicate(thisElement)) { matches[PUSH](thisElement); } } return _wrap(matches); } }; return Collection; }()) }; }()); //Shorthand to use Dinqy functionality var $Dq = function(arr) { return new Dinqyjs.Collection(arr); }; //For node.js if (typeof exports === "object" && exports) { exports.Dinqyjs = Dinqyjs; exports.$Dq = $Dq; }