Skip to content

Commit

Permalink
[#1348] initial groupBy implementation (#1364)
Browse files Browse the repository at this point in the history
* initial groupBy implementation
  • Loading branch information
hargasinski committed Feb 27, 2017
1 parent 741afb1 commit bdc3d81
Show file tree
Hide file tree
Showing 6 changed files with 478 additions and 1 deletion.
40 changes: 40 additions & 0 deletions lib/groupBy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import doLimit from './internal/doLimit';
import groupByLimit from './groupByLimit';

/**
* Returns a new object, where each value corresponds to an array of items, from
* `coll`, that returned the corresponding key. That is, the keys of the object
* correspond to the values passed to the `iteratee` callback.
*
* Note: Since this function applies the `iteratee` to each item in parallel,
* there is no guarantee that the `iteratee` functions will complete in order.
* However, the values for each key in the `result` will be in the same order as
* the original `coll`. For Objects, the values will roughly be in the order of
* the original Objects' keys (but this can vary across JavaScript engines).
*
* @name groupBy
* @static
* @memberOf module:Collections
* @method
* @category Collection
* @param {Array|Iterable|Object} coll - A collection to iterate over.
* @param {Function} iteratee - A function to apply to each item in `coll`.
* The iteratee is passed a `callback(err, key)` which must be called once it
* has completed with an error (which can be `null`) and the `key` to group the
* value under. Invoked with (value, callback).
* @param {Function} [callback] - A callback which is called when all `iteratee`
* functions have finished, or an error occurs. Result is an `Object` whoses
* properties are arrays of values which returned the corresponding key.
* @example
*
* async.groupBy(['userId1', 'userId2', 'userId3'], function(userId, callback) {
* db.findById(userId, function(err, user) {
* if (err) return callback(err);
* return callback(null, user.age);
* });
* }, function(err, result) {
* // result is object containing the userIds grouped by age
* // e.g. { 30: ['userId1', 'userId3'], 42: ['userId2']};
* });
*/
export default doLimit(groupByLimit, Infinity);
51 changes: 51 additions & 0 deletions lib/groupByLimit.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import noop from 'lodash/noop';
import mapLimit from './mapLimit';

/**
* The same as [`groupBy`]{@link module:Collections.groupBy} but runs a maximum of `limit` async operations at a time.
*
* @name groupByLimit
* @static
* @memberOf module:Collections
* @method
* @see [async.groupBy]{@link module:Collections.groupBy}
* @category Collection
* @param {Array|Iterable|Object} coll - A collection to iterate over.
* @param {number} limit - The maximum number of async operations at a time.
* @param {Function} iteratee - A function to apply to each item in `coll`.
* The iteratee is passed a `callback(err, key)` which must be called once it
* has completed with an error (which can be `null`) and the `key` to group the
* value under. Invoked with (value, callback).
* @param {Function} [callback] - A callback which is called when all `iteratee`
* functions have finished, or an error occurs. Result is an `Object` whoses
* properties are arrays of values which returned the corresponding key.
*/
export default function(coll, limit, iteratee, callback) {
callback = callback || noop;

mapLimit(coll, limit, function(val, callback) {
iteratee(val, function(err, key) {
if (err) return callback(err);
return callback(null, {key: key, val: val});
});
}, function(err, mapResults) {
var result = {};
// from MDN, handle object having an `hasOwnProperty` prop
var hasOwnProperty = Object.prototype.hasOwnProperty;

for (var i = 0; i < mapResults.length; i++) {
if (mapResults[i]) {
var key = mapResults[i].key;
var val = mapResults[i].val;

if (hasOwnProperty.call(result, key)) {
result[key].push(val);
} else {
result[key] = [val];
}
}
}

return callback(err, result);
});
};
23 changes: 23 additions & 0 deletions lib/groupBySeries.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import doLimit from './internal/doLimit';
import groupByLimit from './groupByLimit';

/**
* The same as [`groupBy`]{@link module:Collections.groupBy} but runs only a single async operation at a time.
*
* @name groupBySeries
* @static
* @memberOf module:Collections
* @method
* @see [async.groupBy]{@link module:Collections.groupBy}
* @category Collection
* @param {Array|Iterable|Object} coll - A collection to iterate over.
* @param {number} limit - The maximum number of async operations at a time.
* @param {Function} iteratee - A function to apply to each item in `coll`.
* The iteratee is passed a `callback(err, key)` which must be called once it
* has completed with an error (which can be `null`) and the `key` to group the
* value under. Invoked with (value, callback).
* @param {Function} [callback] - A callback which is called when all `iteratee`
* functions have finished, or an error occurs. Result is an `Object` whoses
* properties are arrays of values which returned the corresponding key.
*/
export default doLimit(groupByLimit, 1);
9 changes: 9 additions & 0 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ import filter from './filter';
import filterLimit from './filterLimit';
import filterSeries from './filterSeries';
import forever from './forever';
import groupBy from './groupBy';
import groupByLimit from './groupByLimit';
import groupBySeries from './groupBySeries';
import log from './log';
import map from './map';
import mapLimit from './mapLimit';
Expand Down Expand Up @@ -128,6 +131,9 @@ export default {
filterLimit: filterLimit,
filterSeries: filterSeries,
forever: forever,
groupBy: groupBy,
groupByLimit: groupByLimit,
groupBySeries: groupBySeries,
log: log,
map: map,
mapLimit: mapLimit,
Expand Down Expand Up @@ -220,6 +226,9 @@ export {
filterLimit as filterLimit,
filterSeries as filterSeries,
forever as forever,
groupBy as groupBy,
groupByLimit as groupByLimit,
groupBySeries as groupBySeries,
log as log,
map as map,
mapLimit as mapLimit,
Expand Down
2 changes: 1 addition & 1 deletion lib/map.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import map from './internal/map';
*
* If `map` is passed an Object, the results will be an Array. The results
* will roughly be in the order of the original Objects' keys (but this can
* vary across JavaScript engines)
* vary across JavaScript engines).
*
* @name map
* @static
Expand Down

0 comments on commit bdc3d81

Please sign in to comment.