使用动态键合并和求和对象数组

Posted

技术标签:

【中文标题】使用动态键合并和求和对象数组【英文标题】:Merge and sum array of objects with dynamic keys 【发布时间】:2021-12-27 01:26:35 【问题描述】:

我有一个对象数组的数据结构,其中花费了许多不同角色的小时数,如下所示:

    ["week 01" : "Developer" : 1, 
    "week 01" : "Project Manager" : 5, 
    "week 01" : "Project Manager" : 6, 
    "week 01": "Developer" : 8,
    "week 02" : "Strategy" : 4]

角色和周是动态的(即我事先不知道键/值),我想将同一角色和同一周的所有时间相加以获得以下输出:

["week 01" : 
   "Developer" : 9
   "Project Manager" : 11
    
, 
"week 02" : 
    "Strategy" : 4
    
]

我曾尝试使用reduce 函数,但似乎无法正确使用:

function reduceArray(arr) 
  const result = arr.reduce((result, item) => 
    let obj = ;
    const existing = result.find(function (x)  
        for (var prop in x) 
           if (x[prop] === item[prop]) 
              //same week number found 
              //loop through roles 
              for (var subProp in x[prop]) 
                  if (x[prop][subProp] === item[prop][subProp]) 
                     obj[prop][subProp] += x[prop][subProp] + item[prop][subProp]
                     //This is where I lose myself in the prop-loops....

                  
              
           
        
    )
  , [])
  return result; 

【问题讨论】:

你真的需要每个星期的数组中的单个对象吗?为什么不只使用一个对象? 我在一个非结构化的 Excel 表中循环,所有的日子都是单独的行,所以每个条目都是一个独立的单元格,这就是为什么它们最终成为单个对象 但是是的,最终结果绝对可能看起来像 "week 01" : role: hours, "week 02" : role : hours 【参考方案1】:

如果每个条目只有一个(参见第二个[0])角色,您可以使用以下内容:

const summary = work.reduce((acc,cur) => 
    let [week, val] = Object.entries(cur)[0];
    let [role, hours] = Object.entries(val)[0];
    acc[week] = acc[week] || ;
    acc[week][role] = (acc[week][role] || 0) + hours;
    return acc;
,);

/* OUTPUT:

  "week 01": 
    "Developer": 9,
    "Project Manager": 11
  ,
  "week 02": 
    "Strategy": 4
  

*/

演示

const work = ["week 01" : "Developer" : 1, 
    "week 01" : "Project Manager" : 5, 
    "week 01" : "Project Manager" : 6, 
    "week 01": "Developer" : 8,
    "week 02" : "Strategy" : 4]
    
const summary = work.reduce((acc,cur) => 
    let [week, val] = Object.entries(cur)[0];
    let [role, hours] = Object.entries(val)[0];
    acc[week] = acc[week] || ;
    acc[week][role] = (acc[week][role] || 0) + hours;
    return acc;
,);

console.log(summary);

//To convert summary into an array of objects

const arrsum = Object.entries(summary)
    .map(entry => Object.fromEntries([entry]));
    
console.log( arrsum );

要将输出转换为对象数组,请使用以下代码:

const arrsum = Object.entries(summary)
.map(entry => Object.fromEntries([entry]));

【讨论】:

【参考方案2】:

按周数分组,然后按作业名称对每周数分组并求和。

const arr = [
   "week 01":  Developer: 1  ,
   "week 01":  "Project Manager": 5  ,
   "week 01":  "Project Manager": 6  ,
   "week 01":  Developer: 8  ,
   "week 02":  Strategy: 4  ,
];

// First group By weeks num:
function groupByWeekNum(weeksArray) 
  return weeksArray.reduce((weeks, currWeek) => 
    const weekNum = Object.entries(currWeek)[0][0];
    weeks[weekNum] ??= [];
    weeks[weekNum].push(currWeek);
    return weeks;
  , );

结果如下:


    "week 01": [
        
            "week 01": 
                "Developer": 1
            
        ,
        
            "week 01": 
                "Project Manager": 5
            
        ,
        
            "week 01": 
                "Project Manager": 6
            
        ,
        
            "week 01": 
                "Developer": 8
            
        
    ],
    "week 02": [
        
            "week 02": 
                "Strategy": 4
            
        
    ]

然后是每一周的分组,按Job分组,累加每个job.Developer的值

function groupByWeekJob(jobsArr) 
  const grouping = jobsArr.reduce((jobs, currJob) => 
    const jobName = Object.entries(currJob)[0][0];
    const jobValue = Object.entries(currJob)[0][1];
    jobs[jobName] ??= 0;
    if (jobName in jobs) 
      jobs[jobName] += jobValue;
     else 
      jobs[jobName] = jobValue;
    
    return jobs;
  , );
  return grouping;

调用和调用函数:

const res = Object.entries(groupByWeekNum(arr)).map(([week, weekJobs]) =>
  groupByWeekJob(weekJobs.map((week) => Object.entries(week)[0][1]))
);

