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

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);

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)


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>());
}

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,[])

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


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)

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;
}

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 { };

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.