如何高效计算过去12个月各账户的最高及最低余额?
优化方案:解决过去12个月账户余额极值计算的超时问题
原代码的性能瓶颈
- 低效的月份查找:遍历每笔交易时用
months.find()匹配年月,每次查找都是O(13)的遍历,交易量大时累计开销很高 - 重复创建Date对象:处理交易日期、排序时多次调用
new Date(),造成不必要的性能损耗 - 冗余的余额存储与计算:先把所有余额存入数组,再遍历数组计算最大最小值,多了一次完整遍历
- 排序逻辑冗余:排序回调里每次比较都创建新的Date实例,排序效率低
高效实现思路
- 用Map替代数组查找:以
YYYY-MM格式字符串为键存储月份统计数据,查找月份的时间复杂度降为O(1) - 预计算目标年月范围:提前生成过去13个月的
YYYY-MM键集合,快速判断交易是否在目标范围内 - 一次遍历完成双重任务:遍历交易时同步维护对应月份的最大、最小余额,无需额外存储所有余额数据
- 优化排序逻辑:先将交易日期转换为时间戳,排序时直接比较数字,避免重复创建Date对象
优化后的代码
exports.main = async({ accounts, transactions }) => { const currentDate = new Date(); const currentYear = currentDate.getFullYear(); const currentMonth = currentDate.getMonth(); // 预生成过去13个月的映射关系与基础数据 const monthNames = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]; const monthMap = new Map(); const targetMonths = []; for (let i = 0; i < 13; i++) { const date = new Date(currentYear, currentMonth - i, 1); const year = date.getFullYear(); const monthIdx = date.getMonth(); const key = `${year}-${String(monthIdx + 1).padStart(2, '0')}`; const monthData = { monthName: monthNames[monthIdx], yearName: year, highestUtilisation: -Infinity, lowestUtilisation: Infinity, hasData: false }; monthMap.set(key, monthData); targetMonths.push(monthData); } // 计算初始总余额 const currentBalance = accounts.reduce((total, acc) => total + acc.balance.current, 0); // 优化排序:提前转换时间戳,避免重复创建Date transactions.sort((a, b) => { return new Date(b.postedDate).getTime() - new Date(a.postedDate).getTime(); }); let runningBalance = currentBalance; // 遍历交易,同步更新余额与月份极值 for (const tr of transactions) { if (tr.amount !== 0) { runningBalance += tr.amount; } // 快速提取交易的YYYY-MM键(假设postedDate格式为"YYYY-MM-DD...") const key = tr.postedDate.slice(0, 7); const monthData = monthMap.get(key); if (!monthData) continue; monthData.hasData = true; // 更新当前月份的最大、最小余额 if (runningBalance > monthData.highestUtilisation) { monthData.highestUtilisation = runningBalance; } if (runningBalance < monthData.lowestUtilisation) { monthData.lowestUtilisation = runningBalance; } } // 处理无数据月份,格式化数值 targetMonths.forEach(month => { if (month.hasData) { month.highestUtilisation = Math.round(month.highestUtilisation * 100) / 100; month.lowestUtilisation = Math.round(month.lowestUtilisation * 100) / 100; } else { month.highestUtilisation = null; month.lowestUtilisation = null; } delete month.hasData; }); // 返回过去12个完整月份 return { months: targetMonths.slice(1) }; };
额外优化建议
- 如果交易本身已按时间倒序存储,直接跳过排序步骤,节省排序开销
- 若
postedDate格式固定,优先用字符串截取提取年月,完全避免new Date()的调用开销
内容的提问来源于stack exchange,提问作者Jord
相关产品推荐
相关产品推荐

