diff --git a/lodash.src.js b/lodash.src.js index 0720007dd..bd7157a1b 100644 --- a/lodash.src.js +++ b/lodash.src.js @@ -5328,18 +5328,14 @@ * [`SameValueZero`](http://ecma-international.org/ecma-262/6.0/#sec-samevaluezero) * for equality comparisons, in which only the first occurence of each element * is kept. Providing `true` for `isSorted` performs a faster search algorithm - * for sorted arrays. If an iteratee function is provided it's invoked for - * each element in the array to generate the criterion by which uniqueness - * is computed. The iteratee is invoked with three arguments: (value, index, array). + * for sorted arrays. * * @static * @memberOf _ - * @alias unique * @category Array * @param {Array} array The array to inspect. * @param {boolean} [isSorted] Specify the array is sorted. - * @param {Function|Object|string} [iteratee=_.identity] The function invoked per iteration. - * @returns {Array} Returns the new duplicate-value-free array. + * @returns {Array} Returns the new duplicate free array. * @example * * _.uniq([2, 1, 2]); @@ -5348,18 +5344,42 @@ * // using `isSorted` * _.uniq([1, 1, 2], true); * // => [1, 2] + */ + function uniq(array, isSorted) { + var length = array ? array.length : 0; + if (!length) { + return []; + } + return (isSorted && typeof isSorted == 'boolean' && getIndexOf() == baseIndexOf) + ? sortedUniq(array) + : baseUniq(array); + } + + /** + * This method is like `_.uniq` except that it accepts an iteratee which is + * invoked for each value in `array` to generate the criterion by which + * uniqueness is computed. The iteratee is invoked with three arguments: + * (value, index, array). * - * // using an iteratee function - * _.uniq([1, 2.5, 1.5, 2], function(n) { + * @static + * @memberOf _ + * @category Array + * @param {Array} array The array to inspect. + * @param {boolean} [isSorted] Specify the array is sorted. + * @param {Function|Object|string} [iteratee=_.identity] The function invoked per iteration. + * @returns {Array} Returns the new duplicate free array. + * @example + * + * _.uniqBy([1, 2.5, 1.5, 2], function(n) { * return Math.floor(n); * }); * // => [1, 2.5] * * // using the `_.property` callback shorthand - * _.uniq([{ 'x': 1 }, { 'x': 2 }, { 'x': 1 }], 'x'); + * _.uniqBy([{ 'x': 1 }, { 'x': 2 }, { 'x': 1 }], 'x'); * // => [{ 'x': 1 }, { 'x': 2 }] */ - function uniq(array, isSorted, iteratee) { + function uniqBy(array, isSorted, iteratee) { var length = array ? array.length : 0; if (!length) { return []; @@ -11444,6 +11464,7 @@ lodash.transform = transform; lodash.union = union; lodash.uniq = uniq; + lodash.uniqBy = uniqBy; lodash.unzip = unzip; lodash.unzipWith = unzipWith; lodash.values = values; diff --git a/test/test.js b/test/test.js index be4d9ec30..326341a1f 100644 --- a/test/test.js +++ b/test/test.js @@ -5963,8 +5963,8 @@ } }); - test('`_.uniq` should work with a custom `_.indexOf` method', 6, function() { - _.each([false, true, _.identity], function(param) { + test('`_.uniq` should work with a custom `_.indexOf` method', 4, function() { + _.each([false, true], function(param) { if (!isModularize) { _.indexOf = custom; deepEqual(_.uniq(array, param), array.slice(0, 3)); @@ -5976,6 +5976,20 @@ } }); }); + + test('`_.uniqBy` should work with a custom `_.indexOf` method', 6, function() { + _.each([[false, _.identity], [true, _.identity], [_.identity]], function(params) { + if (!isModularize) { + _.indexOf = custom; + deepEqual(_.uniqBy.apply(_, [array].concat(params)), array.slice(0, 3)); + deepEqual(_.uniqBy.apply(_, [largeArray].concat(params)), [largeArray[0]]); + _.indexOf = indexOf; + } + else { + skipTest(2); + } + }); + }); }()); /*--------------------------------------------------------------------------*/ @@ -8790,10 +8804,10 @@ } }); - test('`_.uniq` should use `_.iteratee` internally', 1, function() { + test('`_.uniqBy` should use `_.iteratee` internally', 1, function() { if (!isModularize) { _.iteratee = getPropB; - deepEqual(_.uniq(objects), [objects[0], objects[2]]); + deepEqual(_.uniqBy(objects), [objects[0], objects[2]]); _.iteratee = iteratee; } else { @@ -13831,7 +13845,7 @@ return _.shuffle([1, 2]); }); - deepEqual(_.sortBy(_.uniq(actual, String), '0'), [[1, 2], [2, 1]]); + deepEqual(_.sortBy(_.uniqBy(actual, String), '0'), [[1, 2], [2, 1]]); }); test('should treat number values for `collection` as empty', 1, function() { @@ -16077,42 +16091,17 @@ /*--------------------------------------------------------------------------*/ - QUnit.module('lodash.uniq'); + QUnit.module('lodash.uniqBy'); (function() { var objects = [{ 'a': 2 }, { 'a': 3 }, { 'a': 1 }, { 'a': 2 }, { 'a': 3 }, { 'a': 1 }]; - test('should return unique values of an unsorted array', 1, function() { - var array = [2, 3, 1, 2, 3, 1]; - deepEqual(_.uniq(array), [2, 3, 1]); - }); - - test('should return unique values of a sorted array', 1, function() { - var array = [1, 1, 2, 2, 3]; - deepEqual(_.uniq(array), [1, 2, 3]); - }); - - test('should treat object instances as unique', 1, function() { - deepEqual(_.uniq(objects), objects); - }); - - test('should not treat `NaN` as unique', 1, function() { - deepEqual(_.uniq([1, NaN, 3, NaN]), [1, NaN, 3]); - }); - - test('should work with `isSorted`', 3, function() { - var expected = [1, 2, 3]; - deepEqual(_.uniq([1, 2, 3], true), expected); - deepEqual(_.uniq([1, 1, 2, 2, 3], true), expected); - deepEqual(_.uniq([1, 2, 3, 3, 3, 3, 3], true), expected); - }); - test('should work with an `iteratee` argument', 2, function() { _.each([objects, _.sortBy(objects, 'a')], function(array, index) { var isSorted = !!index, expected = isSorted ? [objects[2], objects[0], objects[1]] : objects.slice(0, 3); - var actual = _.uniq(array, isSorted, function(object) { + var actual = _.uniqBy(array, isSorted, function(object) { return object.a; }); @@ -16123,7 +16112,7 @@ test('should provide the correct `iteratee` arguments', 1, function() { var args; - _.uniq(objects, function() { + _.uniqBy(objects, function() { args || (args = slice.call(arguments)); }); @@ -16131,7 +16120,7 @@ }); test('should work with `iteratee` without specifying `isSorted`', 1, function() { - var actual = _.uniq(objects, function(object) { + var actual = _.uniqBy(objects, function(object) { return object.a; }); @@ -16139,24 +16128,71 @@ }); test('should work with a "_.property" style `iteratee`', 2, function() { - var actual = _.uniq(objects, 'a'); + var actual = _.uniqBy(objects, 'a'); deepEqual(actual, objects.slice(0, 3)); var arrays = [[2], [3], [1], [2], [3], [1]]; - actual = _.uniq(arrays, 0); + actual = _.uniqBy(arrays, 0); deepEqual(actual, arrays.slice(0, 3)); }); - test('should perform an unsorted uniq when used as an iteratee for methods like `_.map`', 1, function() { + _.each({ + 'an array': [0, 'a'], + 'an object': { '0': 'a' }, + 'a number': 0, + 'a string': '0' + }, + function(iteratee, key) { + test('should work with ' + key + ' for `iteratee`', 1, function() { + var actual = _.uniqBy([['a'], ['b'], ['a']], iteratee); + deepEqual(actual, [['a'], ['b']]); + }); + }); + }()); + + /*--------------------------------------------------------------------------*/ + + QUnit.module('uniq methods'); + + _.each(['uniq', 'uniqBy'], function(methodName) { + var func = _[methodName], + objects = [{ 'a': 2 }, { 'a': 3 }, { 'a': 1 }, { 'a': 2 }, { 'a': 3 }, { 'a': 1 }]; + + test('`_.' + methodName + '` should return unique values of an unsorted array', 1, function() { + var array = [2, 3, 1, 2, 3, 1]; + deepEqual(func(array), [2, 3, 1]); + }); + + test('`_.' + methodName + '` should return unique values of a sorted array', 1, function() { + var array = [1, 1, 2, 2, 3]; + deepEqual(func(array), [1, 2, 3]); + }); + + test('`_.' + methodName + '` should treat object instances as unique', 1, function() { + deepEqual(func(objects), objects); + }); + + test('`_.' + methodName + '` should not treat `NaN` as unique', 1, function() { + deepEqual(func([1, NaN, 3, NaN]), [1, NaN, 3]); + }); + + test('`_.' + methodName + '` should work with `isSorted`', 3, function() { + var expected = [1, 2, 3]; + deepEqual(func([1, 2, 3], true), expected); + deepEqual(func([1, 1, 2, 2, 3], true), expected); + deepEqual(func([1, 2, 3, 3, 3, 3, 3], true), expected); + }); + + test('`_.' + methodName + '` should perform an unsorted uniq when used as an iteratee for methods like `_.map`', 1, function() { var array = [[2, 1, 2], [1, 2, 1]], - actual = _.map(array, _.uniq); + actual = _.map(array, func); deepEqual(actual, [[2, 1], [1, 2]]); }); - test('should work with large arrays', 1, function() { + test('`_.' + methodName + '` should work with large arrays', 1, function() { var largeArray = [], expected = [0, 'a', {}], count = Math.ceil(LARGE_ARRAY_SIZE / expected.length); @@ -16165,10 +16201,10 @@ push.apply(largeArray, expected); }); - deepEqual(_.uniq(largeArray), expected); + deepEqual(func(largeArray), expected); }); - test('should work with large arrays of boolean, `NaN`, and nullish values', 1, function() { + test('`_.' + methodName + '` should work with large arrays of boolean, `NaN`, and nullish values', 1, function() { var largeArray = [], expected = [true, false, NaN, null, undefined], count = Math.ceil(LARGE_ARRAY_SIZE / expected.length); @@ -16177,24 +16213,24 @@ push.apply(largeArray, expected); }); - deepEqual(_.uniq(largeArray), expected); + deepEqual(func(largeArray), expected); }); - test('should work with large arrays of symbols', 1, function() { + test('`_.' + methodName + '` should work with large arrays of symbols', 1, function() { if (Symbol) { var largeArray = _.times(LARGE_ARRAY_SIZE, function() { return Symbol(); }); - deepEqual(_.uniq(largeArray), largeArray); + deepEqual(func(largeArray), largeArray); } else { skipTest(); } }); - test('should work with large arrays of well-known symbols', 1, function() { - // See https://people.mozilla.org/~jorendorff/es6-draft.html#sec-well-known-symbols. + test('`_.' + methodName + '` should work with large arrays of well-known symbols', 1, function() { + // See http://www.ecma-international.org/ecma-262/6.0/#sec-well-known-symbols. if (Symbol) { var expected = [ Symbol.hasInstance, Symbol.isConcatSpreadable, Symbol.iterator, @@ -16213,14 +16249,14 @@ push.apply(largeArray, expected); }); - deepEqual(_.uniq(largeArray), expected); + deepEqual(func(largeArray), expected); } else { skipTest(); } }); - test('should distinguish between numbers and numeric strings', 1, function() { + test('`_.' + methodName + '` should distinguish between numbers and numeric strings', 1, function() { var array = [], expected = ['2', 2, Object('2'), Object(2)], count = Math.ceil(LARGE_ARRAY_SIZE / expected.length); @@ -16229,22 +16265,9 @@ push.apply(array, expected); }); - deepEqual(_.uniq(array), expected); + deepEqual(func(array), expected); }); - - _.each({ - 'an array': [0, 'a'], - 'an object': { '0': 'a' }, - 'a number': 0, - 'a string': '0' - }, - function(callback, key) { - test('should work with ' + key + ' for `iteratee`', 1, function() { - var actual = _.uniq([['a'], ['b'], ['a']], callback); - deepEqual(actual, [['a'], ['b']]); - }); - }); - }()); + }); /*--------------------------------------------------------------------------*/ @@ -17515,7 +17538,7 @@ var acceptFalsey = _.difference(allMethods, rejectFalsey); - test('should accept falsey arguments', 207, function() { + test('should accept falsey arguments', 208, function() { var emptyArrays = _.map(falsey, _.constant([])); _.each(acceptFalsey, function(methodName) {