Jak zrobić odpowiednik LINQ SelectMany () tylko w javascript


90

Niestety nie mam JQuery ani Underscore, tylko czysty javascript (kompatybilny z IE9).

Chcę odpowiednika SelectMany () z funkcji LINQ.

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

Czy mogę to zrobić?

EDYTOWAĆ:

Dzięki odpowiedziom działało to:

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
To wcale nie jest niefortunne. Czysty JavaScript jest niesamowity. Bez kontekstu bardzo trudno jest zrozumieć, co próbujesz tutaj osiągnąć.
Sterling Archer

3
@SterlingArcher, zobacz, jak konkretna okazała się odpowiedź. Nie było zbyt wielu możliwych odpowiedzi, a najlepsza odpowiedź była krótka i zwięzła.
toddmo

Odpowiedzi:


120

dla prostego wyboru możesz użyć funkcji redukcji Array.
Powiedzmy, że masz tablicę liczb:

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]

Edycja:
odkąd es6 flatMap został dodany do prototypu Array. SelectManyjest synonimem flatMap.
Metoda najpierw mapuje każdy element przy użyciu funkcji mapowania, a następnie spłaszcza wynik do nowej tablicy. Jego uproszczony podpis w TypeScript to:

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

Aby wykonać zadanie, musimy po prostu zmapować każdy element do phoneNumbers

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

11
Ten ostatni można również zapisać jakoarr.reduce(function(a, b){ return a.concat(b.phoneNumbers); }, [])
Timwi

Nie powinieneś używać .map () ani .concat (). Zobacz moją odpowiedź poniżej.
WesleyAC

czy to nie zmienia oryginalnej tablicy?
Ewan

Teraz mniej ważne, ale flatMapnie spełnia wymagań OP dotyczących rozwiązania zgodnego z IE9 - zgodnego z przeglądarkąflatmap .
ruffin

24

Jako prostsza opcja Array.prototype.flatMap () lub 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
Proste i zwięzłe. Zdecydowanie najlepsza odpowiedź.
Ste Brown

flat () nie jest dostępny w Edge według MDN od 4.12.2019.
pettys

Ale nowy Edge (oparty na Chromium) będzie go obsługiwał.
Necip Sunmaz

2
Wygląda na to, że Array.prototype.flatMap () też jest rzeczą, więc twój przykład można uprościć doconst result = data.flatMap(a => a.details)
Kyle

12

Dla tych, którzy chwilę później rozumieją javascript, ale nadal chcą prostej metody Typed SelectMany w 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 ma rację, używając metody concat do spłaszczenia tablicy. Ale aby uzyskać coś podobnego do tego przykładu, potrzebujesz również mapy dla wybranej części 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,[])

I '; zamierzam zrobić skrzypce; Mogę użyć twoich danych jako json. Spróbuje spłaszczyć twoją odpowiedź do jednej linii kodu. „Jak spłaszczyć odpowiedź na temat spłaszczania hierarchii obiektów” lol
toddmo

Zapraszam do korzystania ze swoich danych ... Wyraźnie wyodrębniłem funkcję mapy, abyś mógł łatwo wybrać dowolną nazwę właściwości bez konieczności pisania nowej funkcji za każdym razem. Wystarczy wymienić arrz peoplei "pets"z"PhoneNumbers"
Fabio Beltramini

Zmodyfikowałem moje pytanie ze spłaszczoną wersją i zagłosowałem za Twoją odpowiedzią. Dzięki.
toddmo

1
Funkcje pomocnicze zrobić czyste rzeczy, ale z ES6 można po prostu to zrobić: petOwners.map(owner => owner.Pets).reduce((a, b) => a.concat(b), []);. Albo jeszcze prościej, 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"]}]

Zaczynajmy

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

UWAGA: użyj prawidłowej tablicy (innej niż null) jako wartości początkowej w funkcji redukuj


3

spróbuj tego (z es6):

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

przykładowa tablica:

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

za pomocą :

juices.SelectMany(x=>x.data)

3

Zrobiłbym to (unikając .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]

Jeśli nie chcesz używać .reduce ():

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

Jeśli chcesz użyć .forEach ():

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

    return arr;
}

2
Myślę, że to zabawne, że zapytałem o to 4 lata temu i wyskoczyło powiadomienie o przepełnieniu stosu dla Twojej odpowiedzi, a to, co robię w tej chwili, to walczenie z tablicami js. Niezła rekurencja!
toddmo

Po prostu cieszę się, że ktoś to widział! Zaskoczyło mnie, że czegoś takiego, jak napisałem, nie było już na liście, biorąc pod uwagę, jak długo trwa wątek i ile jest prób odpowiedzi.
WesleyAC

@toddmo Co to jest warte, jeśli pracujesz teraz na tablicach js, możesz być zainteresowany rozwiązaniem, które dodałem niedawno tutaj: stackoverflow.com/questions/1960473/… .
WesleyAC

2

Proszę bardzo, przepisana wersja odpowiedzi Joela-Harkesa w TypeScript jako rozszerzenie, nadające się do użytku w dowolnej tablicy. Możesz więc używać go dosłownie jak somearray.selectMany(c=>c.someprop). Przetłumaczone, to jest 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 { };

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.