2025-06-27 10:04:22 +08:00

1055 lines
23 KiB
JavaScript

/*!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;
}