随着时间的推移,对两个有序累积对象值数组进行求和。

5
我正在尝试计算两个数组随时间累积值的总和,看起来很简单,但是以下情况会使它变得复杂:其中一个数组可能缺少日期。
当一个数组有某个日期的值而另一个数组没有时,我们将这一存在于一个数组中的值和另一个数组中该日期上一次出现的值(前一个值)相加(我给出的示例可以更好地说明这一点)。
例如,假设有两个对象数组data1和data2,其中data2有一个日期的值而data1没有:
var data1 = [
    {date: "30-08-2019", value: 1},
    {date: "03-09-2019", value: 2},
    {date: "04-09-2019", value: 3}
]

var data2 = [
    {date: "30-08-2019", value: 1},
    {date: "02-09-2019", value: 2},
    {date: "03-09-2019", value: 3},
    {date: "04-09-2019", value: 4}
]

我希望将这两个数据相加的结果(data1 + data2)呈现为:

var result = [
    {date: "30-08-2019", value: 2}, //{date: "30-08-2019", value: 1} + {date: "30-08-2019", value: 1}
    {date: "02-09-2019", value: 3}, //{date: "30-08-2019", value: 1} + {date: "02-09-2019", value: 2}
    {date: "03-09-2019", value: 5}, //{date: "03-09-2019", value: 2} + {date: "03-09-2019", value: 3}
    {date: "04-09-2019", value: 7}  //{date: "04-09-2019", value: 3} + {date: "04-09-2019", value: 4}
]

由于两个数组都是有序的,我的想法是循环拥有更多数据的数组,并将其与拥有较少数据的数组的值相加,同时跟踪较小数据给出的最后日期值,方法如下:

for(let i = 0; i < biggerData.length; i++){

    //both have values for a date that exists the in bigger date array, so we sum them together
    if(smallerData[i][biggerData[i].date]){
       biggerData[i].value+=smallerData[i][biggerData[i].date];
       lastValue = smallerData[i][biggerData[i].date];

    //array with less data has a missing date, then sum the last saved value it gave
    }else{
       biggerData[i].value+=lastValue;
    }
}

这种方法有一个问题,如果较小的数组有一个更大的数组中没有的日期,那么这个值就不会被添加到最终结果中。
更进一步讲,我开始循环一个数组(如前所示),然后再循环另一个数组以获取缺失的日期,但是这似乎过于复杂和低效。我相信有一种解决方案可以在一个循环中完成这个任务(甚至可以不使用循环)。
我想询问是否有人能够想出更好的解决方案,我是用JavaScript编写的。

1
我对语言进行了轻微的编辑,我认为这可以澄清您所需的行为。如果我误解了,请还原。 - Mitya
2
首先,我会从两个数组中提取出所有的日期,并将它们放入一个新的结果数组中。接着循环遍历这个新数组,以确保你覆盖到所有相关的日期。 - CBroe
1
.concat() + "javascript 按属性分组对象数组" - Andreas
1
@Andreas 按属性分组不会起到作用,我想要“将一个数组中存在的值和另一个数组上一个日期的最后一次出现的值相加”。进行分组只会将共同的日期放在一起。 - hanuruh
2个回答

2

我使用了许多辅助变量,并将日期转换为易于排序的格式。按照时间顺序逐个查看所有现有日期,这样就很容易跟踪每个数组的累积值。排序是效率低下的部分,因为其余部分具有线性复杂度。您可以通过利用两个数组已经排好序的事实来优化排序,但我在这里懒得这样做 :)

// Turn '30-08-2019' into '2019-08-30'
const getSortableDate = (dateString) => dateString.split('-').reverse().join('-');

// Enable direct lookup of values
const mapDatesToValues = (data) => {
    const dates = {};
    data.forEach((item) => {
        dates[getSortableDate(item.date)] = item.value;
    });
    return dates;  
};

// Source data
const data1 = [
    {date: "30-08-2019", value: 1},
    {date: "03-09-2019", value: 2},
    {date: "04-09-2019", value: 3}
];

const data2 = [
    {date: "30-08-2019", value: 1},
    {date: "02-09-2019", value: 2},
    {date: "03-09-2019", value: 3},
    {date: "04-09-2019", value: 4}
];

