วิธีทำเทียบเท่า LINQ SelectMany () ใน javascript


93

น่าเสียดายที่ฉันไม่มี JQuery หรือ Underscore มีแค่ javascript ล้วน ๆ (เข้ากันได้กับ IE9)

ฉันต้องการเทียบเท่ากับ SelectMany () จากฟังก์ชัน LINQ

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

ฉันทำมันได้ไหม?

แก้ไข:

ขอบคุณสำหรับคำตอบฉันได้ผล:

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
นั่นไม่ใช่เรื่องโชคร้ายเลย JavaScript บริสุทธิ์นั้นยอดเยี่ยมมาก หากไม่มีบริบทก็ยากที่จะเข้าใจว่าคุณพยายามบรรลุอะไรที่นี่
Sterling Archer

3
@SterlingArcher ดูว่าคำตอบนั้นเฉพาะเจาะจงแค่ไหน ไม่มีคำตอบที่เป็นไปได้มากเกินไปและคำตอบที่ดีที่สุดคือสั้นและกระชับ
Toddmo

คำตอบ:


128

สำหรับการเลือกอย่างง่ายคุณสามารถใช้ฟังก์ชันลดของ Array
สมมติว่าคุณมีอาร์เรย์ของตัวเลข:

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]

แก้ไข:
เนื่องจาก es6 flatMap ถูกเพิ่มไปยังต้นแบบ Array เป็นคำพ้องที่จะSelectMany วิธีแรกจะแมปแต่ละองค์ประกอบโดยใช้ฟังก์ชันการแม็ปจากนั้นจึงทำให้ผลลัพธ์แบนลงในอาร์เรย์ใหม่ ลายเซ็นที่เรียบง่ายใน TypeScript คือ:flatMap

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

เพื่อให้บรรลุภารกิจเราต้องแบนแมปแต่ละองค์ประกอบไปยัง phoneNumbers

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

11
คนสุดท้ายสามารถเขียนได้ว่าarr.reduce(function(a, b){ return a.concat(b.phoneNumbers); }, [])
Timwi

คุณไม่จำเป็นต้องใช้. map () หรือ. concat () ดูคำตอบของฉันด้านล่าง
WesleyAC

สิ่งนี้ไม่เปลี่ยนแปลงอาร์เรย์ดั้งเดิมหรือไม่
Ewan

ความสำคัญน้อยกว่าในขณะนี้ แต่flatMapไม่ได้ตอบสนองความต้องการของ OP เพื่อแก้ปัญหาร่วมกันได้ IE9 - เบราว์เซอร์สำหรับ compat flatmap
ruffin

1
ลด () ล้มเหลวในอาร์เรย์ว่างดังนั้นคุณจะต้องเพิ่มค่าเริ่มต้น ดูstackoverflow.com/questions/23359173/…
halllo

26

เป็นตัวเลือกที่ง่ายกว่าArray.prototype.flatMap ()หรือ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
เรียบง่ายและกระชับ คำตอบที่ดีที่สุด
Ste Brown

flat () ไม่มีใน Edge ตาม MDN ณ วันที่ 12/4/2019
อนุ

แต่ Edge ใหม่ (อิงตาม Chromium) จะรองรับ
Necip Sunmaz

2
ดูเหมือนว่าArray.prototype.flatMap ()ก็เป็นสิ่งหนึ่งเช่นกันดังนั้นตัวอย่างของคุณอาจจะง่ายขึ้นถึงconst result = data.flatMap(a => a.details)
Kyle

12

ในภายหลังเข้าใจ javascript แต่ยังต้องการวิธีการ Typed SelectMany แบบง่ายๆใน 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 ถูกต้องในการใช้วิธี concat เพื่อทำให้อาร์เรย์แบนราบ แต่เพื่อให้ได้สิ่งที่คล้ายกับตัวอย่างนี้คุณจะต้องมีแผนที่สำหรับส่วนที่เลือก 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,[])

ฉันจะทำซอ; ฉันสามารถใช้ข้อมูลของคุณเป็น json จะพยายามแบนคำตอบของคุณให้เป็นโค้ดหนึ่งบรรทัด "วิธีแบนคำตอบเกี่ยวกับลำดับชั้นของวัตถุที่แบน" lol
Toddmo

อย่าลังเลที่จะใช้ข้อมูลของคุณ ... ฉันสรุปฟังก์ชันแผนที่อย่างชัดเจนเพื่อให้คุณสามารถเลือกชื่อคุณสมบัติใด ๆ ได้อย่างง่ายดายโดยไม่ต้องเขียนฟังก์ชันใหม่ทุกครั้ง เพียงแค่แทนที่arrด้วยpeopleและ"pets"ด้วย"PhoneNumbers"
Fabio Beltramini

แก้ไขคำถามของฉันด้วยเวอร์ชันแบนและโหวตคำตอบของคุณ ขอบคุณ.
Toddmo

1
ฟังก์ชั่นผู้ช่วยทำในสิ่งที่ทำความสะอาด แต่มี ES6 petOwners.map(owner => owner.Pets).reduce((a, b) => a.concat(b), []);คุณก็สามารถทำเช่นนี้: หรือง่ายกว่า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"]}]

เริ่มกันเลย

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

หมายเหตุ: ใช้อาร์เรย์ที่ถูกต้อง (ไม่ใช่ค่าว่าง) เป็นค่าเริ่มต้นในฟังก์ชันลด


3

ลองสิ่งนี้ (ด้วย es6):

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

อาร์เรย์ตัวอย่าง:

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

ใช้:

juices.SelectMany(x=>x.data)

3

ฉันจะทำสิ่งนี้ (หลีกเลี่ยง. 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]

หากคุณไม่ต้องการใช้. reduce ():

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

หากคุณต้องการใช้. forEach ():

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

    return arr;
}

2
ฉันคิดว่ามันตลกที่ฉันถามสิ่งนี้เมื่อ 4 ปีที่แล้วและการแจ้งเตือนสแต็กล้นสำหรับคำตอบของคุณก็โผล่ขึ้นมาและสิ่งที่ฉันกำลังทำอยู่ในขณะนี้กำลังดิ้นรนกับอาร์เรย์ js การเรียกซ้ำที่ดี!
Toddmo

ดีใจที่มีคนเห็น! ฉันรู้สึกประหลาดใจที่บางสิ่งบางอย่างที่ฉันเขียนนั้นไม่ได้อยู่ในรายการเนื่องจากเธรดนั้นดำเนินไปนานแค่ไหนและมีคำตอบที่พยายามมากแค่ไหน
WesleyAC

@toddmo สำหรับสิ่งที่คุ้มค่าถ้าคุณกำลังทำงานบน js อาร์เรย์ตอนนี้คุณอาจจะสนใจในการแก้ปัญหาผมเพิ่มเมื่อเร็ว ๆ นี้ที่นี่: stackoverflow.com/questions/1960473/...
WesleyAC

2

ได้แล้วคำตอบของ joel-harkes เวอร์ชันที่เขียนใหม่ใน TypeScript เป็นส่วนขยายซึ่งใช้งานได้กับอาร์เรย์ใด ๆ ดังนั้นคุณสามารถใช้มันอย่างsomearray.selectMany(c=>c.someprop)แท้จริง Trans-piled นี่คือจาวาสคริปต์

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

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.