JS性能优化:替换嵌套forEach循环实现血压数据高效合并
血压配对合并函数性能优化
需求说明
入参vitalsArray为未排序的生命体征对象数组,数组中收缩压(systolic)、舒张压(diastolic)条目可能存在配对关系,也可能无对应匹配项。满足匹配规则时,属于同一次血压(BP)测量的收缩压与舒张压对象需合并为一条完整的血压记录。
当前实现采用嵌套forEach循环完成匹配逻辑,需要性能更优的替代方案。
原有实现代码
const mergeBloodPressures = (vitalsArray) => { let ids = [] let finalArray = [] vitalsArray.forEach((tmpBP) => { vitalsArray.forEach((bp) => { let matchDate = moment(bp.takenOn).isSame( moment(tmpBP.takenOn), "minute" ) if ( matchDate && bp.vitalType.name != tmpBP.vitalType.name && !ids.includes(bp.id) && !ids.includes(tmpBP.id) ) { let temp = {} if (bp.vitalType.name == "diastolic blood pressure") { temp.diastolic = bp temp.systolic = tmpBP } else { temp.diastolic = tmpBP temp.systolic = bp } ids.push(bp.id) finalArray.push(temp) } }) }) return finalArray }
示例输入
[ { "patient_id": 716, "vital_type_id": 2, "value": "78", "taken_on": "2022-06-22T14:49:48.948-05:00", "vitalType": { "id": 2, "name": "diastolic_blood_pressure", "units": "mmHg", "created_at": "2022-06-22T14:40:43.746-05:00", "updated_at": "2022-06-22T14:40:43.746-05:00" }, "id": 9101 }, { "patient_id": 716, "vital_type_id": 1, "value": "129", "taken_on": "2022-06-22T14:49:48.948-05:00", "vital_type": { "id": 1, "name": "systolic_blood_pressure", "units": "mmHg", "created_at": "2022-06-22T14:40:43.740-05:00", "updated_at": "2022-06-22T14:40:43.740-05:00" }, "id": 9102 } ]
示例输出
[ { "diastolic": { "patient_id": 716, "vital_type_id": 2, "value": "78", "taken_on": "2022-06-22T14:49:48.948-05:00", "vitalType": { "id": 2, "name": "diastolic_blood_pressure", "units": "mmHg", "created_at": "2022-06-22T14:40:43.746-05:00", "updated_at": "2022-06-22T14:40:43.746-05:00" }, "id": 9101 }, "systolic": { "patient_id": 716, "vital_type_id": 1, "value": "129", "taken_on": "2022-06-22T14:49:48.948-05:00", "vitalType": { "id": 1, "name": "systolic_blood_pressure", "units": "mmHg", "created_at": "2022-06-22T14:40:43.740-05:00", "updated_at": "2022-06-22T14:40:43.740-05:00" }, "id": 9102 } } ]
原有实现的问题
- 嵌套循环导致时间复杂度为O(n²),数据量上涨时性能会快速下降
- 用数组
includes判断ID是否已匹配,查找复杂度为O(n),进一步拉高整体耗时 - 每次循环都重复解析两个时间值做对比,存在大量冗余计算
- 没有加患者ID作为匹配维度,可能出现不同患者同时间的血压记录错配
- 没有兼容字段名、类型名格式不一致的边界情况,实际运行容易出错
优化方案
用哈希表做分组,将匹配维度(对齐到分钟的时间戳+患者ID)作为key,单次遍历完成分组,再遍历分组输出配对结果,整体时间复杂度降到O(n)。
优化后代码
const mergeBloodPressures = (vitalsArray) => { const matchedIds = new Set() const minuteGroupMap = new Map() const finalArray = [] // 第一次遍历:按匹配维度分组,预处理时间避免重复解析 for (const bp of vitalsArray) { // 兼容takenOn/taken_on两种字段命名 const takenTime = moment(bp.takenOn ?? bp.taken_on) // 时间对齐到分钟,拼接患者ID作为唯一分组key,避免跨患者错配 const groupKey = `${bp.patient_id}_${takenTime.startOf('minute').valueOf()}` if (!minuteGroupMap.has(groupKey)) { minuteGroupMap.set(groupKey, { systolic: null, diastolic: null }) } const group = minuteGroupMap.get(groupKey) // 兼容vitalType/vital_type两种字段,兼容下划线/空格分隔的类型名 const typeName = (bp.vitalType?.name ?? bp.vital_type?.name || '').replaceAll(' ', '_') if (typeName === 'systolic_blood_pressure') { group.systolic = bp } else if (typeName === 'diastolic_blood_pressure') { group.diastolic = bp } } // 第二次遍历分组,输出配对完成的记录 for (const group of minuteGroupMap.values()) { if (group.systolic && group.diastolic) { finalArray.push({ systolic: group.systolic, diastolic: group.diastolic }) matchedIds.add(group.systolic.id) matchedIds.add(group.diastolic.id) } // 若需要保留未配对的单条血压记录,可在此处补充逻辑将单条记录加入结果 } return finalArray }
优化点说明
- 去掉嵌套循环,整体时间复杂度从O(n²)降至O(n),千条以上数据量下性能提升非常明显
- 用
Set存储已匹配ID,ID查找操作从O(n)降至O(1) - 每条记录的时间只解析一次,消除重复的moment解析开销
- 补充患者ID作为匹配维度,修复跨患者错配的逻辑bug
- 兼容接口返回字段名不一致、类型名格式不一致的边界情况,鲁棒性更强
如果存在同一患者同一分钟内有多组血压测量的场景,只需把分组内的
systolic/diastolic字段改为数组结构,按记录入库顺序依次配对即可,不需要调整整体实现逻辑。
内容的提问来源于stack exchange,提问作者user19549927
相关产品推荐
相关产品推荐

