如何使用Linq根据前瞻条件计算设备错误日志的总错误时长
Linq实现设备错误总时长统计方案
问题背景
某数据库存储设备错误日志,每条日志包含时间戳与错误码,规则如下:
- 错误码非0代表错误激活,为0代表错误未激活
- 同一时间设备最多一个活跃错误,错误时长从首次非0错误上报时间开始,到首次0错误码上报时间结束
- 上报顺序不固定,可能存在连续非0、连续0的情况,需正确统计多段错误时长的总和
示例:
| timeStamp | code |
|---|---|
| 100 | 101 |
| 200 | 0 |
| 300 | 500 |
| 350 | 501 |
| 400 | 0 |
总错误时长为 (200-100)+(400-300)=200
原有实现问题
你尝试的Zip+SkipWhile方案存在逻辑缺陷:SkipWhile是全局跳过序列开头的非0码,无法分段识别每个错误周期的起止,因此不能适配多段错误的场景。
推荐实现方案
这类带状态遍历的场景,使用Linq的Aggregate累加器是最优选择,它可以在遍历过程中携带自定义状态,完全对齐原有循环的逻辑:
// 遍历日志,累计错误总时长 long totalErrorTime = errors.Aggregate( // 初始化状态:未处于错误中,起始时间为0,总时长为0 new { IsInError = false, StartTime = 0L, Total = 0L }, (state, currLog) => { // 场景1:当前未处于错误,遇到非0码,开启新错误周期 if (!state.IsInError && currLog.code != 0) { return new { IsInError = true, StartTime = currLog.timeStamp, Total = state.Total }; } // 场景2:当前处于错误中,遇到0码,结算当前周期时长 if (state.IsInError && currLog.code == 0) { return new { IsInError = false, StartTime = 0L, Total = state.Total + (currLog.timeStamp - state.StartTime) }; } // 其他场景:状态不变直接返回 return state; }).Total; // 包装为你需要的返回结果 var result = new ErrorTimeResult(totalErrorTime);
方案优势
- 时间复杂度O(n),仅遍历一次日志序列,性能与原生循环一致
- 自动兼容所有边界场景:连续非0码仅记录第一个的时间,连续0码仅结算一次时长,未结束的错误周期(日志结尾无0收尾)默认不统计(如果需要统计到当前时间,遍历结束后判断
IsInError状态补算时长即可)
可选Zip实现方案
如果坚持用Zip思路,可先筛选状态切换点再配对计算,适配.NET 6及以上版本:
long totalErrorTime = errors // 补前导0码节点,兼容第一条日志就是非0的场景 .Prepend(new { timeStamp = 0L, code = 0 }) // 相邻两条日志配对,识别状态切换点 .Zip(errors, (prev, curr) => new { prev.code, curr.code, curr.timeStamp }) // 仅保留错误开始(0切非0)、错误结束(非0切0)的节点 .Where(x => (x.prev.code == 0 && x.curr.code != 0) || (x.prev.code != 0 && x.curr.code == 0)) // 每两个节点为一组,对应一个错误周期的起止 .Chunk(2) // 过滤掉未结束的错误周期(只有开始没有结束) .Where(c => c.Length == 2) // 累加每个周期的时长 .Sum(c => c[1].timeStamp - c[0].timeStamp);
内容的提问来源于stack exchange,提问作者mneumann
相关产品推荐
相关产品推荐

