从JSON对象数组提取符合特定条件周期的JavaScript算法需求
嘿,针对你要开发的这个从JSON数组里提取特定周期的算法,我整理了一套实用的实现思路和代码参考,咱们来一步步拆解:
先明确需求细节
先再把需求捋清楚,避免理解偏差:
待处理数据:JSON对象数组,每个对象包含
year(年份)、month(月份)、colval(仅可取0/1/2/3),时间范围覆盖1960年1月至2016年12月
有效周期判定规则:
- 周期内最多仅存在1个
colval=0的条目(出现2个及以上0则直接无效)- 周期持续时长至少6个月
- 周期内其余所有条目的
colval必须大于0
核心实现思路
因为数据是按时间序列排列的(如果不是,第一步必须先排序),最适合用**滑动窗口(双指针)**的方式来处理:通过左右两个指针维护一个动态的窗口,跟踪窗口内0的数量和窗口的时间跨度,一旦窗口符合有效周期的条件,就记录下来。这种方法的时间复杂度是O(n),处理1960-2016共684条数据非常高效。
具体实现步骤
确保数据按时间排序
不管输入数据是否有序,都先按年份升序、同一年按月份升序排列,这是滑动窗口逻辑成立的前提:const sortedData = [...data].sort((a, b) => { if (a.year !== b.year) return a.year - b.year; return a.month - b.month; });初始化滑动窗口变量
- 左指针
left:标记窗口的起始位置 zeroCount:统计当前窗口内colval=0的条目数量validPeriods:保存所有符合条件的有效周期
- 左指针
遍历数组维护窗口
用右指针right遍历每个元素,动态调整左指针的位置,确保窗口始终满足“0的数量≤1”的条件,同时检查窗口时长是否达标:- 遇到
colval=0的元素,zeroCount加1 - 当
zeroCount>1时,不断移动左指针,直到窗口内0的数量回到≤1(移动时如果左指针指向的元素是0,要把zeroCount减1) - 计算窗口的时间跨度:把年月转换成总月数(比如1960年1月=1960*12+1),用结束总月数减去起始总月数再加1,得到实际持续的月数
- 如果时长≥6且
zeroCount≤1,就把这个窗口作为有效周期保存
- 遇到
完整代码示例(JavaScript)
function findValidPeriods(data) { // 前置:确保数据按时间排序 const sortedData = [...data].sort((a, b) => { if (a.year !== b.year) return a.year - b.year; return a.month - b.month; }); const validPeriods = []; let left = 0; let zeroCount = 0; for (let right = 0; right < sortedData.length; right++) { const currentItem = sortedData[right]; // 更新当前窗口内的0数量 if (currentItem.colval === 0) { zeroCount++; } // 窗口内0数量超标时,收缩左边界 while (zeroCount > 1) { const leftItem = sortedData[left]; if (leftItem.colval === 0) { zeroCount--; } left++; } // 计算窗口的持续月数 const startTotalMonths = sortedData[left].year * 12 + sortedData[left].month; const endTotalMonths = currentItem.year * 12 + currentItem.month; const durationMonths = endTotalMonths - startTotalMonths + 1; // 符合条件则记录周期 if (durationMonths >= 6 && zeroCount <= 1) { validPeriods.push({ start: { year: sortedData[left].year, month: sortedData[left].month }, end: { year: currentItem.year, month: currentItem.month }, duration: durationMonths, entries: sortedData.slice(left, right + 1) }); } } // 可选:合并重叠/连续的有效周期(比如保留最长的不重叠周期) if (validPeriods.length === 0) return validPeriods; const mergedPeriods = [validPeriods[0]]; for (let i = 1; i < validPeriods.length; i++) { const last = mergedPeriods[mergedPeriods.length - 1]; const current = validPeriods[i]; // 判断是否需要合并:当前周期起始与上一个周期结束连续或重叠 const lastEndTotal = last.end.year * 12 + last.end.month; const currentStartTotal = current.start.year * 12 + current.start.month; if (currentStartTotal <= lastEndTotal + 1) { const newEndTotal = Math.max(lastEndTotal, current.end.year * 12 + current.end.month); mergedPeriods[mergedPeriods.length - 1] = { start: last.start, end: { year: Math.floor(newEndTotal / 12), month: newEndTotal % 12 || 12 }, duration: newEndTotal - (last.start.year * 12 + last.start.month) + 1, entries: sortedData.slice( sortedData.findIndex(item => item.year === last.start.year && item.month === last.start.month), sortedData.findIndex(item => item.year === Math.floor(newEndTotal / 12) && item.month === (newEndTotal % 12 || 12)) + 1 ) }; } else { mergedPeriods.push(current); } } return mergedPeriods; }
注意事项
- 时间排序的必要性:如果输入数据是乱序的,滑动窗口逻辑完全失效,所以排序步骤不能省略
- 时长计算的准确性:用总月数计算能完美处理跨年的情况(比如1960年11月到1961年4月,总月数差是5,加1后得到正确的6个月)
- 周期合并逻辑:上面的代码包含了可选的合并逻辑,如果你需要保留所有符合条件的子周期,可以去掉合并部分直接返回
validPeriods - 边界情况测试:记得测试刚好有1个0且时长6个月、整个时间范围都是有效周期、只有5个月符合条件等极端场景
内容的提问来源于stack exchange,提问作者Alper Dincer
相关产品推荐
相关产品推荐

