Add _.sortByMultiple.

This commit is contained in:
John-David Dalton
2014-11-23 23:26:11 -08:00
parent d8a7a7792a
commit 0fe8197100
2 changed files with 145 additions and 62 deletions

120
lodash.js
View File

@@ -528,6 +528,26 @@
return result;
}
/**
* The base implementation of `_.sortBy` and `_.sortByMultiple` which uses
* `comparer` to define the sort order of `array` and replaces criteria objects
* with their corresponding values.
*
* @private
* @param {Array} array The array to sort.
* @param {Function} comparer The function to define sort order.
* @returns {Array} Returns `array`.
*/
function baseSortBy(array, comparer) {
var length = array.length;
array.sort(comparer);
while (length--) {
array[length] = array[length].value;
}
return array;
}
/**
* Used by `_.max` and `_.min` as the default callback for string values.
*
@@ -586,8 +606,8 @@
}
/**
* Used by `_.sortBy` to compare multiple properties of each element in a
* collection and stable sort them in ascending order.
* Used by `_.sortByMultiple` to compare multiple properties of each element
* in a collection and stable sort them in ascending order.
*
* @private
* @param {Object} object The object to compare to `other`.
@@ -1000,10 +1020,10 @@
* `omit`, `once`, `pairs`, `partial`, `partialRight`, `partition`, `pick`,
* `pluck`, `property`, `propertyOf`, `pull`, `pullAt`, `push`, `range`,
* `rearg`, `reject`, `remove`, `rest`, `reverse`, `shuffle`, `slice`, `sort`,
* `sortBy`, `splice`, `take`, `takeRight`, `takeRightWhile`, `takeWhile`,
* `tap`, `throttle`, `thru`, `times`, `toArray`, `transform`, `union`, `uniq`,
* `unshift`, `unzip`, `values`, `valuesIn`, `where`, `without`, `wrap`, `xor`,
* `zip`, and `zipObject`
* `sortBy`, `sortByMultiple`, `splice`, `take`, `takeRight`, `takeRightWhile`,
* `takeWhile`, `tap`, `throttle`, `thru`, `times`, `toArray`, `transform`,
* `union`, `uniq`, `unshift`, `unzip`, `values`, `valuesIn`, `where`,
* `without`, `wrap`, `xor`, `zip`, and `zipObject`
*
* The non-chainable wrapper functions are:
* `attempt`, `camelCase`, `capitalize`, `clone`, `cloneDeep`, `deburr`,
@@ -6020,9 +6040,6 @@
* If a property name is provided for `iteratee` the created "_.pluck" style
* callback returns the property value of the given element.
*
* If an array of property names is provided for `iteratee` the collection
* is sorted by each property value.
*
* If an object is provided for `iteratee` the created "_.where" style callback
* returns `true` for elements that have the properties of the given object,
* else `false`.
@@ -6032,8 +6049,8 @@
* @category Collection
* @param {Array|Object|string} collection The collection to iterate over.
* @param {Array|Function|Object|string} [iteratee=_.identity] The function
* invoked per iteration. If property name(s) or an object is provided it
* is used to create a "_.pluck" or "_.where" style callback respectively.
* invoked per iteration. If a property name or an object is provided it is
* used to create a "_.pluck" or "_.where" style callback respectively.
* @param {*} [thisArg] The `this` binding of `iteratee`.
* @returns {Array} Returns the new sorted array.
* @example
@@ -6045,52 +6062,74 @@
* // => [3, 1, 2]
*
* var users = [
* { 'user': 'barney', 'age': 36 },
* { 'user': 'fred', 'age': 40 },
* { 'user': 'barney', 'age': 26 },
* { 'user': 'fred', 'age': 30 }
* { 'user': 'fred' },
* { 'user': 'pebbles' },
* { 'user': 'barney' }
* ];
*
* // using "_.pluck" callback shorthand
* _.map(_.sortBy(users, 'age'), _.values);
* // => [['barney', 26], ['fred', 30], ['barney', 36], ['fred', 40]]
*
* // sorting by multiple properties
* _.map(_.sortBy(users, ['user', 'age']), _.values);
* // = > [['barney', 26], ['barney', 36], ['fred', 30], ['fred', 40]]
* _.pluck(_.sortBy(users, 'user'), 'user');
* // => ['barney', 'fred', 'pebbles']
*/
function sortBy(collection, iteratee, thisArg) {
if (thisArg && isIterateeCall(collection, iteratee, thisArg)) {
iteratee = null;
}
iteratee = getCallback(iteratee, thisArg, 3);
var index = -1,
length = collection ? collection.length : 0,
multi = iteratee && isArray(iteratee),
result = isLength(length) ? Array(length) : [];
if (!multi) {
iteratee = getCallback(iteratee, thisArg, 3);
}
baseEach(collection, function(value, key, collection) {
if (multi) {
var length = iteratee.length,
criteria = Array(length);
result[++index] = { 'criteria': iteratee(value, key, collection), 'index': index, 'value': value };
});
return baseSortBy(result, compareAscending);
}
while (length--) {
criteria[length] = value == null ? undefined : value[iteratee[length]];
}
} else {
criteria = iteratee(value, key, collection);
/**
* This method is like `_.sortBy` except that it sorts by property names
* instead of an iteratee function.
*
* @static
* @memberOf _
* @category Collection
* @param {Array|Object|string} collection The collection to iterate over.
* @param {...(string|string[])} props The property names to sort by,
* specified as individual property names or arrays of property names.
* @returns {Array} Returns the new sorted array.
* @example
*
* var users = [
* { 'user': 'barney', 'age': 36 },
* { 'user': 'fred', 'age': 40 },
* { 'user': 'barney', 'age': 26 },
* { 'user': 'fred', 'age': 30 }
* ];
*
* _.map(_.sortBy(users, ['user', 'age']), _.values);
* // => [['barney', 26], ['barney', 36], ['fred', 30], ['fred', 40]]
*/
function sortByMultiple(collection) {
var args = arguments;
if (args.length == 4 && isIterateeCall(args[1], args[2], args[3])) {
args = [collection, args[1]];
}
var index = -1,
length = collection ? collection.length : 0,
props = baseFlatten(args, false, false, 1),
result = isLength(length) ? Array(length) : [];
baseEach(collection, function(value, key, collection) {
var length = props.length,
criteria = Array(length);
while (length--) {
criteria[length] = value == null ? undefined : value[props[length]];
}
result[++index] = { 'criteria': criteria, 'index': index, 'value': value };
});
length = result.length;
result.sort(multi ? compareMultipleAscending : compareAscending);
while (length--) {
result[length] = result[length].value;
}
return result;
return baseSortBy(result, compareMultipleAscending);
}
/**
@@ -9965,6 +10004,7 @@
lodash.shuffle = shuffle;
lodash.slice = slice;
lodash.sortBy = sortBy;
lodash.sortByMultiple = sortByMultiple;
lodash.take = take;
lodash.takeRight = takeRight;
lodash.takeRightWhile = takeRightWhile;

View File

@@ -10712,8 +10712,8 @@
});
test('should work with a "_.pluck" style `iteratee`', 1, function() {
var actual = _.pluck(_.sortBy(objects, 'b'), 'b');
deepEqual(actual, [1, 2, 3, 4]);
var actual = _.pluck(_.sortBy(objects.concat(undefined), 'b'), 'b');
deepEqual(actual, [1, 2, 3, 4, undefined]);
});
test('should work with an object for `collection`', 1, function() {
@@ -10728,21 +10728,6 @@
deepEqual(_.sortBy(1), []);
});
test('should support sorting by an array of properties', 1, function() {
var actual = _.sortBy(objects, ['a', 'b']);
deepEqual(actual, [objects[2], objects[0], objects[3], objects[1]]);
});
test('should not error on nullish elements when sorting by multiple properties', 1, function() {
var actual = _.sortBy(objects.concat(undefined), ['a', 'b']);
deepEqual(actual, [objects[2], objects[0], objects[3], objects[1], undefined]);
});
test('should perform a stable sort when sorting by multiple properties (test in IE > 8, Opera, and V8)', 1, function() {
var actual = _.sortBy(stableOrder, ['a', 'c']);
deepEqual(actual, stableOrder);
});
test('should coerce arrays returned from `iteratee`', 1, function() {
var actual = _.sortBy(objects, function(object) {
var result = [object.a, object.b];
@@ -10754,15 +10739,72 @@
});
test('should work as an iteratee for `_.map`', 1, function() {
var array = [[2, 1, 3], [3, 2, 1]],
actual = _.map(array, _.sortBy);
var actual = _.map([[2, 1, 3], [3, 2, 1]], _.sortBy);
deepEqual(actual, [[1, 2, 3], [1, 2, 3]]);
});
}());
/*--------------------------------------------------------------------------*/
QUnit.module('lodash.sortByMultiple');
(function() {
function Pair(a, b, c) {
this.a = a;
this.b = b;
this.c = c;
}
var objects = [
{ 'a': 'x', 'b': 3 },
{ 'a': 'y', 'b': 4 },
{ 'a': 'x', 'b': 1 },
{ 'a': 'y', 'b': 2 }
];
var stableOrder = [
new Pair(1, 1, 1), new Pair(1, 2, 1),
new Pair(1, 1, 1), new Pair(1, 2, 1),
new Pair(1, 3, 1), new Pair(1, 4, 1),
new Pair(1, 5, 1), new Pair(1, 6, 1),
new Pair(2, 1, 2), new Pair(2, 2, 2),
new Pair(2, 3, 2), new Pair(2, 4, 2),
new Pair(2, 5, 2), new Pair(2, 6, 2),
new Pair(undefined, 1, 1), new Pair(undefined, 2, 1),
new Pair(undefined, 3, 1), new Pair(undefined, 4, 1),
new Pair(undefined, 5, 1), new Pair(undefined, 6, 1)
];
test('should sort mutliple properties in ascending order', 1, function() {
var actual = _.sortByMultiple(objects, ['a', 'b']);
deepEqual(actual, [objects[2], objects[0], objects[3], objects[1]]);
});
test('should perform a stable sort (test in IE > 8, Opera, and V8)', 1, function() {
var actual = _.sortByMultiple(stableOrder, ['a', 'c']);
deepEqual(actual, stableOrder);
});
test('should not error on nullish elements', 1, function() {
var actual = _.sortByMultiple(objects.concat(undefined), ['a', 'b']);
deepEqual(actual, [objects[2], objects[0], objects[3], objects[1], undefined]);
});
test('should work as an iteratee for `_.reduce`', 1, function() {
var objects = [
{ 'a': 'x', '0': 3 },
{ 'a': 'y', '0': 4 },
{ 'a': 'x', '0': 1 },
{ 'a': 'y', '0': 2 }
];
var actual = _.reduce([['a']], _.sortByMultiple, objects);
deepEqual(actual, [objects[0], objects[2], objects[1], objects[3]]);
});
}());
/*--------------------------------------------------------------------------*/
QUnit.module('lodash.sortedIndex');
(function() {
@@ -13409,6 +13451,7 @@
'sample',
'shuffle',
'sortBy',
'sortByMultiple',
'take',
'times',
'toArray',
@@ -13447,7 +13490,7 @@
var acceptFalsey = _.difference(allMethods, rejectFalsey);
test('should accept falsey arguments', 200, function() {
test('should accept falsey arguments', 202, function() {
var emptyArrays = _.map(falsey, _.constant([])),
isExposed = '_' in root,
oldDash = root._;
@@ -13489,7 +13532,7 @@
});
});
test('should return an array', 68, function() {
test('should return an array', 70, function() {
var array = [1, 2, 3];
_.each(returnArrays, function(methodName) {