如何只在javascript中做LINQ SelectMany()的等效项


90

不幸的是,我没有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太神奇了。没有上下文,很难理解您要在此处实现的目标。
Sterling Archer 2015年

3
@SterlingArcher,看看答案的具体程度。没有太多可能的答案,最好的答案是简洁明了。
toddmo '16

Answers:


120

一个简单的选择,您可以使用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]

编辑:
由于es6 flatMap已被添加到Array原型。 SelectMany是的同义词flatMap
该方法首先使用映射函数映射每个元素,然后将结果展平为新数组。它在TypeScript中的简化签名是:

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

这不会改变原始数组吗?
伊万

现在flatMap不那么重要了,但不符合OP对IE9兼容解决方案的要求-浏览器兼容flatmap
鲁芬

24

作为更简单的选项,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

截至2019年12月4日,根据MDN,flat()在Edge中不可用。
小资

但是新的Edge(基于Chromium)将支持它。
Necip Sunmaz,

2
看起来Array.prototype.flatMap()也是一回事,因此您的示例可以简化为const result = data.flatMap(a => a.details)
Kyle

12

稍后,请理解javascript,但仍希望在Typescript中使用简单的Typed SelectMany方法:

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。将尝试使您对一行代码的回答变得平坦。“如
何展平

随时使用您的数据...我明确抽象了map函数,以便您可以轻松选择任何属性名称,而不必每次都编写新函数。只需更换arrpeople"pets""PhoneNumbers"
法比奥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”]

注意:在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 { };

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.