Comment faire l'équivalent de LINQ SelectMany () juste en javascript

90

Malheureusement, je n'ai pas JQuery ou Underscore, juste du javascript pur (compatible IE9).

Je veux l'équivalent de SelectMany () de la fonctionnalité LINQ.

// SelectMany flattens it to just a list of phone numbers.
IEnumerable<PhoneNumber> phoneNumbers = people.SelectMany(p => p.PhoneNumbers);

Puis-je le faire?

ÉDITER:

Grâce aux réponses, j'ai fait fonctionner ceci:

var petOwners = 
[
    {
        Name: "Higa, Sidney", Pets: ["Scruffy", "Sam"]
    },
    {
        Name: "Ashkenazi, Ronen", Pets: ["Walker", "Sugar"]
    },
    {
        Name: "Price, Vernette", Pets: ["Scratches", "Diesel"]
    },
];

function property(key){return function(x){return x[key];}}
function flatten(a,b){return a.concat(b);}

var allPets = petOwners.map(property("Pets")).reduce(flatten,[]);

console.log(petOwners[0].Pets[0]);
console.log(allPets.length); // 6

var allPets2 = petOwners.map(function(p){ return p.Pets; }).reduce(function(a, b){ return a.concat(b); },[]); // all in one line

console.log(allPets2.length); // 6
Toddmo
la source
5
Ce n'est pas du tout malheureux. Le JavaScript pur est incroyable. Sans contexte, il est très difficile de comprendre ce que vous essayez de réaliser ici.
Sterling Archer
3
@SterlingArcher, voyez à quel point la réponse s'est avérée spécifique. Il n'y a pas eu trop de réponses possibles et la meilleure réponse était courte et concise.
toddmo

Réponses:

120

pour une sélection simple, vous pouvez utiliser la fonction de réduction de Array.
Disons que vous avez un tableau de tableaux de nombres:

var arr = [[1,2],[3, 4]];
arr.reduce(function(a, b){ return a.concat(b); });
=>  [1,2,3,4]

var arr = [{ name: "name1", phoneNumbers : [5551111, 5552222]},{ name: "name2",phoneNumbers : [5553333] }];
arr.map(function(p){ return p.phoneNumbers; })
   .reduce(function(a, b){ return a.concat(b); })
=>  [5551111, 5552222, 5553333]

Edit:
depuis es6 flatMap a été ajouté au prototype Array. SelectManyest synonyme de flatMap.
La méthode mappe d'abord chaque élément à l'aide d'une fonction de mappage, puis aplatit le résultat dans un nouveau tableau. Sa signature simplifiée dans TypeScript est:

function flatMap<A, B>(f: (value: A) => B[]): B[]

Afin d'accomplir la tâche, nous devons simplement mapper chaque élément sur phoneNumbers

arr.flatMap(a => a.phoneNumbers);
Sagi
la source
11
Ce dernier peut également être écrit commearr.reduce(function(a, b){ return a.concat(b.phoneNumbers); }, [])
Timwi
Vous ne devriez pas avoir besoin d'utiliser .map () ou .concat (). Voir ma réponse ci-dessous.
WesleyAC
cela ne modifie-t-il pas le tableau d'origine?
Ewan
Moins important maintenant, mais flatMapne répond pas à la demande de l'OP pour une solution compatible IE9 - compatible avec le navigateurflatmap .
ruffin
24

En tant qu'option plus simple Array.prototype.flatMap () ou Array.prototype.flat ()

const data = [
{id: 1, name: 'Dummy Data1', details: [{id: 1, name: 'Dummy Data1 Details'}, {id: 1, name: 'Dummy Data1 Details2'}]},
{id: 1, name: 'Dummy Data2', details: [{id: 2, name: 'Dummy Data2 Details'}, {id: 1, name: 'Dummy Data2 Details2'}]},
{id: 1, name: 'Dummy Data3', details: [{id: 3, name: 'Dummy Data3 Details'}, {id: 1, name: 'Dummy Data3 Details2'}]},
]

const result = data.flatMap(a => a.details); // or data.map(a => a.details).flat(1);
console.log(result)

Necip Sunmaz
la source
3
Simple et concis. De loin la meilleure réponse.
Ste Brown
flat () n'est pas disponible dans Edge selon MDN à partir du 04/12/2019.
pettys
Mais le nouvel Edge (basé sur Chromium) le supportera.
Necip Sunmaz
2
Il semble que Array.prototype.flatMap () est également une chose, donc votre exemple pourrait être simplifié àconst result = data.flatMap(a => a.details)
Kyle le
12

Pour ceux qui sont un peu plus tard, comprendre javascript mais veulent toujours une méthode SelectMany typée simple dans Typescript:

function selectMany<TIn, TOut>(input: TIn[], selectListFn: (t: TIn) => TOut[]): TOut[] {
  return input.reduce((out, inx) => {
    out.push(...selectListFn(inx));
    return out;
  }, new Array<TOut>());
}
Joël Harkes
la source
8

Sagi a raison d'utiliser la méthode concat pour aplatir un tableau. Mais pour obtenir quelque chose de similaire à cet exemple, vous auriez également besoin d'une carte pour la partie sélectionnée https://msdn.microsoft.com/library/bb534336(v=vs.100).aspx

