Come eseguire l'equivalente di LINQ SelectMany () solo in javascript


93

Sfortunatamente, non ho JQuery o Underscore, solo javascript puro (compatibile con IE9).

Voglio l'equivalente di SelectMany () dalla funzionalità LINQ.

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

Posso farlo?

MODIFICARE:

Grazie alle risposte, ho funzionato:

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

5
Non è affatto un peccato. Pure JavaScript è sorprendente. Senza contesto, è molto difficile capire cosa stai cercando di ottenere qui.
Sterling Archer

3
@SterlingArcher, guarda quanto si è rivelata specifica la risposta. Non c'erano troppe risposte possibili e la risposta migliore era breve e concisa.
Toddmo

Risposte:


128

per una semplice selezione è possibile utilizzare la funzione di riduzione di Array.
Diciamo che hai una matrice di matrici di numeri:

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]

Modifica:
poiché es6 flatMap è stato aggiunto al prototipo Array. SelectManyè sinonimo di flatMap.
Il metodo mappa prima ogni elemento utilizzando una funzione di mappatura, quindi appiattisce il risultato in un nuovo array. La sua firma semplificata in TypeScript è:

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

Per ottenere il compito, abbiamo solo bisogno di flatMap ogni elemento su phoneNumbers

arr.flatMap(a => a.phoneNumbers);

11
Quest'ultimo può anche essere scritto comearr.reduce(function(a, b){ return a.concat(b.phoneNumbers); }, [])
Timwi

Non dovresti aver bisogno di usare .map () o .concat (). Vedi la mia risposta di seguito.
WesleyAC

questo non altera l'array originale?
Ewan

Meno importante ora, ma flatMapnon soddisfa la richiesta dell'OP per una soluzione compatibile con IE9 - compatibilità browser perflatmap .
ruffin

1
reduce () fallisce su un array vuoto, quindi dovresti aggiungere un valore iniziale. vedi stackoverflow.com/questions/23359173/...
halllo

26

Come opzione più semplice Array.prototype.flatMap () o 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)


3
Semplice e conciso. Di gran lunga la migliore risposta.
Ste Brown

flat () non è disponibile in Edge secondo MDN a partire dal 12/4/2019.
pettys

Ma il nuovo Edge (basato su Chromium) lo supporterà.
Necip Sunmaz

2
Sembra che anche Array.prototype.flatMap () sia una cosa, quindi il tuo esempio potrebbe essere semplificato inconst result = data.flatMap(a => a.details)
Kyle

12

Per quelli un po 'più tardi, capiscono javascript ma vogliono ancora un semplice metodo SelectMany tipizzato in 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>());
}

8

Sagi ha ragione nell'usare il metodo concat per appiattire un array. Ma per ottenere qualcosa di simile a questo esempio, è necessaria anche una mappa per la parte selezionata 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,[])

Sto per fare un violino; Posso usare i tuoi dati come JSON. Proverà ad appiattire la tua risposta a una riga di codice. "Come appiattire una risposta
sull'appiattimento delle

Sentiti libero di usare i tuoi dati ... Ho esplicitamente astratto la funzione mappa in modo che tu possa facilmente selezionare qualsiasi nome di proprietà senza dover scrivere una nuova funzione ogni volta. Basta sostituire arrcon peoplee "pets"con"PhoneNumbers"
Fabio Beltramini

Ho modificato la mia domanda con una versione appiattita e votato a favore della tua risposta. Grazie.
Toddmo

1
Le funzioni di aiuto fanno le cose pulite, ma con ES6 si può solo fare questo: petOwners.map(owner => owner.Pets).reduce((a, b) => a.concat(b), []);. O, ancora più semplice, 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"]}]

Iniziamo

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"]

NOTA: utilizzare un array valido (non null) come valore iniziale nella funzione di riduzione


3

prova questo (con es6):

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

array di esempio:

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

utilizzando:

juices.SelectMany(x=>x.data)

3

Lo farei (evitando .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]

Se non vuoi usare .reduce ():

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

Se vuoi usare .forEach ():

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

    return arr;
}

2
Penso che sia divertente che l'ho chiesto 4 anni fa, e la notifica di overflow dello stack per la tua risposta è spuntata, e quello che sto facendo in questo momento è alle prese con gli array js. Bella ricorsione!
Toddmo

Sono solo contento che qualcuno l'abbia visto! Sono rimasto sorpreso dal fatto che qualcosa del genere che ho scritto non fosse già elencato, dato quanto tempo è passato il thread e quanti tentativi di risposte ci sono.
WesleyAC

@toddmo Per quello che vale, se stai lavorando su array js in questo momento, potresti essere interessato alla soluzione che ho aggiunto di recente qui: stackoverflow.com/questions/1960473/… .
WesleyAC

2

Ecco qui, una versione riscritta della risposta di joel-harkes in TypeScript come estensione, utilizzabile su qualsiasi array. Quindi puoi letteralmente usarlo come somearray.selectMany(c=>c.someprop). Trans-accumulato, questo è 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 { };

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.