基于指定起始小时的LINQ按时间段分组Exam问题求助
按指定起始小时的时间段分组Exam为Visit的LINQ实现
需求说明
需要编写LINQ语句,将Exam按**以一天中指定小时为起始点的时间段(1天或2天)**分组为Visit,要求从单元测试场景出发实现逻辑。
基础测试数据
1st exam with recording date 2024-03-21T11:20:00, Id = 3; 2nd exam with recording date 2024-03-21T11:50:00, Id = 4; 3rd exam with recording date 2024-03-21T12:20:00, Id = 5; 4th exam with recording date 2024-03-21T12:50:00, Id = 6; 5th exam with recording date 2024-03-22T11:20:00, Id = 7; 6th exam with recording date 2024-03-22T11:50:00, Id = 8; 7th exam with recording date 2024-03-22T12:20:00, Id = 9; 8th exam with recording date 2024-03-23T12:50:00, Id = 10; 9th exam with recording date 2024-03-23T11:20:00, Id = 11; 10th exam with recording date 2024-03-23T11:50:00, Id = 12; 11th exam with recording date 2024-03-23T12:20:00, Id = 13; 12th exam with recording date 2024-03-23T12:50:00, Id = 24;
期望分组结果
按中午12点为起始的1天时间段分组
1st visit with Id = 1 and exams with Id = [3, 4] 2nd visit with Id = 5 and exams with Id = [5, 6, 7, 8] 3rd visit with Id = 9 and exams with Id = [9, 10, 11, 12] 4th visit with Id = 13 and exams with Id = [13, 24]
按中午12点为起始的2天时间段分组
1st visit with Id = 1 and exams with Id = [3, 4] 2nd visit with Id = 5 and exams with Id = [5, 6, 7, 8, 9, 10, 11, 12] 3rd visit with Id = 13 and exams with Id = [13, 24]
单元测试代码
[TestMethod] public void Convert_TwoExamSplitAtNoon_TwoVisits() { PatientWithExams p = new() { PatientId = 1 }; p.Exams.Add(new LightExam() { ExamId = 1, ExamType = Micromed.Domain.Archive.Types.ExamFileTypeId.EegTrace, RecordingDate = new DateTime(2024, 3, 21, 11, 20, 00) }); p.Exams.Add(new LightExam() { ExamId = 1, ExamType = Micromed.Domain.Archive.Types.ExamFileTypeId.EegTrace, RecordingDate = new DateTime(2024, 3, 21, 12, 20, 00) }); List<PatientWithExams> patients = new() { p }; TimeSpan splitTime = new(12, 00, 00); Dictionary<int, List<LightVisit>> visits = _converter.ConvertNew(patients, splitTime); // Assert Assert.AreEqual(true, visits[1].Count() == 2); }
现有代码(未实现指定小时分割逻辑)
TimeSpan groupInterval = TimeSpan.FromDays(1); var eegExams = from e in patient.Exams.Where(e => e.ExamType == Domain.Archive.Types.ExamFileTypeId.EegTrace) group e by e.RecordingDate.Ticks / groupInterval.Ticks into g select new { day = new DateTime(g.Key * groupInterval.Ticks), Values = g.Count(), Exams = g.DefaultIfEmpty() };
解决方案
核心逻辑是先将每个RecordingDate调整为以指定起始小时为基准的分组锚点,再按锚点分组,同时支持自定义分组间隔。
完善后的LINQ代码
// 可作为参数传入的配置项 TimeSpan splitTime = new TimeSpan(12, 0, 0); // 指定起始小时(示例:中午12点) TimeSpan groupInterval = TimeSpan.FromDays(1); // 分组时长(示例:1天,可改为FromDays(2)) var eegExams = patient.Exams .Where(e => e.ExamType == Domain.Archive.Types.ExamFileTypeId.EegTrace) .Select(e => new { Exam = e, // 计算当前Exam所属分组的锚点时间 GroupAnchor = GetGroupAnchor(e.RecordingDate, splitTime, groupInterval) }) .GroupBy(x => x.GroupAnchor.Ticks) .Select(g => new { VisitId = g.First().Exam.ExamId, // 按业务需求生成VisitId,示例取分组首个Exam的Id Exams = g.Select(x => x.Exam).ToList(), GroupStartTime = new DateTime(g.Key) });
辅助方法:计算分组锚点
private DateTime GetGroupAnchor(DateTime date, TimeSpan splitTime, TimeSpan groupInterval) { // 计算当天的起始分割点 DateTime dailySplitPoint = date.Date.Add(splitTime); // 若当前时间早于分割点,归到前一个分组区间 DateTime intervalStart = date < dailySplitPoint ? dailySplitPoint.Subtract(groupInterval) : dailySplitPoint; // 计算该时间所属分组的起始锚点 long intervalTicks = groupInterval.Ticks; long groupKeyTicks = (intervalStart.Ticks / intervalTicks) * intervalTicks; return new DateTime(groupKeyTicks); }
代码说明
GetGroupAnchor方法处理跨天场景:比如12点前的时间会被归到前一个以12点为起始的分组区间- LINQ中先映射每个Exam对应的分组锚点,再按锚点的Ticks值分组,确保同一区间的Exam被分到一组
- 配置项
splitTime和groupInterval可灵活调整,满足不同业务场景的分组需求
内容的提问来源于stack exchange,提问作者Riccardo Zamuner
相关产品推荐
相关产品推荐

