javascriptだけでLINQSelectMany()と同等の方法を実行する方法


93

残念ながら、私はJQueryやUnderscoreを持っておらず、純粋なjavascript(IE9互換)だけを持っています。

LINQ機能のSelectMany()に相当するものが必要です。

// 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は素晴らしいです。文脈がなければ、ここで何を達成しようとしているのかを理解するのは非常に困難です。
スターリングアーチャー

3
@SterlingArcher、答えがどれほど具体的であるかを見てください。考えられる答えはそれほど多くなく、最良の答えは短く簡潔でした。
toddmo 2016

回答:


128

単純な選択の場合は、Arrayのreduce関数を使用できます。
数の配列の配列があるとしましょう:

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]

編集:
es6flatMapがアレイプロトタイプに追加されたため。 SelectManyの同義語flatMapです。
このメソッドは、最初にマッピング関数を使用して各要素をマッピングし、次に結果を新しい配列にフラット化します。TypeScriptでの簡略化された署名は次のとおりです。

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

タスクを実行するには、各要素をphoneNumbersにflatMapする必要があります。

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

11
その最後の1は、のように書くこともできるarr.reduce(function(a, b){ return a.concat(b.phoneNumbers); }, [])
Timwi

.map()または.concat()を使用する必要はありません。以下の私の答えを参照してください。
WesleyAC

これは元の配列を変更しませんか?
ユアン・

今はそれほど重要ではありflatMapませんが、IE9互換ソリューションに対するOPの要求を満たしていません-ブラウザflatmap互換。
ラフィン

1
reduce()は空の配列で失敗するため、初期値を追加する必要があります。参照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
シンプルで簡潔。断然ベストアンサー。
SteBrown19年

flat()は、2019年12月4日の時点で、MDNによるとEdgeでは使用できません。
ささいな

しかし、新しいEdge(Chromiumに基づく)はそれをサポートします。
NecipSunmaz19年

2
Array.prototype.flatMap()も同じように見えるので、例を次のように簡略化できますconst result = data.flatMap(a => a.details)
Kyle

12

しばらくして、javascriptを理解していても、Typescriptで単純なTypedSelectManyメソッドが必要な場合は次のようにします。

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として使うことができます。1行のコードに対する回答をフラット化しようとします。笑「オブジェクト階層をフラット化についての答えを平らにする方法」
toddmo

データを自由に使用してください...毎回新しい関数を作成しなくてもプロパティ名を簡単に選択できるように、map関数を明示的に抽象化しました。ただ、交換するarrpeopleして"pets""PhoneNumbers"
ファビオBeltramini

フラット化されたバージョンで私の質問を編集し、あなたの答えに投票しました。ありがとう。
toddmo 2015年

1
ヘルパー関数はクリーンアップを行いますが、ES6では次のようにすることができますpetOwners.map(owner => owner.Pets).reduce((a, b) => a.concat(b), []);。または、さらに簡単に、petOwners.reduce((a, b) => a.concat(b.Pets), []);
ErikE 2017年

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

注:reduce関数の初期値として有効な配列(null以外)を使用してください


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

これが、TypeScriptのjoel-harkesの回答を拡張機能として書き直したもので、どの配列でも使用できます。したがって、文字通りのように使用できますsomearray.selectMany(c=>c.someprop)。トランスパイル、これは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 { };

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.