如何仅在 javascript 中执行相当于 LINQ SelectMany() 的操作

Posted

技术标签:

【中文标题】如何仅在 javascript 中执行相当于 LINQ SelectMany() 的操作【英文标题】:How to do equivalent of LINQ SelectMany() just in javascript 【发布时间】:2016-02-14 06:34:03 【问题描述】:

不幸的是,我没有 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

【问题讨论】:

这一点也不不幸。纯 JavaScript 是惊人的。没有上下文,很难理解您在这里想要实现的目标。 @SterlingArcher,看看答案有多具体。没有太多可能的答案,最好的答案简短明了。 【参考方案1】:

对于简单的选择,您可以使用 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 原型中。 SelectManyflatMap 的同义词。 该方法首先使用映射函数映射每个元素,然后将结果展平为新数组。 它在 TypeScript 中的简化签名是:

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

为了完成任务,我们只需要将每个元素 flatMap 到 phoneNumbers

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

【讨论】:

最后一个也可以写成arr.reduce(function(a, b) return a.concat(b.phoneNumbers); , []) 您不需要使用 .map() 或 .concat()。请参阅下面的答案。 这不会改变原始数组吗? 现在不那么重要了,但flatMap 不符合 OP 对 IE9 兼容解决方案的要求 -- browser compat for flatmap reduce() 在空数组上失败,因此您必须添加一个初始值。见***.com/questions/23359173/…【参考方案2】:

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

【讨论】:

简洁明了。到目前为止最好的答案。 根据 MDN,截至 2019 年 12 月 4 日,flat() 在 Edge 中不可用。 但是新的 Edge(基于 Chromium)会支持它。 看来Array.prototype.flatMap()也是一个东西,所以你的例子可以简化为const result = data.flatMap(a =&gt; a.details)【参考方案3】:

稍后,了解 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>());

【讨论】:

【参考方案4】:

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 随意使用您的数据...我明确抽象了 map 函数,以便您可以轻松选择任何属性名称,而无需每次都编写新函数。只需将arr 替换为people 并将"pets" 替换为"PhoneNumbers" 用扁平化版本编辑了我的问题,并对你的答案投了赞成票。谢谢。 辅助函数会清理东西,但是使用 ES6 你可以这样做:petOwners.map(owner =&gt; owner.Pets).reduce((a, b) =&gt; a.concat(b), []);。或者,更简单的是petOwners.reduce((a, b) =&gt; a.concat(b.Pets), []);【参考方案5】:
// 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)作为初始值

【讨论】:

【参考方案6】:

试试这个(使用 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)

【讨论】:

【参考方案7】:

我会这样做(避免使用 .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;

【讨论】:

我觉得很有趣,我在 4 年前问过这个问题,然后弹出堆栈溢出通知给你的答案,而我此时正在做的事情是与 js 数组作斗争。很好的递归! 我很高兴有人看到它!我很惊讶我写的东西还没有列出,考虑到线程已经持续了多长时间以及有多少尝试的答案。 @toddmo 对于它的价值,如果你现在正在处理 js 数组,你可能会对我最近在这里添加的解决方案感兴趣:***.com/questions/1960473/…。【参考方案8】:

这里是 joel-harkes 在 TypeScript 中的答案的重写版本,作为扩展,可用于任何数组。所以你可以像somearray.selectMany(c=&gt;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  ;

【讨论】:

【参考方案9】:

您可以尝试实现所有 C# LINQ 方法并保留其语法的 manipula 包:

Manipula.from(petOwners).selectMany(x=>x.Pets).toArray()

https://github.com/litichevskiydv/manipula

https://www.npmjs.com/package/manipula

【讨论】:

不错的解决方案!【参考方案10】:

对于更高版本的 JavaScript,您可以这样做:

  var petOwners = [
    
      Name: 'Higa, Sidney',
      Pets: ['Scruffy', 'Sam']
    ,
    
      Name: 'Ashkenazi, Ronen',
      Pets: ['Walker', 'Sugar']
    ,
    
      Name: 'Price, Vernette',
      Pets: ['Scratches', 'Diesel']
    
  ];

  var arrayOfArrays = petOwners.map(po => po.Pets);
  var allPets = [].concat(...arrayOfArrays);

  console.log(allPets); // ["Scruffy","Sam","Walker","Sugar","Scratches","Diesel"]

见example StackBlitz。

【讨论】:

以上是关于如何仅在 javascript 中执行相当于 LINQ SelectMany() 的操作的主要内容,如果未能解决你的问题,请参考以下文章

如何仅在页面加载的 10% 时执行 JavaScript

仅在 javascript 循环完成后执行 ajax 调用

Javascript/JQuery 仅在页面焦点上执行 Javascript

如何始终显示滚动条

如何仅在 Windows 服务完成后继续执行代码?

python selenium如何仅在函数可用时执行_script