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

Javascriptunderscore.jsLinq

Javascript Problem Overview


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?

Javascript Solutions


Solution 1 - Javascript

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

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

Heres a fiddle

Solution 3 - Javascript

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 ]

Solution 4 - Javascript

I couldn't find any methods in lodash that work quite like SelectMany, so I created one using pure JS:

Array.prototype.selectMany = function(fn) {
    return Array.prototype.concat(...this.map(fn));
};

Boom.

> console.log([{a:[1,2],b:'x'},{a:[3,4],b:'y'}].selectMany(o => o.a));
[ 1, 2, 3, 4 ]

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionDrew NoakesView Question on Stackoverflow
Solution 1 - JavascriptPatrickView Answer on Stackoverflow
Solution 2 - Javascriptarmen.shimoonView Answer on Stackoverflow
Solution 3 - JavascriptmpenView Answer on Stackoverflow
Solution 4 - JavascriptmpenView Answer on Stackoverflow