diff --git a/lodash.src.js b/lodash.src.js index aef4caa9d..980f21ff7 100644 --- a/lodash.src.js +++ b/lodash.src.js @@ -1987,7 +1987,7 @@ * @param {*} exValue The initial extremum value. * @returns {*} Returns the extremum value. */ - function baseExtremum(collection, iteratee, comparator, exValue) { + function baseExtremumBy(collection, iteratee, comparator, exValue) { var computed = exValue, result = computed; @@ -3302,11 +3302,24 @@ * Creates a `_.max` or `_.min` function. * * @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 {*} exValue The initial extremum value. * @returns {Function} Returns the new extremum function. */ - function createExtremum(comparator, exValue) { + function createExtremumBy(comparator, exValue) { return function(collection, iteratee, guard) { if (guard && isIterateeCall(collection, iteratee, guard)) { iteratee = undefined; @@ -3314,12 +3327,15 @@ iteratee = getIteratee(iteratee); if (iteratee.length == 1) { collection = isArray(collection) ? collection : toIterable(collection); + if (!collection.length) { + return exValue; + } var result = arrayExtremum(collection, iteratee, comparator, exValue); - if (!(collection.length && result === exValue)) { + if (result !== exValue) { return result; } } - return baseExtremum(collection, iteratee, comparator, exValue); + return baseExtremumBy(collection, iteratee, comparator, exValue); }; } @@ -11291,10 +11307,10 @@ var floor = createRound('floor'); /** - * Gets the maximum value of `collection`. If `collection` is empty or falsey - * `-Infinity` is returned. If an iteratee function is provided it's 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). + * This method is like `_.max` 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 * @memberOf _ @@ -11304,31 +11320,44 @@ * @returns {*} Returns the maximum value. * @example * - * _.max([4, 2, 8, 6]); - * // => 8 - * - * _.max([]); - * // => -Infinity - * * var users = [ * { 'user': 'barney', 'age': 36 }, * { 'user': 'fred', 'age': 40 } * ]; * - * _.max(users, function(o) { return o.age; }); + * _.maxBy(users, function(o) { return o.age; }); * // => { 'user': 'fred', 'age': 40 } * * // using the `_.property` callback shorthand - * _.max(users, 'age'); + * _.maxBy(users, 'age'); * // => { '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 - * `Infinity` is returned. If an iteratee function is provided it's 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). + * Gets the maximum 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 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 * @memberOf _ @@ -11338,25 +11367,38 @@ * @returns {*} Returns the minimum value. * @example * - * _.min([4, 2, 8, 6]); - * // => 2 - * - * _.min([]); - * // => Infinity - * * var users = [ * { 'user': 'barney', 'age': 36 }, * { 'user': 'fred', 'age': 40 } * ]; * - * _.min(users, function(o) { return o.age; }); + * _.minBy(users, function(o) { return o.age; }); * // => { 'user': 'barney', 'age': 36 } * * // using the `_.property` callback shorthand - * _.min(users, 'age'); + * _.minBy(users, 'age'); * // => { '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`. @@ -11616,7 +11658,9 @@ lodash.lt = lt; lodash.lte = lte; lodash.max = max; + lodash.maxBy = maxBy; lodash.min = min; + lodash.minBy = minBy; lodash.noConflict = noConflict; lodash.noop = noop; lodash.now = now; diff --git a/test/test.js b/test/test.js index 075178d78..be4d9ec30 100644 --- a/test/test.js +++ b/test/test.js @@ -4724,8 +4724,8 @@ 'map', 'mapKeys', 'mapValues', - 'max', - 'min', + 'maxBy', + 'minBy', 'omit', 'partition', 'pick', @@ -4750,8 +4750,8 @@ 'groupBy', 'indexBy', 'map', - 'max', - 'min', + 'maxBy', + 'minBy', 'partition', 'reduce', 'reduceRight', @@ -4813,7 +4813,9 @@ 'forOwn', 'forOwnRight', 'max', + 'maxBy', 'min', + 'minBy', 'some' ]; @@ -8607,10 +8609,10 @@ } }); - test('`_.max` should use `_.iteratee` internally', 1, function() { + test('`_.maxBy` should use `_.iteratee` internally', 1, function() { if (!isModularize) { _.iteratee = getPropB; - deepEqual(_.max(objects), objects[2]); + deepEqual(_.maxBy(objects), objects[2]); _.iteratee = iteratee; } else { @@ -8618,10 +8620,10 @@ } }); - test('`_.min` should use `_.iteratee` internally', 1, function() { + test('`_.minBy` should use `_.iteratee` internally', 1, function() { if (!isModularize) { _.iteratee = getPropB; - deepEqual(_.min(objects), objects[0]); + deepEqual(_.minBy(objects), objects[0]); _.iteratee = iteratee; } else { @@ -10833,10 +10835,10 @@ QUnit.module('extremum methods'); - _.each(['max', 'min'], function(methodName) { + _.each(['max', 'maxBy', 'min', 'minBy'], function(methodName) { var array = [1, 2, 3], func = _[methodName], - isMax = methodName == 'max'; + isMax = /^max/.test(methodName); test('`_.' + methodName + '` should work with Date objects', 1, function() { var curr = new Date, @@ -10845,63 +10847,6 @@ 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() { var actual = func({ 'a': 1, 'b': 2, 'c': 3 }); 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'); @@ -17318,7 +17300,9 @@ 'join', 'last', 'max', + 'maxBy', 'min', + 'minBy', 'parseInt', 'pop', 'shift', @@ -17531,7 +17515,7 @@ var acceptFalsey = _.difference(allMethods, rejectFalsey); - test('should accept falsey arguments', 205, function() { + test('should accept falsey arguments', 207, function() { var emptyArrays = _.map(falsey, _.constant([])); _.each(acceptFalsey, function(methodName) {