【问题标题】:Filter out events order by time based on their value根据事件的值按时间顺序过滤掉事件
【发布时间】:2021-12-11 22:04:21
【问题描述】:

问题已被编辑。参见 cmets。更改以斜体显示。

我有以下由 ID 标识的多个物理设备生成的有序(开始/停止)事件列表。为简单起见,仅显示一台设备的数据。

Date       Hour     ID Event           IsStart
18/10/2021 10:35:22 1  DeviceConnected  True
18/10/2021 10:20:10 1  DeviceConnected  True
18/10/2021 10:12:20 1  DeviceConnected  False
18/10/2021 10:12:19 1  DeviceConnected  False
18/10/2021 08:24:14 1  DeviceConnected  True

在给定的时间段内,通常是 24 小时,我不能有两次或多次连续的开始或停止。我需要删除“重复项”。

在上面的示例中,这意味着在使用 linq 应用过滤器之后:

Date       Hour     ID Event           IsStart
18/10/2021 10:20:10 1  DeviceConnected  True
18/10/2021 10:12:19 1  DeviceConnected  False
18/10/2021 08:24:14 1  DeviceConnected  True

开始后应该有一个停止或什么都没有,反之亦然。

这是一个典型的差距和岛屿问题,对吧?让我们将岛号添加到初始数据集中。除非我错了,否则我们应该以这样的方式结束:

Date       Hour     ID Event           IsStart Island
18/10/2021 10:35:22 1  DeviceConnected  True    3
18/10/2021 10:20:10 1  DeviceConnected  True    3
18/10/2021 10:12:20 1  DeviceConnected  False   2
18/10/2021 10:12:19 1  DeviceConnected  False   2
18/10/2021 08:24:14 1  DeviceConnected  True    1

我可以用 Linq 做到这一点吗?如果是这样,我应该只能保留岛上的第一条记录。

【问题讨论】:

    标签: c# linq gaps-and-islands


    【解决方案1】:

    我认为您无法使用开箱即用的 LINQ 函数来完成此操作,但通过编写一个快速的“滞后”扩展方法,您可以使用单个 LINQ 语句来完成它:

    public static class EnumerableExtensions 
    {
        public static IEnumerable<TResult> Lag<TSource, TResult>(
            this IEnumerable<TSource> source,
            TSource defaultLagValue,
            Func<TSource, TSource, TResult> resultSelector
        )
        {
            TSource lagValue = defaultLagValue;
            foreach (var item in source)
            {
                yield return resultSelector(item, lagValue);
                lagValue = item;
            }
        }
    }
    

    然后,出于此答案的目的,我正在创建一个类来保存数据(您应该已经拥有):

    public class Event
    {
        public DateTime Date { get; set; }
        public string Name { get; set; }
        public bool IsStart { get; set; }
    }
    

    那么,你将如何运行它:

    var data = new List<Event>();
    data.Add(new Event { Date = new DateTime(2021, 10, 18, 10, 35, 22), Name = "DeviceConnected", IsStart = true  });
    data.Add(new Event { Date = new DateTime(2021, 10, 18, 10, 20, 10), Name = "DeviceConnected", IsStart = true  });
    data.Add(new Event { Date = new DateTime(2021, 10, 18, 10, 12, 20), Name = "DeviceConnected", IsStart = false });
    data.Add(new Event { Date = new DateTime(2021, 10, 18, 10, 12, 19), Name = "DeviceConnected", IsStart = false });
    data.Add(new Event { Date = new DateTime(2021, 10, 18,  8, 24, 14), Name = "DeviceConnected", IsStart = true  });
    
    List<Event> filteredData = data
        .OrderBy(e => e.Date)
        .Lag(null, (e, lag) => new {
            Event = e,
            PreviousItem = lag,
        })
        .Where(x => x.PreviousItem == null || x.Event.IsStart != x.PreviousItem.IsStart)
        .Select(x => x.Event)
        .OrderByDescending(e => e.Date)
        .ToList();
    

    之后,filteredData 应该包含您的预期输出,类似于:

    Date       Hour     Event           IsStart
    18/10/2021 10:20:10 DeviceConnected True
    18/10/2021 10:12:19 DeviceConnected False
    18/10/2021 08:24:14 DeviceConnected True
    

    【讨论】:

    • 您能详细说明一下使用 2 OrderBy 吗?
    • 这行得通。我忘了说我有多个设备生成事件(问题已被编辑)。在这种情况下,我看不出正确的 Where 子句应该是什么。
    • 我通过按 ID 对事件进行分组解决了我的其他问题
    • 第一个顺序是按正确的顺序获取项目,以便Lag 函数将根据日期将前一个项目显示为前一个项目。第二个 order by 只是因为您的示例输出是按日期降序排序的,所以如果您不需要它们这样的顺序,实际上可能没有必要。如果您能够使用GroupBy 使其正常工作,那很好,但您也可以根据ID 然后日期对其进行排序,然后在Where 子句中您也可以检查前一个项目的ID。
    猜你喜欢
    • 1970-01-01
    • 2018-11-05
    • 2011-01-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-07
    相关资源
    最近更新 更多