diff --git a/build.js b/build.js index 55ea15f09..3d024c1b4 100755 --- a/build.js +++ b/build.js @@ -171,6 +171,7 @@ 'union': ['uniq'], 'uniq': ['createCallback', 'indexOf'], 'uniqueId': [], + 'unzip': ['max', 'pluck'], 'value': ['forOwn', 'isArray'], 'values': ['keys'], 'where': ['filter'], @@ -258,8 +259,8 @@ 'without' ]; - /** List of methods used by Underscore */ - var underscoreMethods = _.without.apply(_, [allMethods].concat([ + /** List of Lo-Dash only methods */ + var lodashOnlyMethods = [ 'at', 'bindKey', 'cloneDeep', @@ -272,8 +273,12 @@ 'merge', 'parseInt', 'partialRight', - 'runInContext' - ])); + 'runInContext', + 'unzip' + ]; + + /** List of methods used by Underscore */ + var underscoreMethods = _.without.apply(_, [allMethods].concat(lodashOnlyMethods)); /** List of ways to export the `lodash` function */ var exportsAll = [ diff --git a/build/pre-compile.js b/build/pre-compile.js index 8e403158b..0e6b62360 100644 --- a/build/pre-compile.js +++ b/build/pre-compile.js @@ -210,6 +210,7 @@ 'uniq', 'unique', 'uniqueId', + 'unzip', 'value', 'values', 'variable', diff --git a/lodash.js b/lodash.js index 1bb429ace..8818aa2ee 100644 --- a/lodash.js +++ b/lodash.js @@ -222,8 +222,8 @@ * `invoke`, `keys`, `map`, `max`, `memoize`, `merge`, `min`, `object`, `omit`, * `once`, `pairs`, `partial`, `partialRight`, `pick`, `pluck`, `push`, `range`, * `reject`, `rest`, `reverse`, `shuffle`, `slice`, `sort`, `sortBy`, `splice`, - * `tap`, `throttle`, `times`, `toArray`, `union`, `uniq`, `unshift`, `values`, - * `where`, `without`, `wrap`, and `zip` + * `tap`, `throttle`, `times`, `toArray`, `union`, `uniq`, `unshift`, `unzip`, + * `values`, `where`, `without`, `wrap`, and `zip` * * The non-chainable wrapper functions are: * `clone`, `cloneDeep`, `contains`, `escape`, `every`, `find`, `has`, @@ -4051,6 +4051,37 @@ return result; } + /** + * The inverse of `_.zip`, this method splits groups of elements into arrays + * composed of elements from each group at their corresponding indexes. + * + * @static + * @memberOf _ + * @category Arrays + * @param {Array} array The array to process. + * @returns {Array} Returns a new array of the composed arrays. + * @example + * + * _.unzip([['moe', 30, true], ['larry', 40, false]]); + * // => [['moe', 'larry'], [30, 40], [true, false]]; + */ + function unzip(array) { + var index = -1, + length = array ? array.length : 0, + tupleLength = length ? max(pluck(array, 'length')) : 0, + result = Array(tupleLength); + + while (++index < length) { + var tupleIndex = -1, + tuple = array[index]; + + while (++tupleIndex < tupleLength) { + (result[tupleIndex] || (result[tupleIndex] = Array(length)))[index] = tuple[tupleIndex]; + } + } + return result; + } + /** * Creates an array with all occurrences of the passed values removed using * strict equality for comparisons, i.e. `===`. @@ -5242,6 +5273,7 @@ lodash.toArray = toArray; lodash.union = union; lodash.uniq = uniq; + lodash.unzip = unzip; lodash.values = values; lodash.where = where; lodash.without = without; diff --git a/perf/perf.js b/perf/perf.js index 42d72b549..40e8a7020 100644 --- a/perf/perf.js +++ b/perf/perf.js @@ -487,6 +487,10 @@ var _findWhere = _.findWhere || _.find,\ lodashFindWhere = lodash.findWhere || lodash.find,\ whereObject = { "num": 9 };\ + }\ + if (typeof zip != "undefined") {\ + var unzipped = [["a", "b", "c"], [1, 2, 3], [true, false, true]],\ + zipped = [["a", 1, true], ["b", 2, false], ["c", 3, true]];\ }' }); @@ -1752,6 +1756,20 @@ /*--------------------------------------------------------------------------*/ + suites.push( + Benchmark.Suite('`_.unzip`') + .add(buildName, { + 'fn': 'lodash.unzip(zipped);', + 'teardown': 'function zip(){}' + }) + .add(otherName, { + 'fn': '_.unzip(zipped);', + 'teardown': 'function zip(){}' + }) + ); + + /*--------------------------------------------------------------------------*/ + suites.push( Benchmark.Suite('`_.values`') .add(buildName, '\ @@ -1802,6 +1820,20 @@ /*--------------------------------------------------------------------------*/ + suites.push( + Benchmark.Suite('`_.zip`') + .add(buildName, { + 'fn': 'lodash.zip.apply(lodash, unzipped);', + 'teardown': 'function zip(){}' + }) + .add(otherName, { + 'fn': '_.zip.apply(_, unzipped);', + 'teardown': 'function zip(){}' + }) + ); + + /*--------------------------------------------------------------------------*/ + if (Benchmark.platform + '') { log(Benchmark.platform); } diff --git a/test/test-build.js b/test/test-build.js index ce28577b0..cd9053552 100644 --- a/test/test-build.js +++ b/test/test-build.js @@ -118,6 +118,7 @@ 'union', 'uniq', 'unique', + 'unzip', 'without', 'zip', 'zipObject' @@ -296,8 +297,8 @@ 'without' ]; - /** List of methods used by Underscore */ - var underscoreMethods = _.without.apply(_, [allMethods].concat([ + /** List of Lo-Dash only methods */ + var lodashOnlyMethods = [ 'at', 'bindKey', 'cloneDeep', @@ -310,8 +311,12 @@ 'merge', 'parseInt', 'partialRight', - 'runInContext' - ])); + 'runInContext', + 'unzip' + ]; + + /** List of methods used by Underscore */ + var underscoreMethods = _.without.apply(_, [allMethods].concat(lodashOnlyMethods)); /*--------------------------------------------------------------------------*/ @@ -970,22 +975,7 @@ vm.runInContext(data.source, context); var lodash = context._; - _.each([ - 'assign', - 'at', - 'bindKey', - 'createCallback', - 'findIndex', - 'findKey', - 'forIn', - 'forOwn', - 'isPlainObject', - 'merge', - 'parseInt', - 'partialRight', - 'runInContext', - 'zipObject' - ], function(methodName) { + _.each(lodashOnlyMethods.concat('assign'), function(methodName) { equal(lodash[methodName], undefined, '_.' + methodName + ' should not exist: ' + basename); }); diff --git a/test/test.js b/test/test.js index b6100971f..3214eb544 100644 --- a/test/test.js +++ b/test/test.js @@ -2787,6 +2787,58 @@ /*--------------------------------------------------------------------------*/ + QUnit.module('lodash.unzip'); + + (function() { + var object = { + 'an empty array': [ + [], + [] + ], + '0-tuples': [ + [[], []], + [] + ], + '1-tuples': [ + [['moe'], ['larry']], + [['moe', 'larry']] + ], + '2-tuples': [ + [['moe', 30], ['larry', 40]], + [['moe', 'larry'], [30, 40]] + ], + '3-tuples': [ + [['moe', 30, true], ['larry', 40, false]], + [['moe', 'larry'], [30, 40], [true, false]] + ] + }; + + _.forOwn(object, function(pair, key) { + test('should work with ' + key, function() { + var actual = _.unzip(pair[0]); + deepEqual(actual, pair[1]); + deepEqual(_.zip.apply(_, actual), pair[1].length ? pair[0] : pair[1]); + }); + }); + + test('should work with tuples of different lengths', function() { + var pair = [ + [['moe', 30], ['larry', 40, false]], + [['moe', 'larry'], [30, 40], [undefined, false]] + ]; + + var actual = _.unzip(pair[0]); + ok(1 in actual); + deepEqual(actual, pair[1]); + + actual = _.zip.apply(_, actual); + ok(2 in actual[0]); + deepEqual(actual, [['moe', 30, undefined], ['larry', 40, false]]); + }); + }()); + + /*--------------------------------------------------------------------------*/ + QUnit.module('lodash.where'); (function() {