Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[#1348] initial groupBy implementation #1364

Merged
merged 2 commits into from Feb 27, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
40 changes: 40 additions & 0 deletions lib/groupBy.js
@@ -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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is a decent 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
@@ -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]) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code style question: is there a preference for:

if (!mapResults[i]) continue;

// do something ...

versus

if (mapResults[i]) {
    // do something ...
}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the continue style, but that's just like... ...my opinion, man.

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
@@ -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
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
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