Split _.max and _.min out into _.maxBy and _.minBy.

This commit is contained in:
jdalton
2015-07-05 10:19:52 -07:00
committed by John-David Dalton
parent cb94b03e3e
commit 86b19f742c
2 changed files with 126 additions and 98 deletions

View File

@@ -1987,7 +1987,7 @@
* @param {*} exValue The initial extremum value. * @param {*} exValue The initial extremum value.
* @returns {*} Returns the extremum value. * @returns {*} Returns the extremum value.
*/ */
function baseExtremum(collection, iteratee, comparator, exValue) { function baseExtremumBy(collection, iteratee, comparator, exValue) {
var computed = exValue, var computed = exValue,
result = computed; result = computed;
@@ -3302,11 +3302,24 @@
* Creates a `_.max` or `_.min` function. * Creates a `_.max` or `_.min` function.
* *
* @private * @private
* @param {Function} extremumBy The function used to get the extremum value.
* @returns {Function} Returns the new extremum function.
*/
function createExtremum(extremumBy) {
return function(collection) {
return extremumBy(collection, identity);
};
}
/**
* Creates a `_.maxBy` or `_.minBy` function.
*
* @private
* @param {Function} comparator The function used to compare values. * @param {Function} comparator The function used to compare values.
* @param {*} exValue The initial extremum value. * @param {*} exValue The initial extremum value.
* @returns {Function} Returns the new extremum function. * @returns {Function} Returns the new extremum function.
*/ */
function createExtremum(comparator, exValue) { function createExtremumBy(comparator, exValue) {
return function(collection, iteratee, guard) { return function(collection, iteratee, guard) {
if (guard && isIterateeCall(collection, iteratee, guard)) { if (guard && isIterateeCall(collection, iteratee, guard)) {
iteratee = undefined; iteratee = undefined;
@@ -3314,12 +3327,15 @@
iteratee = getIteratee(iteratee); iteratee = getIteratee(iteratee);
if (iteratee.length == 1) { if (iteratee.length == 1) {
collection = isArray(collection) ? collection : toIterable(collection); collection = isArray(collection) ? collection : toIterable(collection);
if (!collection.length) {
return exValue;
}
var result = arrayExtremum(collection, iteratee, comparator, exValue); var result = arrayExtremum(collection, iteratee, comparator, exValue);
if (!(collection.length && result === exValue)) { if (result !== exValue) {
return result; return result;
} }
} }
return baseExtremum(collection, iteratee, comparator, exValue); return baseExtremumBy(collection, iteratee, comparator, exValue);
}; };
} }
@@ -11291,10 +11307,10 @@
var floor = createRound('floor'); var floor = createRound('floor');
/** /**
* Gets the maximum value of `collection`. If `collection` is empty or falsey * This method is like `_.max` except that it accepts an iteratee which is
* `-Infinity` is returned. If an iteratee function is provided it's invoked * invoked for each value in `collection` to generate the criterion by which
* for each value in `collection` to generate the criterion by which the value * the value is ranked. The iteratee is invoked with three arguments:
* is ranked. The iteratee is invoked with three arguments: (value, index, collection). * (value, index, collection).
* *
* @static * @static
* @memberOf _ * @memberOf _
@@ -11304,31 +11320,44 @@
* @returns {*} Returns the maximum value. * @returns {*} Returns the maximum value.
* @example * @example
* *
* _.max([4, 2, 8, 6]);
* // => 8
*
* _.max([]);
* // => -Infinity
*
* var users = [ * var users = [
* { 'user': 'barney', 'age': 36 }, * { 'user': 'barney', 'age': 36 },
* { 'user': 'fred', 'age': 40 } * { 'user': 'fred', 'age': 40 }
* ]; * ];
* *
* _.max(users, function(o) { return o.age; }); * _.maxBy(users, function(o) { return o.age; });
* // => { 'user': 'fred', 'age': 40 } * // => { 'user': 'fred', 'age': 40 }
* *
* // using the `_.property` callback shorthand * // using the `_.property` callback shorthand
* _.max(users, 'age'); * _.maxBy(users, 'age');
* // => { 'user': 'fred', 'age': 40 } * // => { 'user': 'fred', 'age': 40 }
*/ */
var max = createExtremum(gt, NEGATIVE_INFINITY); var maxBy = createExtremumBy(gt, NEGATIVE_INFINITY);
/** /**
* Gets the minimum value of `collection`. If `collection` is empty or falsey * Gets the maximum value of `collection`. If `collection` is empty or falsey
* `Infinity` is returned. If an iteratee function is provided it's invoked * `-Infinity` is returned.
* for each value in `collection` to generate the criterion by which the value *
* is ranked. The iteratee is invoked with three arguments: (value, index, collection). * @static
* @memberOf _
* @category Math
* @param {Array|Object|string} collection The collection to iterate over.
* @returns {*} Returns the maximum value.
* @example
*
* _.max([4, 2, 8, 6]);
* // => 8
*
* _.max([]);
* // => -Infinity
*/
var max = createExtremum(maxBy);
/**
* This method is like `_.min` except that it accepts an iteratee which is
* invoked for each value in `collection` to generate the criterion by which
* the value is ranked. The iteratee is invoked with three arguments:
* (value, index, collection).
* *
* @static * @static
* @memberOf _ * @memberOf _
@@ -11338,25 +11367,38 @@
* @returns {*} Returns the minimum value. * @returns {*} Returns the minimum value.
* @example * @example
* *
* _.min([4, 2, 8, 6]);
* // => 2
*
* _.min([]);
* // => Infinity
*
* var users = [ * var users = [
* { 'user': 'barney', 'age': 36 }, * { 'user': 'barney', 'age': 36 },
* { 'user': 'fred', 'age': 40 } * { 'user': 'fred', 'age': 40 }
* ]; * ];
* *
* _.min(users, function(o) { return o.age; }); * _.minBy(users, function(o) { return o.age; });
* // => { 'user': 'barney', 'age': 36 } * // => { 'user': 'barney', 'age': 36 }
* *
* // using the `_.property` callback shorthand * // using the `_.property` callback shorthand
* _.min(users, 'age'); * _.minBy(users, 'age');
* // => { 'user': 'barney', 'age': 36 } * // => { 'user': 'barney', 'age': 36 }
*/ */
var min = createExtremum(lt, POSITIVE_INFINITY); var minBy = createExtremumBy(lt, POSITIVE_INFINITY);
/**
* Gets the minimum value of `collection`. If `collection` is empty or falsey
* `Infinity` is returned.
*
* @static
* @memberOf _
* @category Math
* @param {Array|Object|string} collection The collection to iterate over.
* @returns {*} Returns the minimum value.
* @example
*
* _.min([4, 2, 8, 6]);
* // => 2
*
* _.min([]);
* // => Infinity
*/
var min = createExtremum(minBy);
/** /**
* Calculates `n` rounded to `precision`. * Calculates `n` rounded to `precision`.
@@ -11616,7 +11658,9 @@
lodash.lt = lt; lodash.lt = lt;
lodash.lte = lte; lodash.lte = lte;
lodash.max = max; lodash.max = max;
lodash.maxBy = maxBy;
lodash.min = min; lodash.min = min;
lodash.minBy = minBy;
lodash.noConflict = noConflict; lodash.noConflict = noConflict;
lodash.noop = noop; lodash.noop = noop;
lodash.now = now; lodash.now = now;

View File

@@ -4724,8 +4724,8 @@
'map', 'map',
'mapKeys', 'mapKeys',
'mapValues', 'mapValues',
'max', 'maxBy',
'min', 'minBy',
'omit', 'omit',
'partition', 'partition',
'pick', 'pick',
@@ -4750,8 +4750,8 @@
'groupBy', 'groupBy',
'indexBy', 'indexBy',
'map', 'map',
'max', 'maxBy',
'min', 'minBy',
'partition', 'partition',
'reduce', 'reduce',
'reduceRight', 'reduceRight',
@@ -4813,7 +4813,9 @@
'forOwn', 'forOwn',
'forOwnRight', 'forOwnRight',
'max', 'max',
'maxBy',
'min', 'min',
'minBy',
'some' 'some'
]; ];
@@ -8607,10 +8609,10 @@
} }
}); });
test('`_.max` should use `_.iteratee` internally', 1, function() { test('`_.maxBy` should use `_.iteratee` internally', 1, function() {
if (!isModularize) { if (!isModularize) {
_.iteratee = getPropB; _.iteratee = getPropB;
deepEqual(_.max(objects), objects[2]); deepEqual(_.maxBy(objects), objects[2]);
_.iteratee = iteratee; _.iteratee = iteratee;
} }
else { else {
@@ -8618,10 +8620,10 @@
} }
}); });
test('`_.min` should use `_.iteratee` internally', 1, function() { test('`_.minBy` should use `_.iteratee` internally', 1, function() {
if (!isModularize) { if (!isModularize) {
_.iteratee = getPropB; _.iteratee = getPropB;
deepEqual(_.min(objects), objects[0]); deepEqual(_.minBy(objects), objects[0]);
_.iteratee = iteratee; _.iteratee = iteratee;
} }
else { else {
@@ -10833,10 +10835,10 @@
QUnit.module('extremum methods'); QUnit.module('extremum methods');
_.each(['max', 'min'], function(methodName) { _.each(['max', 'maxBy', 'min', 'minBy'], function(methodName) {
var array = [1, 2, 3], var array = [1, 2, 3],
func = _[methodName], func = _[methodName],
isMax = methodName == 'max'; isMax = /^max/.test(methodName);
test('`_.' + methodName + '` should work with Date objects', 1, function() { test('`_.' + methodName + '` should work with Date objects', 1, function() {
var curr = new Date, var curr = new Date,
@@ -10845,63 +10847,6 @@
strictEqual(func([curr, past]), isMax ? curr : past); strictEqual(func([curr, past]), isMax ? curr : past);
}); });
test('`_.' + methodName + '` should work with an `iteratee` argument', 1, function() {
var actual = func(array, function(num) {
return -num;
});
strictEqual(actual, isMax ? 1 : 3);
});
test('`_.' + methodName + '` should provide the correct `iteratee` arguments when iterating an array', 1, function() {
var args;
func(array, function() {
args || (args = slice.call(arguments));
});
deepEqual(args, [1, 0, array]);
});
test('`_.' + methodName + '` should provide the correct `iteratee` arguments when iterating an object', 1, function() {
var args,
object = { 'a': 1, 'b': 2 },
firstKey = _.first(_.keys(object));
var expected = firstKey == 'a'
? [1, 'a', object]
: [2, 'b', object];
func(object, function() {
args || (args = slice.call(arguments));
}, 0);
deepEqual(args, expected);
});
test('should work with a "_.property" style `iteratee`', 2, function() {
var objects = [{ 'a': 2 }, { 'a': 3 }, { 'a': 1 }],
actual = func(objects, 'a');
deepEqual(actual, objects[isMax ? 1 : 2]);
var arrays = [[2], [3], [1]];
actual = func(arrays, 0);
deepEqual(actual, arrays[isMax ? 1 : 2]);
});
test('`_.' + methodName + '` should work when `iteratee` returns +/-Infinity', 1, function() {
var value = isMax ? -Infinity : Infinity,
object = { 'a': value };
var actual = func([object, { 'a': value }], function(object) {
return object.a;
});
strictEqual(actual, object);
});
test('`_.' + methodName + '` should iterate an object', 1, function() { test('`_.' + methodName + '` should iterate an object', 1, function() {
var actual = func({ 'a': 1, 'b': 2, 'c': 3 }); var actual = func({ 'a': 1, 'b': 2, 'c': 3 });
strictEqual(actual, isMax ? 3 : 1); strictEqual(actual, isMax ? 3 : 1);
@@ -10933,6 +10878,43 @@
}); });
}); });
_.each(['maxBy', 'minBy'], function(methodName) {
var array = [1, 2, 3],
func = _[methodName],
isMax = methodName == 'maxBy';
test('`_.' + methodName + '` should work with an `iteratee` argument', 1, function() {
var actual = func(array, function(num) {
return -num;
});
strictEqual(actual, isMax ? 1 : 3);
});
test('should work with a "_.property" style `iteratee`', 2, function() {
var objects = [{ 'a': 2 }, { 'a': 3 }, { 'a': 1 }],
actual = func(objects, 'a');
deepEqual(actual, objects[isMax ? 1 : 2]);
var arrays = [[2], [3], [1]];
actual = func(arrays, 0);
deepEqual(actual, arrays[isMax ? 1 : 2]);
});
test('`_.' + methodName + '` should work when `iteratee` returns +/-Infinity', 1, function() {
var value = isMax ? -Infinity : Infinity,
object = { 'a': value };
var actual = func([object, { 'a': value }], function(object) {
return object.a;
});
strictEqual(actual, object);
});
});
/*--------------------------------------------------------------------------*/ /*--------------------------------------------------------------------------*/
QUnit.module('lodash.mixin'); QUnit.module('lodash.mixin');
@@ -17318,7 +17300,9 @@
'join', 'join',
'last', 'last',
'max', 'max',
'maxBy',
'min', 'min',
'minBy',
'parseInt', 'parseInt',
'pop', 'pop',
'shift', 'shift',
@@ -17531,7 +17515,7 @@
var acceptFalsey = _.difference(allMethods, rejectFalsey); var acceptFalsey = _.difference(allMethods, rejectFalsey);
test('should accept falsey arguments', 205, function() { test('should accept falsey arguments', 207, function() {
var emptyArrays = _.map(falsey, _.constant([])); var emptyArrays = _.map(falsey, _.constant([]));
_.each(acceptFalsey, function(methodName) { _.each(acceptFalsey, function(methodName) {