【问题标题】:Group By, Count Distinct, Sum Totals From Objects in JavaScript ArrayJavaScript 数组中对象的分组、计数、总和
【发布时间】:2018-08-22 16:31:38
【问题描述】:

这可能是补救措施,但我无法弄清楚。我尝试使用 d3 并使用 lodash 来获得有效的解决方案,但没有得到任何接近。

我在 JavaScript 中有一个对象数组。如果 [Selected] 值为 true,我想创建一个按 [Version Name] 分组的对象,其中包含 distinct 区域的计数,以及每个版本的总数。比如对象...

[ 
     { Selcted: false, Version Name: "aaa", Zone: "11111", Value: 5 },
     { Selcted: false, Version Name: "aaa", Zone: "11111", Value: 10 },
     { Selcted: true, Version Name: "aaa", Zone: "11111", Value: 15 },
     { Selcted: true, Version Name: "aaa", Zone: "11111", Value: 20 },
     { Selcted: true, Version Name: "aaa", Zone: "22222", Value: 25 },
     { Selcted: true, Version Name: "bbb", Zone: "22222", Value: 30 },
     { Selcted: true, Version Name: "bbb", Zone: "22222", Value: 35 },
     { Selcted: true, Version Name: "bbb", Zone: "2222", Value: 40 }
]

应该返回一个结果

[ 
     { Version Name: "aaa", Zone Count: "2", Value Sum: 50 },
     { Version Name: "bbb", Zone Count: "1", Value Sum: 105 },
]

【问题讨论】:

  • 您的输出错误。 aaa 有 60 个,bbb 有 2 个不同的区域。另外,您意识到您在Selcted 上有一个错字

标签: javascript


【解决方案1】:

您可以使用 lodash 并在使用分组后获得想要的计数

  • _“Seq”方法,用于链接 lodash 方法,
  • _.groupBy"Version Name" 分组
  • _.map 用于结果集
  • _.uniqBy 用于计算不同的值,
  • _.sumBy 求和 Value
  • _.value 用于获取以对象作为结果集的数组。

var data = [{ Selected: false, "Version Name": "aaa", Zone: "11111", Value: 5 }, { Selected: false, "Version Name": "aaa", Zone: "11111", Value: 10 }, { Selected: true, "Version Name": "aaa", Zone: "11111", Value: 15 }, { Selected: true, "Version Name": "aaa", Zone: "11111", Value: 20 }, { Selected: true, "Version Name": "aaa", Zone: "22222", Value: 25 }, { Selected: true, "Version Name": "bbb", Zone: "22222", Value: 30 }, { Selected: true, "Version Name": "bbb", Zone: "22222", Value: 35 }, { Selected: true, "Version Name": "bbb", Zone: "22222", Value: 40 }],
    result = _(data)
        .groupBy('Version Name')
        .map((array, key) => ({
            "Version Name": key,
            "Zone Count": _.uniqBy(array, 'Zone').length,
            "Value Sum": _.sumBy(array, 'Value')
        }))
        .value();

console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.15.0/lodash.min.js"></script>