结果 console.log(res, null, 4);:

[
    
        "Developer": 9,
        "Project Manager": 11
    ,
    
        "Strategy": 4
    
]

【讨论】:

【参考方案3】:

这是一个简单的方法。希望 cmets 有用。

const data = [
   "week 01":  Developer: 1  ,
   "week 01":  "Project Manager": 5  ,
   "week 01":  "Project Manager": 6  ,
   "week 01":  Developer: 8  ,
   "week 02":  Strategy: 4  
];

function cleanUp(data) 
  return data.reduce((acc, item) => 
    // assign variables
    const week = Object.keys(item).pop();
    const [job, value] = Object.entries(item[week]).pop();
    // initialise them, if they aren't in the accumilator.
    if (!acc[week]) acc[week] = ;
    if (!acc[week][job]) acc[week][job] = 0;
    // add the value
    acc[week][job] += value;
    return acc;
  , );


console.log(cleanUp(data));

这是一个使用for...in 而不是Object.keys()Object.entries()

const data = [
   "week 01":  Developer: 1  ,
   "week 01":  "Project Manager": 5  ,
   "week 01":  "Project Manager": 6  ,
   "week 01":  Developer: 8  ,
   "week 02":  Strategy: 4  
];

function cleanUp(data) 
  return data.reduce((acc, item) => 
    for (const week in item) 
      if (!acc[week]) acc[week] = ;
      for (const job in item[week]) 
        if (!acc[week][job]) acc[week][job] = 0;
        acc[week][job] += item[week][job];
      
    
    return acc;
  , );


console.log(cleanUp(data));

【讨论】:

【参考方案4】:

使用嵌套循环。在纯 javascript 中你可能会得到类似的东西

const data = [ "week 01":  "Developer": 1  ,
 "week 01":  "Project Manager": 5  ,
 "week 01":  "Project Manager": 6  ,
 "week 01":  "Developer": 8  ,
 "week 02":  "Strategy": 4  ];

function getTotalHoursPerRole(data) 
    let totalHoursPerRole = [];
    for (let i = 0; i < data.length; i++) 
        for (let week in data[i]) 
            let roleWithHour = data[i][week];
            for (let role in roleWithHour) 
                if (!totalHoursPerRole[week]) totalHoursPerRole[week] = ;
                if (!totalHoursPerRole[week][role]) totalHoursPerRole[week][role] = 0;
                totalHoursPerRole[week][role] += roleWithHour[role];
            

        
    
    return totalHoursPerRole;


console.log(getTotalHoursPerRole(data));

【讨论】:

【参考方案5】:

const data = [
   "week 01":  Developer: 1  ,
   "week 01":  "Project Manager": 5  ,
   "week 01":  "Project Manager": 6  ,
   "week 01":  Developer: 8  ,
   "week 02":  Strategy: 4  ,
];

const result = data.reduce(
  (prev, item) => 
    for (const week in item) 
      if (prev[week]) 
        for (const act in item[week]) 
          if (prev[week][act]) 
            prev[week][act] += item[week][act];
           else 
            prev[week][act] = item[week][act];
          
        
       else 
        prev[week] = item[week];
      
    

    return prev;
  ,
  
);

console.log(result);

【讨论】:

这正是我想要实现的。当它全部用明文写出来时,看起来很简单。非常感谢您的帮助【参考方案6】:

我会使用反射和嵌套循环而不是 reduce。

const weeks = ;
for(let cell of array)
    //Assumes your cells will only have one key and that key is a week
    for(let key in Object.keys(cell))
        if(!weeks[key])
            weeks[key] = ;
        
        //Similiar assumption here
        for(let role in Object.keys(cell[key]))
            weeks[key][role] = (weeks[key][role] ? weeks[key][role] : 0) + cell[key][role];
        
    
    

【讨论】:

【参考方案7】:

您可以使用所有类型的累积值生成单个对象。

const
    data = [ "week 01":  Developer: 1  ,  "week 01":  "Project Manager": 5  ,  "week 01":  "Project Manager" : 6  ,  "week 01":  Developer : 8  ,  "week 02":  Strategy: 4  ],
    result = data.reduce((r, o) => 
        Object
            .entries(o)
            .forEach(([week, q]) => Object
                .entries(q)
                .forEach(([type, value]) => 
                    (r[week] ??= )[type] ??= 0;
                    r[week][type] += value;
                )
            );
        return r;
    , );
    
console.log(result);
.as-console-wrapper  max-height: 100% !important; top: 0; 

【讨论】:

以上是关于使用动态键合并和求和对象数组的主要内容,如果未能解决你的问题,请参考以下文章

如何根据javascript中的键合并和替换两个数组中的对象?

将键数组和值数组合并到 JavaScript 中的对象中

使用相同的键合并来自不同对象的数组

无法使用 OPENJSON 和 SQL Server 2017 解析具有动态键的对象数组

sh 使用jq从多个JSON文件的“对象”键合并数组

JavaScript 常用技巧