lodash-contrib
Version:
The brass buckles on lodash's utility belt
220 lines (179 loc) • 6.72 kB
JavaScript
module.exports = function(_) {
// Create quick reference variables for speed access to core prototypes.
var slice = Array.prototype.slice,
concat = Array.prototype.concat,
sort = Array.prototype.sort;
var existy = function(x) { return x != null; };
// Mixing in the array builders
// ----------------------------
_.mixin({
// Concatenates one or more arrays given as arguments. If given objects and
// scalars as arguments `cat` will plop them down in place in the result
// array. If given an `arguments` object, `cat` will treat it like an array
// and concatenate it likewise.
cat: function() {
return _.reduce(arguments, function(acc, elem) {
if (_.isArguments(elem)) {
return concat.call(acc, slice.call(elem));
}
else {
return concat.call(acc, elem);
}
}, []);
},
// 'Constructs' an array by putting an element at its front
cons: function(head, tail) {
return _.cat([head], tail);
},
// Takes an array and chunks it some number of times into
// sub-arrays of size n. Allows and optional padding array as
// the third argument to fill in the tail chunk when n is
// not sufficient to build chunks of the same size.
chunkContrib: function(array, n, pad) {
var args = arguments;
var p = function(array) {
if (array == null) return [];
var part = _.take(array, n);
if (n === _.size(part)) {
return _.cons(part, p(_.drop(array, n)));
}
else if (args.length === 3) {
pad = _.isArray(pad) ? pad : _.repeatContrib(n, pad);
return [_.take(_.cat(part, pad), n)];
}
else {
return [];
}
};
return p(array);
},
// Takes an array and chunks it some number of times into
// sub-arrays of size n. If the array given cannot fill the size
// needs of the final chunk then a smaller chunk is used
// for the last.
chunkAll: function(array, n, step) {
step = (step != null) ? step : n;
var p = function(array, n, step) {
if (_.isEmpty(array)) return [];
return _.cons(_.take(array, n),
p(_.drop(array, step), n, step));
};
return p(array, n, step);
},
// Maps a function over an array and concatenates all of the results.
mapcat: function(array, fun) {
return _.cat.apply(null, _.map(array, fun));
},
// Returns an array with some item between each element
// of a given array.
interpose: function(array, inter) {
if (!_.isArray(array)) throw new TypeError;
var sz = _.size(array);
if (sz === 0) return array;
if (sz === 1) return array;
return slice.call(_.mapcat(array, function(elem) {
return _.cons(elem, [inter]);
}), 0, -1);
},
// Weaves two or more arrays together
weave: function(/* args */) {
if (!_.some(arguments)) return [];
if (arguments.length == 1) return arguments[0];
return _.filter(_.flatten(_.zip.apply(null, arguments), false), function(elem) {
return elem != null;
});
},
interleave: _.weave,
// Returns an array of a value repeated a certain number of
// times.
repeatContrib: function(t, elem) {
return _.times(t, function() { return elem; });
},
// Returns an array built from the contents of a given array repeated
// a certain number of times.
cycle: function(t, elems) {
return _.flatten(_.times(t, function() { return elems; }), true);
},
// Returns an array with two internal arrays built from
// taking an original array and spliting it at an index.
splitAt: function(array, index) {
return [_.take(array, index), _.drop(array, index)];
},
// Call a function recursively f(f(f(args))) until a second
// given function goes falsey. Expects a seed value to start.
iterateUntil: function(doit, checkit, seed) {
var ret = [];
var result = doit(seed);
while (checkit(result)) {
ret.push(result);
result = doit(result);
}
return ret;
},
// Takes every nth item from an array, returning an array of
// the results.
takeSkipping: function(array, n) {
var ret = [];
var sz = _.size(array);
if (n <= 0) return [];
if (n === 1) return array;
for(var index = 0; index < sz; index += n) {
ret.push(array[index]);
}
return ret;
},
// Returns an array of each intermediate stage of a call to
// a `reduce`-like function.
reductions: function(array, fun, init) {
var ret = [];
var acc = init;
_.each(array, function(v,k) {
acc = fun(acc, array[k]);
ret.push(acc);
});
return ret;
},
// Runs its given function on the index of the elements rather than
// the elements themselves, keeping all of the truthy values in the end.
keepIndexed: function(array, pred) {
return _.filter(_.map(_.range(_.size(array)), function(i) {
return pred(i, array[i]);
}),
existy);
},
// Accepts an array-like object (other than strings) as an argument and
// returns an array whose elements are in the reverse order. Unlike the
// built-in `Array.prototype.reverse` method, this does not mutate the
// original object. Note: attempting to use this method on a string will
// result in a `TypeError`, as it cannot properly reverse unicode strings.
reverseOrder: function(obj) {
if (typeof obj == 'string')
throw new TypeError('Strings cannot be reversed by _.reverseOrder');
return slice.call(obj).reverse();
},
// Returns copy or array sorted according to arbitrary ordering
// order must be an array of values; defines the custom sort
// key must be one of: missing/null, a string, or a function;
collate: function(array, order, key) {
if (!_.isArray(array)) throw new TypeError("expected an array as the first argument");
if (!_.isArray(order)) throw new TypeError("expected an array as the second argument");
return sort.call(array, function (a, b) {
if(_.isFunction(key)) {
valA = key.call(a);
valB = key.call(b);
} else if(existy(key)) {
valA = a[key];
valB = b[key];
} else {
valA = a;
valB = b;
}
var rankA = _.indexOf(order, valA);
var rankB = _.indexOf(order, valB);
if(rankA === -1) return 1;
if(rankB === -1) return -1;
return rankA - rankB;
});
}
});
};