【讨论】:

    【解决方案2】:

    首先过滤掉,然后分组,然后计算区域,为此我们需要数组上的一些辅助函数:

      Array.prototype.groupBy = function groupBy(key) {
        const hash = {}, result = [];
        for(const el of this) {
           if(hash[ el[key] ]) {
             hash[ el[key] ].push(el);
           } else {
             result.push({
               key: el[key],
               values: hash[ el[key] ] = [ el ],
            });
         }
      }
      return result;
     };
    
     Array.prototype.key = function(key) {
       return this.map(el => el[key]);
     };
    
     Array.prototype.sum = function(key) {
      return this.reduce((total, el) => total + (key ? el[key] : el), 0);
     };
    
     Array.prototype.unique = function() {
       return [...new Set(this)];
     };
    

    这实际上是相当多的代码,但现在我们可以使用它来构建我们的结果:

      const result = array
           .filter(el =>  el.Selected)
           .groupBy("Version Name")
           .map(({ key, values }) => ({
             "Version Name": key,
             "Value Sum": values.sum("Sum"),
             "Zone Count":values.key("Zone").unique().length,
           }));
    

    【讨论】:

      【解决方案3】:

      这使用了我最喜欢的 groupBy 函数 :) 获得组后,您可以进行另一组以获取区域计数,并减少以获取总和。

      简而言之

      const byName = groupBy(input.filter(it => it.Selcted), it => it['Version Name'])
      
      const output = Object.keys(byName).map(name => {
        const byZone = groupBy(byName[name], it => it.Zone)
        const sum = byName[name].reduce((acc, it) => acc + it.Value, 0)
        return {
          'Version Name': name,
          ZoneCount: Object.keys(byZone).length,
          ValueSum: sum
        }
      })
      

      别忘了,您需要在“版本名称”周围加上引号才能将其用作键。

      这是您的数据集的一个工作示例。

      function groupBy(a, keyFunction) {
        const groups = {};
        a.forEach(function(el) {
          const key = keyFunction(el);
          if (key in groups === false) {
            groups[key] = [];
          }
          groups[key].push(el);
        });
        return groups;
      }
      
      const input = [{
          Selcted: false,
          'Version Name': "aaa",
          Zone: "11111",
          Value: 5
        },
        {
          Selcted: false,
          'Version Name': "aaa",
          Zone: "11111",
          Value: 10
        },
        {
          Selcted: true,
          'Version Name': "aaa",
          Zone: "11111",
          Value: 15
        },
        {
          Selcted: true,
          'Version Name': "aaa",
          Zone: "11111",
          Value: 20
        },
        {
          Selcted: true,
          'Version Name': "aaa",
          Zone: "22222",
          Value: 25
        },
        {
          Selcted: true,
          'Version Name': "bbb",
          Zone: "22222",
          Value: 30
        },
        {
          Selcted: true,
          'Version Name': "bbb",
          Zone: "22222",
          Value: 35
        },
        {
          Selcted: true,
          'Version Name': "bbb",
          Zone: "2222",
          Value: 40
        }
      ]
      
      const byName = groupBy(input.filter(it => it.Selcted), it => it['Version Name'])
      
      const output = Object.keys(byName).map(name => {
        const byZone = groupBy(byName[name], it => it.Zone)
        const sum = byName[name].reduce((acc, it) => acc + it.Value, 0)
        return {
          'Version Name': name,
          ZoneCount: Object.keys(byZone).length,
          ValueSum: sum
        }
      })
      
      
      console.log(output)

      【讨论】:

        【解决方案4】:

        使用filter 获取您想要的项目后,您可以分两步完成此操作,reduce 后跟 map

        let input = [ 
             { Selcted: false, "Version Name": "aaa", Zone: "11111", Value: 5 },
             { Selcted: false, "Version Name": "aaa", Zone: "11111", Value: 10 },
             { Selcted: true, "Version Name": "aaa", Zone: "11111", Value: 15 },
             { Selcted: true, "Version Name": "aaa", Zone: "11111", Value: 20 },
             { Selcted: true, "Version Name": "aaa", Zone: "22222", Value: 25 },
             { Selcted: true, "Version Name": "bbb", Zone: "22222", Value: 30 },
             { Selcted: true, "Version Name": "bbb", Zone: "22222", Value: 35 },
             { Selcted: true, "Version Name": "bbb", Zone: "2222", Value: 40 }
        ]
        
        var result = input.filter(x => x.Selcted)
                          .reduce( (acc, curr) => {
                              let item = acc.find(x => x.version == curr["Version Name"]);
                              if(!item){
                                  item = {version: curr["Version Name"], zones:{}}
                                  acc.push(item);
                              }
                              item.zones[curr.Zone] = (item.zones[curr.Zone] || 0) + curr.Value
                              return acc;
                          },[])
                          .map(x => ({
                            "Version Name": x.version,
                            "Zone Count": Object.keys(x.zones).length,
                            "Value Sum": Object.values(x.zones).reduce( (a,b) => a+b ,0)
                          }))
                          
        console.log(result);

        【讨论】:

          【解决方案5】:

          假设您的数据数组名为arr

          const summed = Object.values(
            arr
              .filter(x => x.Selected)
              .reduce((acc, obj) => {
                if (!acc[obj["Version Name"]) {
                  acc["Version Name"] = {
                    "Version Name": acc["Version Name"],
                    "Zones": new Set(),
                    "Value Sum": 0,
                  };
                }
                acc["Version Name"]["Value Sum"] += obj.Value;
                acc["Version Name"].Zones.add(obj.Zone);
                return acc;
              })
          ).map(obj => {
            return {
              "Version Name": obj["Version Name"],
              "Zone Count": obj.Zones.size,
              "Value Sum": obj["Value Sum"],
            };
          });
          

          首先,我们过滤掉未选择的对象,并通过版本名称的散列得到一个不同的对象数组,聚合值,使用 Set 跟踪不同的区域,然后使用 Object.values 将散列转换回大批。然后我们对其进行映射以获得区域计数的每个 Set 的大小。

          【讨论】:

            【解决方案6】:

            您可以为此使用Array.reduce()Set() 创建地图,然后地图上的Object.values() 将为您提供所需的结果。尝试以下方法:

            let arr = [ { Selected: false, VersionName: "aaa", Zone: "11111", Value: 5 }, { Selected: false, VersionName: "aaa", Zone: "11111", Value: 10 }, { Selected: true, VersionName: "aaa", Zone: "11111", Value: 15 }, { Selected: true, VersionName: "aaa", Zone: "11111", Value: 20 }, { Selected: true, VersionName: "aaa", Zone: "22222", Value: 25 }, { Selected: true, VersionName: "bbb", Zone: "22222", Value: 30 }, { Selected: true, VersionName: "bbb", Zone: "22222", Value: 35 }, { Selected: true, VersionName: "bbb", Zone: "2222", Value: 40 } ];
            let set = new Set();
            
            let result = Object.values(arr.reduce((a, curr)=>{
              if(curr.Selected){
                a[curr.VersionName] = a[curr.VersionName] || {versionName : curr.VersionName, ZoneCount : 0, ValueSum : 0} ;
                  if(!set.has(curr.Zone+"_"+curr.VersionName)){
                    a[curr.VersionName].ZoneCount += 1;
                    set.add(curr.Zone+"_"+curr.VersionName);
                  }
                 a[curr.VersionName].ValueSum += curr.Value;
               }
              return a;
            },{}));
            
            console.log(result);

            【讨论】:

            • @JonasWilms 它运行良好,没有任何错误。对我来说很好:-(
            • 嗯 .. 那么它是 SO sn-ps 的错误,但是......你确定全局 set 会起作用吗?
            • @JonasWilms 谢谢更新了答案 :-) 是的,如果我添加一个包含 Zone 和 versionName 组合的条目,全局设置正在工作。
            【解决方案7】:

            我认为最简单的解决方案是使用reducefilter

            const selectedObjects = objects.filter((i) => i.Selected === true);
            
            const results = selectedObjects.reduce((items, obj) => {
                let item = items.find((i) => i.VersionName === obj["Version Name"]);
                if (!item) {
                    const ZoneCount = selectedObjects
                        .filter((i) => i["Version Name"] === obj["Version Name"])
                        .reduce((zones, obj) => {
                            if (zones.indexOf(obj.Zone) === -1) {
                                zones.push(obj.Zone);
                            }
                            return zones;
                        }, []).length;
            
                    item = {
                        ValueSum: 0,
                        VersionName: obj["Version Name"],
                        ZoneCount
                    };
            
                    items.push(item);
                }
            
                item.ValueSum += obj.Value;
            
                return items;
            }, []);
            
            console.log(results);
            

            【讨论】:

            • @JonasWilms 是的!谢谢。我已经确定了答案。
            【解决方案8】:

            我觉得你可以用一个 reduce 来做到这一点,使用 2 个哈希(因为你想要它在 vanilla JS 中)并且只遍历整个数组一次。似乎您想在开始时进行过滤,但是您可以执行一系列操作,例如

            filter(...).map(...).reduce().continue().blabla(...)(使用纯 js 或任何库,如 lodashunderscore...等)

            现在,

            要么分离出逻辑并执行易于理解和维护的更简洁的操作(例如:Nina Scholz's answer 中的那个)

            或者,使用reduce(或简单的for循环)只遍历一次并执行所有必要的操作并尝试避免一系列遍历,而是使用更多的内存来缓存它,如散列(或任何适合的)。

            这是我创建的一个示例

            let input = [{ Selected: false, "Version Name": "aaa", Zone: "11111", Value: 5 }, { Selected: false, "Version Name": "aaa", Zone: "11111", Value: 10 }, { Selected: true, "Version Name": "aaa", Zone: "11111", Value: 15 }, { Selected: true, "Version Name": "aaa", Zone: "11111", Value: 20 }, { Selected: true, "Version Name": "aaa", Zone: "22222", Value: 25 }, { Selected: true, "Version Name": "bbb", Zone: "22222", Value: 30 }, { Selected: true, "Version Name": "bbb", Zone: "22222", Value: 35 }, { Selected: true, "Version Name": "bbb", Zone: "22222", Value: 40 }],
                vn = "Version Name", vs = "Value Sum", zc = "Zone Count", //Target Keys
                {res} = input.reduce(({res={}, hash={}}, e) => {
            	if(e.Selected) {
            		let vrsn = e[vn],
            		vrsnGrp = (hash[vrsn] = hash[vrsn] || {});
            		res[vrsn] = (res[vrsn] || {[vn]: vrsn, [vs]:0, [zc]: 0});
            		res[vrsn][vs] += e.Value;
            		res[vrsn][zc] += !vrsnGrp[e.Zone];
            		vrsnGrp[e.Zone] = true;
            	}
            	return {res, hash};
            }, {});
            
            console.log(Object.values(res));
            .as-console-wrapper { max-height: 100% !important; top: 0; }

            它将执行所有必要的操作(过滤、分组...),但只遍历输入数组一次,它只会使用一个额外的哈希(因为res 在这里也是一个哈希)。

            【讨论】:

              【解决方案9】:
              let campos = ["classificacao", "count_tables"];
              let conteudo = mysql_information_schema[cliente][projeto][dbName].conteudo;
              let count = {};
              conteudo.forEach( (tabela)=> { //agrupa dentro de count
                   count[tabela.classificacao] = count[tabela.classificacao] > 0 ? count[tabela.classificacao] + 1 : 1 ;
              });
              //converte o objeto para o padrão do array object da view
              conteudo =  Object.keys(count).map( (key)=>{return {classificacao: key,  count_tables: count[key]} } ); 
              

              【讨论】:

              • 嗨 Mateus,为您的解决方案添加解释总是一个好主意!
              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2017-01-17
              • 2021-03-11
              • 2016-09-25
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2018-08-05
              相关资源
              最近更新 更多