What's the underscore.js equivalent to LINQ's SelectMany operator?

Imagine I have a nested array structure.

var nested = [ [1], [2], [3] ];

Using underscore.js, how would I produce a flattened array?

In C# you would use Enumerable.SelectMany like this:

var flattened = nested.SelectMany(item => item);

Note that the lambda in this case selects the nested item directly, but it could have been any arbitrary expression.

In jQuery, it's possible to just use:

var flattened = $.map(nested, function(item) { return item; });

However this approach doesn't work with underscore's map function.

So how would I get the flattened array [1, 2, 3] using underscore.js?


Solution 1:

If you have a slightly more complicated array, say one coming from JSON, you can take advantage of the pluck method as well, extracting the specific property you are interested in, similar to parents.SelectMany(parent => parent.Items);

// underscore version
var allitems = _.flatten(_.pluck(parents, 'items'));

allitems is now the array of all subitems from the parents, [a,b,c,d].

And a JSFiddle showing the same thing.


Or, if you are using lodash you can do the same thing by using the _.flatMap function which is available since version 4. Cred to Noel for pointing it out in the comments.

var parents = [
  { name: 'hello', items: ['a', 'b'] },
  { name: 'world', items: ['c', 'd'] }
];


// version 1 of lodash, straight up
var allitems = _.flatMap(parents, 'items');
logIt('straight', allitems);

// or by wrapping the collection first
var allitems = _(parents)
  .flatMap('items')
  .value();
logIt('wrapped', allitems);

// this basically does _(parents).map('items').flatten().value();

function logIt(wat, value) {
  console.log(wat, value)
}
<script src="https://cdn.jsdelivr.net/lodash/4.16.6/lodash.min.js"></script>
<pre id="result"></pre>

In case you want to do more stuff and don't want to chain operators, you can use the flow function to get the same effect. This is useful if you are using TypeScript and importing each operator individually, since you can then optimize your final payload.

const parents = [
  { name: 'hello', items: ['a', 'b'] },
  { name: 'world', items: ['c', 'd'] }
];
logIt('original', parents);

const result = _.flow(
  (collection) => _.flatMap(collection, (item) => item.items),
  (flattened) => flattened.filter((item) => item !== 'd')
)(parents);
logIt('result without "d"', result);

function logIt(wat, value) {
  console.log(wat, value);
}
<script src="https://cdn.jsdelivr.net/npm/[email protected]/lodash.min.js"></script>
<pre id="result"></pre>

Solution 2:

var nested = [ [1], [2], [3] ];
var flattened = _.flatten(nested);

Heres a fiddle

Solution 3:

We can also make Patrick's solution into a mixin so that it becomes chainable:

_.mixin({
    selectMany: function(collection, iteratee=_.identity) {
        return _.flatten(_.map(collection, iteratee));
    }
});

Examples:

let sample = [{a:[1,2],b:'x'},{a:[3,4],b:'y'}];

console.log(_.selectMany(sample, 'a')); // [ 1, 2, 3, 4 ]
console.log(_.chain(sample).selectMany(o => o.a).filter(a => a % 2 === 0).map(a => a * 3).value()); // [ 6, 12 ]