// values for direct lookup
const dates1 = mapDatesToValues(data1);
const dates2 = mapDatesToValues(data2);

// Chronological order for all existing dates
const allDatesOrdered = Object.keys({ ...dates1, ...dates2 }).sort();

// Helper variables:
let acc1 = 0; // Accumulated value while iterating through data1
let acc2 = 0; // Accumulated value while iterating through data2

let existsIn1;
let existsIn2;

let value1; // Current value while iterating through data1
let value2; // Current value while iterating through data2

allDatesOrdered.forEach((date) => {
    existsIn1 = dates1.hasOwnProperty(date);
    existsIn2 = dates2.hasOwnProperty(date);

    value1 = dates1[date];
    value2 = dates2[date];

    // Remember accumulated values
    if (existsIn1) {
        acc1 = value1;
    }
    if (existsIn2) {
        acc2 = value2;
    }

    if (existsIn1 && existsIn2) {
        console.log('sum for', date, 'is', value1 + value2, '(found in both arrays)');
    } else {
        if (existsIn1) {
            console.log('sum for', date, 'is', value1 + acc2, '(only found in data1)');
        } else {
            console.log('sum for', date, 'is', value2 + acc1, '(only found in data2)');
        }
    }
});

这是一个很好的答案,可以正确地完成工作,但遗憾的是在日期排序方面需要做很多额外的工作。 - hanuruh
1
@Anamoreira 我猜这是因为 DD-MM-YYYY 字符串 - 在某些时候不可避免地需要比较两个字符串。无论优化多少,检查这样的字符串的代码都需要存在于某个地方。 - timotgl

0

找到了一种高效的方法来处理这个问题,但由于在我使用它的上下文中更适合,我将日期转换为毫秒时间戳。因此,由于这个改变,我不会把我的答案作为正确答案。

@timotgl的答案没有转换日期值,因此我将其标记为正确答案,尽管该解决方案还包含了日期格式的更改(这在我的情况下没有帮助我,但可以帮助其他人)。

我基本上正在执行一个zip函数,遍历两个数组,并将合并的结果一次性推入对象的结果数组中。

data1.forEach((item) => {
    item.date = new Date(item.date).getTime();
});

data2.forEach((item) => {
    item.date = new Date(item.date).getTime();
});

let mergedPortfolio = [], //final array of objects

        data1Idx = 0, //indexes for each array of objects
        data2Idx = 0,

        data1Last, //keeping track of last values
        data2Last,

        date1, //current date value
        date2,

        value1,//current value
        value2;    

while(data1Idx < data1.length || data2Idx < data2.length){

    //both arrays exist
    if(data1Idx < data1.length && data2Idx < data2.length){

        date1 = data1[data1Idx].date;
        date2 = data2[data2Idx].date;

        value1 = data1[data1Idx].value;
        value2 = data2[data2Idx].value;

        if(date1 < date2){

            mergedPortfolio.push({date: date1, value: value1+data2Last});

            data1Last = value1;
            ++data1Idx;

        }else if(data1[data1Idx].date === data2[data2Idx].date){
            mergedPortfolio.push({date: date1, value: value1+value2})

            data1Last = value1;
            data2Last = value2;

            ++data1Idx;
            ++data2Idx;

        }else if(data1[data1Idx].date > data2[data2Idx].date){

            mergedPortfolio.push({date: date2, value: data1Last+value2});

            data2Last = value2;
            ++data2Idx;

        }

    //Working through the remaining items in one data1 array
    }else if(data1Idx < data1.length){

        date1 = data1[data1Idx].date;

        value1 = data1[data1Idx].value;

        mergedPortfolio.push({date: date1, value: value1+data2Last});

        data1Last = value1;
        ++data1Idx;

    //Working through the remaining items in the data2 array
    }else if(data2Idx > data2.length){

        date2 = data2[data2Idx].date;

        value2 = data2[data2Idx].value;

        mergedPortfolio.push({date: date2, value: value2+data1Last});

        data2Last = value1;
        ++data2Idx;

    }

}


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接