Bagaimana melakukan yang setara dengan LINQ SelectMany () hanya di javascript

90

Sayangnya, saya tidak memiliki JQuery atau Underscore, hanya javascript murni (kompatibel dengan IE9).

Saya menginginkan fungsi yang setara dengan SelectMany () dari LINQ.

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

Bisakah saya melakukannya?

EDIT:

Berkat jawaban, saya mendapatkan ini berfungsi:

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
sumber
5
Itu sama sekali tidak disayangkan. JavaScript murni luar biasa. Tanpa konteks, sangat sulit untuk memahami apa yang ingin Anda capai di sini.
Sterling Archer
3
@SterlingArcher, lihat seberapa spesifik jawabannya. Tidak terlalu banyak kemungkinan jawaban dan jawaban terbaik pendek dan ringkas.
toddmo

Jawaban:

120

untuk pemilihan sederhana Anda dapat menggunakan fungsi pengurangan Array.
Katakanlah Anda memiliki array array angka:

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]

Sunting:
karena es6 flatMap telah ditambahkan ke prototipe Array. SelectManyadalah sinonim dengan flatMap.
Metode ini pertama-tama memetakan setiap elemen menggunakan fungsi pemetaan, kemudian meratakan hasilnya ke dalam array baru. Tanda tangannya yang disederhanakan di TypeScript adalah:

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

Untuk mencapai tugas tersebut kita hanya perlu melakukan flatMap setiap elemen ke phoneNumbers

arr.flatMap(a => a.phoneNumbers);
Sagi
sumber
11
Yang terakhir juga dapat ditulis sebagaiarr.reduce(function(a, b){ return a.concat(b.phoneNumbers); }, [])
Timwi
Anda tidak perlu menggunakan .map () atau .concat (). Lihat jawaban saya di bawah.
WesleyAC
bukankah ini mengubah larik aslinya?
Ewan
Kurang penting sekarang, tetapi flatMaptidak memenuhi permintaan OP untuk solusi yang kompatibel dengan IE9 - browser compat forflatmap .
ruffin
24

Sebagai opsi yang lebih sederhana Array.prototype.flatMap () atau 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
sumber
3
Sederhana dan ringkas. Sejauh ini jawaban terbaik.
Ste Brown
flat () tidak tersedia di Edge menurut MDN mulai 12/4/2019.
pettys
Tetapi Edge baru (berdasarkan Chromium) akan mendukungnya.
Necip Sunmaz
2
Sepertinya Array.prototype.flatMap () juga merupakan suatu hal, jadi contoh Anda dapat disederhanakan menjadiconst result = data.flatMap(a => a.details)
Kyle
12

Untuk beberapa waktu kemudian, memahami javascript tetapi masih menginginkan metode SelectMany Ketik yang sederhana di Typecript:

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
sumber
8

Sagi benar dalam menggunakan metode concat untuk meratakan array. Tetapi untuk mendapatkan sesuatu yang mirip dengan contoh ini, Anda juga memerlukan peta untuk bagian tertentu 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
sumber
I '; m akan membuat biola; Saya dapat menggunakan data Anda sebagai json. Akan mencoba untuk meratakan jawaban Anda menjadi satu baris kode. "Bagaimana meratakan jawaban tentang mendatarkan hierarki objek" lol
toddmo
Silakan gunakan data Anda ... Saya secara eksplisit mengabstraksi fungsi peta sehingga Anda dapat dengan mudah memilih nama properti apa pun tanpa harus menulis fungsi baru setiap saat. Ganti saja arrdengan peopledan "pets"dengan"PhoneNumbers"
Fabio Beltramini
Mengedit pertanyaan saya dengan versi rata dan memilih jawaban Anda. Terima kasih.
toddmo
1
Fungsi helper melakukan hal-hal yang bersih, tapi dengan ES6 Anda hanya dapat melakukan ini: petOwners.map(owner => owner.Pets).reduce((a, b) => a.concat(b), []);. Atau, bahkan lebih sederhana 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"]}]

Ayo mulai

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

CATATAN: gunakan array yang valid (bukan null) sebagai nilai intisial dalam fungsi pengurangan

Bogdan Manole
sumber
3

coba ini (dengan es6):

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

contoh larik:

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

menggunakan:

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

Saya akan melakukan ini (menghindari .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]

Jika Anda tidak ingin menggunakan .reduce ():

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

Jika Anda ingin menggunakan .forEach ():

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

    return arr;
}
WesleyAC
sumber
2
Saya pikir lucu bahwa saya menanyakan ini 4 tahun yang lalu, dan pemberitahuan stack overflow untuk jawaban Anda muncul, dan apa yang saya lakukan saat ini adalah berjuang dengan array js. Rekursi yang bagus!
toddmo
Saya hanya senang seseorang melihatnya! Saya terkejut bahwa sesuatu seperti yang saya tulis belum terdaftar, mengingat berapa lama utas telah berjalan dan berapa banyak percobaan jawaban yang ada.
WesleyAC
@toddmo Untuk apa nilainya, jika Anda sedang mengerjakan js array sekarang, Anda mungkin tertarik dengan solusi yang saya tambahkan baru-baru ini di sini: stackoverflow.com/questions/1960473/… .
WesleyAC
2

Ini dia, versi jawaban joel-harkes yang ditulis ulang di TypeScript sebagai ekstensi, dapat digunakan pada array apa pun. Jadi Anda benar-benar dapat menggunakannya seperti somearray.selectMany(c=>c.someprop). Bertransaksi, ini 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 { };
Layak7
sumber