如何用Linq处理设备启停事件连续重复项(Gaps-and-Islands问题)
实现方案
你描述的确实是典型的Gaps-and-Islands场景,完全可以用LINQ实现。
首先假设你的事件实体类定义如下:
public class DeviceEvent { public DateTime Date { get; set; } public int ID { get; set; } public string Event { get; set; } public bool IsStart { get; set; } }
1. 给事件添加岛屿编号(和你预期的编号规则完全匹配)
核心逻辑是先按时间升序遍历,判断当前事件和上一个事件的IsStart值是否变化,变化则岛屿编号+1,最后再按时间降序返回即可:
// 原始事件列表 List<DeviceEvent> originalEvents = 获取你的原始数据(); // 生成带岛屿编号的结果 var eventsWithIsland = originalEvents // 先按时间升序排序(从早到晚遍历,保证编号从最早的事件开始递增) .OrderBy(e => e.Date) // 生成岛屿编号 .Select(((e, index) => { // 闭包存储上一个事件的状态和当前编号 if (index == 0) { lastIsStart = e.IsStart; currentIslandId = 1; } else if (e.IsStart != lastIsStart) { currentIslandId++; lastIsStart = e.IsStart; } return new { e.Date, e.ID, e.Event, e.IsStart, Island = currentIslandId }; })) // 按时间降序排列,和你示例的输出顺序一致 .OrderByDescending(e => e.Date) .ToList(); // 闭包变量定义 bool lastIsStart; int currentIslandId;
2. 去重得到最终结果
按岛屿编号分组,取每个分组里时间最早的记录即可,和你给出的预期结果完全匹配:
var deduplicatedEvents = eventsWithIsland .GroupBy(e => e.Island) // 取每个岛屿时间最早的记录 .Select(g => g.OrderBy(e => e.Date).First()) // 按时间降序排列输出 .OrderByDescending(e => e.Date) .ToList();
3. 多设备兼容方案
如果是多设备混合的数据集,先按设备ID分组,每个设备单独处理即可:
var multiDeviceResult = originalEvents // 按设备ID分组,不同设备互不影响 .GroupBy(e => e.ID) .SelectMany(g => { bool deviceLastIsStart = default; int deviceCurrentIslandId = 1; return g .OrderBy(e => e.Date) .Select((e, index) => { if (index == 0) { deviceLastIsStart = e.IsStart; deviceCurrentIslandId = 1; } else if (e.IsStart != deviceLastIsStart) { deviceCurrentIslandId++; deviceLastIsStart = e.IsStart; } return new { e.Date, e.ID, e.Event, e.IsStart, Island = deviceCurrentIslandId }; }); }) .OrderByDescending(e => e.Date) .ToList();
注意:以上方案为内存侧LINQ to Objects实现,如果要对接数据库操作,需要先将对应时间范围内的设备数据加载到内存再执行上述逻辑。
内容的提问来源于stack exchange,提问作者anon
相关产品推荐
相关产品推荐