/* arr is something like this from the example PetOwner[] petOwners = 
                    { new PetOwner { Name="Higa, Sidney", 
                          Pets = new List<string>{ "Scruffy", "Sam" } },
                      new PetOwner { Name="Ashkenazi, Ronen", 
                          Pets = new List<string>{ "Walker", "Sugar" } },
                      new PetOwner { Name="Price, Vernette", 
                          Pets = new List<string>{ "Scratches", "Diesel" } } }; */

function property(key){return function(x){return x[key];}}
function flatten(a,b){return a.concat(b);}

arr.map(property("pets")).reduce(flatten,[])
Fabio Beltramini
la source
Je vais faire un violon; Je peux utiliser vos données comme json. Je vais essayer d'aplatir votre réponse sur une ligne de code. "Comment aplatir une réponse sur l'aplatissement des hiérarchies d'objets" lol
toddmo
N'hésitez pas à utiliser vos données ... J'ai explicitement résumé la fonction de carte afin que vous puissiez facilement sélectionner n'importe quel nom de propriété sans avoir à écrire une nouvelle fonction à chaque fois. Il suffit de remplacer arrpar peopleet "pets"par"PhoneNumbers"
Fabio Beltramini
J'ai modifié ma question avec une version aplatie et voté votre réponse. Merci.
toddmo
1
Les fonctions d'aide font des choses propres, mais avec ES6 vous pouvez tout faire: petOwners.map(owner => owner.Pets).reduce((a, b) => a.concat(b), []);. Ou, encore plus simple, petOwners.reduce((a, b) => a.concat(b.Pets), []);.
ErikE
3
// you can save this function in a common js file of your project
function selectMany(f){ 
    return function (acc,b) {
        return acc.concat(f(b))
    }
}

var ex1 = [{items:[1,2]},{items:[4,"asda"]}];
var ex2 = [[1,2,3],[4,5]]
var ex3 = []
var ex4 = [{nodes:["1","v"]}]

Commençons

ex1.reduce(selectMany(x=>x.items),[])

=> [1, 2, 4, "asda"]

ex2.reduce(selectMany(x=>x),[])

=> [1, 2, 3, 4, 5]

ex3.reduce(selectMany(x=> "this will not be called" ),[])

=> []

ex4.reduce(selectMany(x=> x.nodes ),[])

=> ["1", "v"]

REMARQUE: utilisez un tableau valide (non nul) comme valeur initiale dans la fonction de réduction

Bogdan Manole
la source
3

essayez ceci (avec es6):

 Array.prototype.SelectMany = function (keyGetter) {
 return this.map(x=>keyGetter(x)).reduce((a, b) => a.concat(b)); 
 }

exemple de tableau:

 var juices=[
 {key:"apple",data:[1,2,3]},
 {key:"banana",data:[4,5,6]},
 {key:"orange",data:[7,8,9]}
 ]

en utilisant :

juices.SelectMany(x=>x.data)
Cem Tuğut
la source
3

Je ferais ceci (en évitant .concat ()):

function SelectMany(array) {
    var flatten = function(arr, e) {
        if (e && e.length)
            return e.reduce(flatten, arr);
        else 
            arr.push(e);
        return arr;
    };

    return array.reduce(flatten, []);
}

var nestedArray = [1,2,[3,4,[5,6,7],8],9,10];
console.log(SelectMany(nestedArray)) //[1,2,3,4,5,6,7,8,9,10]

Si vous ne souhaitez pas utiliser .reduce ():

function SelectMany(array, arr = []) {
    for (let item of array) {
        if (item && item.length)
            arr = SelectMany(item, arr);
        else
            arr.push(item);
    }
    return arr;
}

Si vous souhaitez utiliser .forEach ():

function SelectMany(array, arr = []) {
    array.forEach(e => {
        if (e && e.length)
            arr = SelectMany(e, arr);
        else
            arr.push(e);
    });

    return arr;
}
WesleyAC
la source
2
Je pense que c'est drôle que j'ai posé cette question il y a 4 ans, et la notification de dépassement de capacité de la pile pour votre réponse est apparue, et ce que je fais en ce moment est aux prises avec les tableaux js. Belle récursion!
toddmo
Je suis juste content que quelqu'un l'ait vu! J'ai été surpris que quelque chose comme celui que j'ai écrit ne soit pas déjà répertorié, compte tenu de la durée du fil et du nombre de tentatives de réponses.
WesleyAC
@toddmo Pour ce que ça vaut, si vous travaillez sur des tableaux js en ce moment, vous pourriez être intéressé par la solution que j'ai ajoutée récemment ici: stackoverflow.com/questions/1960473/… .
WesleyAC
2

Voilà, une version réécrite de la réponse de joel-harkes en TypeScript en tant qu'extension, utilisable sur n'importe quel tableau. Ainsi, vous pouvez littéralement l'utiliser comme somearray.selectMany(c=>c.someprop). Trans-empilé, c'est javascript.

declare global {
    interface Array<T> {
        selectMany<TIn, TOut>(selectListFn: (t: TIn) => TOut[]): TOut[];
    }
}

Array.prototype.selectMany = function <TIn, TOut>( selectListFn: (t: TIn) => TOut[]): TOut[] {
    return this.reduce((out, inx) => {
        out.push(...selectListFn(inx));
        return out;
    }, new Array<TOut>());
}


export { };
Digne7
la source