【问题标题】:How to do equivalent of LINQ SelectMany() just in javascript如何仅在 javascript 中执行相当于 LINQ SelectMany() 的操作
【发布时间】: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,看看答案有多具体。没有太多可能的答案,最好的答案简短明了。

标签: javascript c#


【解决方案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() 在空数组上失败,因此您必须添加一个初始值。见stackoverflow.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 数组,你可能会对我最近在这里添加的解决方案感兴趣:stackoverflow.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

          【讨论】:

            猜你喜欢
            • 2013-01-07
            • 1970-01-01
            • 2015-01-19
            • 1970-01-01
            • 2011-05-03
            • 2010-09-12
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多