按日期减少对象

问题描述

我有一个这样的数组:

transactionData = [
  { MaterialID: "3000745",TransactionQuantity: "2.000",TransactionDate: "01/15/2020"
  },{ MaterialID: "3000745",TransactionQuantity: "3.000",TransactionDate: "2/24/2020"
  },{ MaterialID: "3000051",TransactionQuantity: "1.000",TransactionDate: "3/10/2020"
  },TransactionDate: "4/01/2020"
  }
]

还有一个像这样的对象:

dateObj = {
  "Jan-20": { sum: 0,count: 0 },"Feb-20": { sum: 0,"Mar-20": { sum: 0,"Apr-20": { sum: 0,count: 0 }
}

我正在尝试减少 transactionData 并返回这样的对象:

{
  "3000051": {
      "monthlyVolume": {
          "Jan-20": {
            "sum": 0,"count": 0
          },"Feb-20": {
            "sum": 0,"Mar-20": {
            "sum": 1,"count": 1
          },"Apr-20": {
            "sum": 1,"count": 1
          }
      }
  },"3000745": {
      "monthlyVolume": {
        "Jan-20": {
            "sum": 2,"count": 1
        },"Feb-20": {
            "sum": 3,"Mar-20": {
            "sum": 0,"count": 0
        },"Apr-20": {
            "sum": 0,"count": 0
        }
      }
   }
}

出于某种原因,我的 reduce 函数为两个 MaterialID 返回了相同的值。我已经为此工作了几个小时,但无法弄清楚我做错了什么。我非常感谢 SO 社区的任何帮助。

以下是我的代码(注意,我使用 dayjs 进行格式化)。

 let result = transactionData.reduce((acc,{
    MaterialID,TransactionDate,TransactionQuantity }) => {
    let date = dayjs(TransactionDate).format("MMM-YY");

  const _material = (acc[MaterialID] ?? = { monthlyVolume: dateObj });

  _material.monthlyVolume[date].sum += parseFloat(TransactionQuantity);
  _material.monthlyVolume[date].count++;

    return acc;
  },{}
)

这是我尝试的代码笔。

Codepen Example

解决方法

1)您已经使用过

const _material = (acc[MaterialID] ?? = { monthlyVolume: dateObj });

由于 ?? = 之间的空格而在语法上不正确

2) 您必须克隆 dateObj 为每个 MaterialID 创建 2 个单独的对象

function createDateObjClone(obj) {
  return Object.keys(obj).reduce((acc,curr) => {
    acc[curr] = { ...obj[curr] };
    return acc;
  },{});
}

并将其用作

monthlyVolume: { ...createDateObjClone(dateObj) }

YOUR NEW RUNNING CODEPEN EXAMPLE

你也可以试试我的方案

const transactionData = [
  {
    MaterialID: "3000745",TransactionQuantity: "2.000",TransactionDate: "01/15/2020",},{
    MaterialID: "3000745",TransactionQuantity: "3.000",TransactionDate: "2/24/2020",{
    MaterialID: "3000051",TransactionQuantity: "1.000",TransactionDate: "3/10/2020",TransactionDate: "4/01/2020",];

const dateDict = [
  "Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec",];

const dateObj = {
  "Jan-20": { sum: 0,count: 0 },"Feb-20": { sum: 0,"Mar-20": { sum: 0,"Apr-20": { sum: 0,};

const getMonthObj = (acc,MaterialID,{ month,year }) => acc[MaterialID]["monthlyVolume"][`${dateDict[month - 1]}-${year.slice(-2)}`];

function createDateObjClone(obj) {
  return Object.keys(obj).reduce((acc,{});
}

function addData(target,sum) {
  target.sum = +sum;
  target.count++;
}

const result = transactionData.reduce((acc,curr) => {
  const { MaterialID,TransactionQuantity,TransactionDate } = curr;
  const [month,year] = TransactionDate.split("/");

  if (!acc[MaterialID])
    acc[MaterialID] = { monthlyVolume: { ...createDateObjClone(dateObj) } };

  const obj = getMonthObj(acc,year });
  if (obj) addData(obj,TransactionQuantity);

  return acc;
},{});

console.log(result);
/* This is not a part of answer. It is just to give the output fill height. So IGNORE IT */
.as-console-wrapper { max-height: 100% !important; top: 0; }

,

罪魁祸首在这里:

_material = (acc[MaterialID] ?? = { monthlyVolume: dateObj }

让我们清理一下:

_material = { monthlyVolume: dateObj }

这将使所有 monthlyVolume 属性引用与 DataObj 完全相同的内容,即您从数据库中获取的未命名对象。但是,当然,任何持有对它的引用的人都可以自由修改它,因此您最终会将据称对不同项目执行的所有总和累积到这个单一的公共接收者中。打印您的销售记录只会显示相同的子对象两次。

const 限定符添加到 _material 不会改变任何事情。它对该标识符引用的对象没有影响。

我们问题的极简再现:

var DataObj = { surprise: 123 } // DataObj references a freshly created object
var record1 = { data: DataObj } // record1 is a freshly created object too,*but*
                                // "record1.data" references the same
                                // object as our old DataObj buddy
var record2 = { data: DataObj } // same thing for any number of similar references

record1.data.surprise+= 456; // anyone referencing the underlying object
console.log(DataObj);        // is free to change its value,which will be
console.log (record1.data);  // reflected in every identifier that can access it
console.log (record2.data);

>>> Object { surprise: 579 }
>>> Object { surprise: 579 }
>>> Object { surprise: 579 }

现在观看这个深奥的魔法:

var Phoney_Identity = obj => ({ ...obj }) // a cryptic way of shallow-copying an object
var DataObj = { surprise: 123 } // DataObj is a freshly created object
var a_buddy_of_DataObj = Phoney_Identity (DataObj) // its buddy is a shallow copy
DataObj.surprise = 42 // which is enough to decorrelate them
console.log (DataObj) // as long as they don't contain arrays or objects
console.log (a_buddy_of_DataObj)

>>> Object { surprise: 42 }
>>> Object { surprise: 123 }

如果对象包含数组或子对象,则您必须递归地重做相同的技巧以获得真正的深拷贝。因此名称中带有“虚假”。

大多数被复制的伪功能片段碰巧都能工作,因为大多数对象要么从未修改过,要么从头开始创建(通过可怕的、不纯的代码!)。此外,神圣不可侵犯的 mapfilterreduce 三部曲都返回其参数的浅拷贝,这进一步有助于避免仅进行轻微修改时的相同陷阱。但是当您开始处理稍微复杂的数据结构时,整个事情可能很快就会在您面前炸开。

没有辅助库,尝试玩函数式编程游戏会带来严重的麻烦。此外,对 lambda 函数的混淆、扩展运算符和解构是这些讨厌的小错误的完美藏身之处。