Wie man das Äquivalent von LINQ SelectMany () nur in Javascript macht

90

Leider habe ich kein JQuery oder Underscore, nur reines Javascript (IE9-kompatibel).

Ich möchte das Äquivalent von SelectMany () aus der LINQ-Funktionalität.

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

Kann ich es schaffen?

BEARBEITEN:

Dank der Antworten funktionierte Folgendes:

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
quelle
5
Das ist überhaupt nicht unglücklich. Reines JavaScript ist unglaublich. Ohne Kontext ist es sehr schwer zu verstehen, was Sie hier erreichen wollen.
Sterling Archer
3
@SterlingArcher, sehen Sie, wie spezifisch sich die Antwort herausstellte. Es gab nicht zu viele mögliche Antworten und die beste Antwort war kurz und prägnant.
Toddmo

Antworten:

119

Für eine einfache Auswahl können Sie die Reduktionsfunktion von Array verwenden.
Nehmen wir an, Sie haben eine Reihe von Zahlenfeldern:

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]

Bearbeiten:
da es6 flatMap zum Array-Prototyp hinzugefügt wurde. SelectManyist synonym zu flatMap.
Die Methode ordnet zuerst jedes Element mithilfe einer Zuordnungsfunktion zu und glättet dann das Ergebnis in ein neues Array. Die vereinfachte Signatur in TypeScript lautet:

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

Um die Aufgabe zu erfüllen, müssen wir nur jedes Element auf phoneNumbers flatMap

arr.flatMap(a => a.phoneNumbers);
Sagi
quelle
11
Das letzte kann auch geschrieben werden alsarr.reduce(function(a, b){ return a.concat(b.phoneNumbers); }, [])
Timwi
Sie sollten nicht .map () oder .concat () verwenden müssen. Siehe meine Antwort unten.
WesleyAC
ändert dies nicht das ursprüngliche Array?
Ewan
Jetzt weniger wichtig, flatMaperfüllt aber nicht die Anforderung des OP nach einer IE9-kompatiblen Lösung - Browserkompatibel fürflatmap .
Ruffin
24

Als einfachere Option Array.prototype.flatMap () oder 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
quelle
3
Einfach und prägnant. Mit Abstand die beste Antwort.
Ste Brown
flat () ist laut MDN zum 04.12.2019 nicht in Edge verfügbar.
Pettys
Der neue Edge (basierend auf Chrom) wird dies jedoch unterstützen.
Necip Sunmaz
2
Es sieht so aus, als wäre Array.prototype.flatMap () ebenfalls eine Sache, daher könnte Ihr Beispiel vereinfacht werdenconst result = data.flatMap(a => a.details)
Kyle
12

Für diejenigen, die eine Weile später Javascript verstehen, aber dennoch eine einfache Typed SelectMany-Methode in Typescript benötigen:

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>());
}
Joel Harkes
quelle
8

Sagi verwendet die Concat-Methode korrekt, um ein Array zu reduzieren. Um etwas Ähnliches wie dieses Beispiel zu erhalten, benötigen Sie jedoch auch eine Karte für den ausgewählten Teil 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
quelle
Ich werde eine Geige machen; Ich kann Ihre Daten als json verwenden. Versucht, Ihre Antwort auf eine Codezeile zu reduzieren. "Wie man eine Antwort über das
Reduzieren von Objekthierarchien reduziert
Fühlen Sie sich frei, Ihre Daten zu verwenden ... Ich habe die Kartenfunktion explizit abstrahiert, sodass Sie leicht einen beliebigen Eigenschaftsnamen auswählen können, ohne jedes Mal eine neue Funktion schreiben zu müssen. Ersetzen Sie einfach arrmit peopleund "pets"mit"PhoneNumbers"
Fabio Beltramini
Ich habe meine Frage mit abgeflachter Version bearbeitet und Ihre Antwort bewertet. Vielen Dank.
Toddmo
1
Die Hilfsfunktionen bereinigen die Dinge, aber mit ES6 können Sie dies einfach tun : petOwners.map(owner => owner.Pets).reduce((a, b) => a.concat(b), []);. Oder noch einfacher 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"]}]

Lasst uns beginnen

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

HINWEIS: Verwenden Sie ein gültiges Array (nicht null) als Anfangswert in der Reduktionsfunktion

Bogdan Manole
quelle
3

versuchen Sie dies (mit es6):

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

Beispielarray:

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

mit:

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

Ich würde dies tun (vermeiden .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]

Wenn Sie .reduce () nicht verwenden möchten:

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

Wenn Sie .forEach () verwenden möchten:

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

    return arr;
}
WesleyAC
quelle
2
Ich finde es lustig, dass ich das vor 4 Jahren gefragt habe und die Stapelüberlaufbenachrichtigung für Ihre Antwort aufgetaucht ist. Was ich gerade mache, ist, mit js Arrays zu kämpfen. Schöne Rekursion!
Toddmo
Ich bin nur froh, dass es jemand gesehen hat! Ich war überrascht, dass so etwas wie das, was ich geschrieben habe, nicht bereits aufgeführt war, da der Thread schon so lange läuft und wie viele versuchte Antworten es gibt.
WesleyAC
@toddmo Wenn Sie gerade an js-Arrays arbeiten, könnte Sie die Lösung interessieren, die ich kürzlich hier hinzugefügt habe: stackoverflow.com/questions/1960473/… .
WesleyAC
2

Los geht's, eine umgeschriebene Version der Antwort von Joel-Harkes in TypeScript als Erweiterung, die auf jedem Array verwendet werden kann. So können Sie es buchstäblich wie verwenden somearray.selectMany(c=>c.someprop). Transpiled, das ist 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 { };
Worthy7
quelle