【问题标题】:How to aggregate object values by key in another object如何在另一个对象中按键聚合对象值
【发布时间】:2021-09-02 16:24:49
【问题描述】:

我有两个对象数组,如下所示:

array1 = [
   { material: "ABC123", cost: 100 },
   { material: "DEF456", cost: 150 }
]

array2 = [
  { material: "ABC123", date: "1/1/20", quantity: 4 },
  { material: "ABC123", date: "1/15/20", quantity: 1 },
  { material: "ABC123", date: "2/15/20", quantity: 3 },
  { material: "ABC123", date: "4/15/21", quantity: 1 },
  { material: "DEF456", date: "3/05/20", quantity: 6 },
  { material: "DEF456", date: "3/18/20", quantity: 1 },
  { material: "DEF456", date: "5/15/21", quantity: 2 }
]

我想创建一个新的对象数组,其中包括 array1 中的所有键/值对以及每个项目按年份和月份的聚合数量。

结果是:

[
ABC123: {
    cost: 100,
    byYear: {
        2020: {
            byMonth: { 
                1: 5,
                2: 3
            }
        }
    },
    {
        2021: {
            byMonth: {
                4: 1
            }
        }
    },
},
DEF456: {
    cost: 150,
    byYear: {
        2020: {
            byMonth: {
                3: 7,
                2: 3
            }
        }
    },
    {
        2021: {
            byMonth: {
                5: 2
            }
        }
    },
}
]

到目前为止,以下是我的代码,使用的是 lodash,但解决方案中不需要。我遇到的问题是在每个项目下创建的年和月键并不特定于该项目。每个项目都获取所有项目都存在的年份和月份键。

let itemSummary = {};

 _.forEach(array1, function (item) {
    itemSummary[item["material"]] = itemSummary[item["material"]] || {}; // create material as key
    itemSummary[item["material"]] = item; // add props from array1 under each key

    _.forEach(array2, function (trans) {
      // iterate through transactions and aggregate by year and month
      let transactionYear = new Date(trans["date"]).getFullYear();
      let transactionMonth = new Date(trans["date"]).getMonth() + 1;
      itemSummary[item["material"]]["byYear"] = itemSummary[item["material"]]["byYear"] || {}; //create year key
      itemSummary[item["material"]]["byYear"][transactionYear] = itemSummary[item["material"]]["byYear"][transactionYear] || {}; // set year key

      itemSummary[item["material"]]["byYear"][transactionYear]["byMonth"] = itemSummary[item["material"]]["byYear"][transactionYear]["byMonth"] || {};
      itemSummary[item["material"]]["byYear"][transactionYear]["byMonth"][transactionMonth] =
        itemSummary[item["material"]]["byYear"][transactionYear]["byMonth"][transactionMonth] || {};
    });
  });

显然,这并没有像我上面提到的那样汇总数量,因为我首先需要为每个项目获取正确的年份和月份键。

非常感谢任何帮助

【问题讨论】:

  • 为什么是material/partpart/item?为什么结果是数组?
  • 不错,错字。一切都应该是物质的,更新了帖子。也可以取一个对象作为结果。

标签: javascript arrays object lodash


【解决方案1】:

您可以对所有分组和另一个对象采用动态方法来获得最终总和。

const
    array1 = [{ material: "ABC123", cost: 100 }, { material: "DEF456", cost: 150 }],
    array2 = [{ material: "ABC123", date: "1/1/20", quantity: 4 }, { material: "ABC123", date: "1/15/20", quantity: 1 }, { material: "ABC123", date: "2/15/20", quantity: 3 }, { material: "ABC123", date: "4/15/21", quantity: 1 }, { material: "DEF456", date: "3/05/20", quantity: 6 }, { material: "DEF456", date: "3/18/20", quantity: 1 }, { material: "DEF456", date: "5/15/21", quantity: 2 }],
    groups = [
        ({ material }) => material,
        _ => 'byYear',
        ({ date }) => '20' + date.split('/')[2],
        _ => 'byMonth'
    ],
    sum = {
        key: ({ date }) => date.split('/')[0],
        value: ({ quantity }) => quantity
    }, 
    result = array2.reduce(
        (r, o) => {
            const temp = groups.reduce((t, fn) => t[fn(o)] ??= {}, r);
            temp[sum.key(o)] ??= 0;
            temp[sum.key(o)] += sum.value(o);
            return r;
        },
        Object.fromEntries(array1.map(({ material, cost }) => [material, { cost }]))
    );

console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

如有必要,您可以添加进一步的分组并添加一个函数数组以进行最终聚合。

const
    array1 = [{ material: "ABC123", cost: 100 }, { material: "DEF456", cost: 150 }],
    array2 = [{ material: "ABC123", date: "1/1/20", quantity: 4 }, { material: "ABC123", date: "1/15/20", quantity: 1 }, { material: "ABC123", date: "2/15/20", quantity: 3 }, { material: "ABC123", date: "4/15/21", quantity: 1 }, { material: "DEF456", date: "3/05/20", quantity: 6 }, { material: "DEF456", date: "3/18/20", quantity: 1 }, { material: "DEF456", date: "5/15/21", quantity: 2 }],
    groups = [
        ({ material }) => material,
        _ => 'byYear',
        ({ date }) => '20' + date.split('/')[2],
        _ => 'byMonth',
        ({ date }) => date.split('/')[0]
    ],
    ultimately = [
        (target, { quantity }) => {
            target.sum ??= 0;
            target.sum += quantity;
        },
        (target, { quantity }) => {
            target.max ??= quantity;
            if (quantity > target.max) target.max = quantity;
        }
    ],
    result = array2.reduce(
        (r, o) => {
            const temp = groups.reduce((t, fn) => t[fn(o)] ??= {}, r);
            ultimately.forEach(fn => fn(temp, o));
            return r;
        },
        Object.fromEntries(array1.map(({ material, cost }) => [material, { cost }]))
    );

console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

【讨论】:

  • 这是一个出色的解决方案,在 100K 行数据上运行速度非常快。您能否建议我如何添加每个月的最大交易量(除了总和)?
  • 这种情况下的结果应该如何?
  • byMonth 键看起来像... byMonth: { 3: { sum: 2, max: 1 }, 4: {sum: 4, max: 3} }
【解决方案2】:

可能有多种方法可以达到您想要的格式,这里是其中之一。我选择以 array1 为基础,因为这似乎是主列表,但您也可以以 array2 为基础。

我们从 array1 中获取每个项目,从 array2 中过滤匹配的材料,然后通过 reduce 运行它们,我们解析日期并构建对象以存储 byYear 和 byMonth

array1 = [
   { material: "ABC123", cost: 100 },
   { material: "DEF456", cost: 150 }
]

array2 = [
  { material: "ABC123", date: "1/1/20", quantity: 4 },
  { material: "ABC123", date: "1/15/20", quantity: 1 },
  { material: "ABC123", date: "2/15/20", quantity: 3 },
  { material: "ABC123", date: "4/15/21", quantity: 1 },
  { material: "DEF456", date: "3/05/20", quantity: 6 },
  { material: "DEF456", date: "3/18/20", quantity: 1 },
  { material: "DEF456", date: "5/15/21", quantity: 2 }
]

let data = array1.reduce((b, a) => {
    let thing = a.hasOwnProperty('material') ? 'material' : 'part';
    let items = array2.filter(e => e.material === a[thing]).reduce((d, c) => { 
          let dt = c.date.split("/"),
            m = dt[0],
            y = new Date(dt).getFullYear()
          if (d.hasOwnProperty(y)) {
            if (d[y].byMonth.hasOwnProperty(m)) d[y].byMonth[m]+= +c.quantity;
            else d[y].byMonth[m] = c.quantity;
          } else d[y] = {byMonth: {[m]: c.quantity}};
        return d
      }, {}) ;

    b[a[thing]] = {
      cost: a.cost,
      byYear: items
    };
    return b
  }, {})
  
  console.log(data)

【讨论】:

  • 很抱歉,我用您修改过的数据更新了我的答案,但突然没有任何效果。但现在确实如此:)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-11-20
  • 2021-05-30
  • 2012-01-04
  • 1970-01-01
  • 2021-12-18
  • 1970-01-01
相关资源
最近更新 更多