时间序列LINQ查询

时间:2018-07-02 16:11:52

标签: c# linq

我有一个用于IoT设备日志的中央存储库。因此,当日志到达时,它们具有时间戳。我要解决的问题是,在给定的时间范围内,同一设备可能会发送有关其与特定催化剂相互作用的多个日志。我想将该日志集视为单个事件,而不是5个不同的日志。我想计算互动次数。而不是日志数。

数据集

public class Data
{
    public Guid DeviceId {get; set;}
    public DateTime StartTime { get; set; }
    public DateTime EndDateTime { get; set; }
    public int Id { get; set; }
    public int Direction { get; set;}
}

Data d1 = new Data();// imagine it's populated
Data d2 = new Data();// imagine it's populated

我正在寻找一个LINQ查询,该查询会产生类似以下内容的

If ((d1.DeviceId == d2.DeviceId )  && (d1.Id == d2.Id) && (d1.Direction == d2.Direction) && (d1.StartTime - d2.StartTime < 15 minutes ))  

如果我知道同一IoT设备正在与同一ID(催化剂)交互并且方向相同,并且所有这些日志都在15分钟的时间范围内发生,则可以假定它们对应于同一日志催化剂事件。

我不控制日志的创建,所以...不,我无法更新数据以包含表明关系的“某物”。

每个请求的数据……没什么花哨的。我相信大多数人都会怀疑我拥有30多个属性,并且只提供受计算影响的属性,但这是一组简单的可能性

class SampleData
{
    public List<Data> GetSampleData()
    {
        Guid device1 = Guid.NewGuid();

        List<Data> dataList = new List<Data>();

        Data  data1 = new Data();
        data1.DeviceId = device1;
        data1.Id = 555;
        data1.Direction = 1; 
        data1.StartTime = new DateTime(2010, 8, 18, 16, 32, 0);
        data1.EndDateTime = new DateTime(2010, 8, 18, 16, 32, 30);
        dataList.Add(data1);

        //so this data point should be excluded in the final result
        Data data2 = new Data();
        data1.DeviceId = device1;
        data1.Id = 555;
        data1.Direction = 1;
        data1.StartTime = new DateTime(2010, 8, 18, 16, 32, 32);
        data1.EndDateTime = new DateTime(2010, 8, 18, 16, 33, 30);
        dataList.Add(data2);

        //Should be included because ID is different
        Data data3 = new Data();
        data1.DeviceId = device1;
        data1.Id = 600;
        data1.Direction = 1;
        data1.StartTime = new DateTime(2010, 8, 18, 16, 32, 2);
        data1.EndDateTime = new DateTime(2010, 8, 18, 16, 32, 35);
        dataList.Add(data3);

        //exclude due to time
        Data data4 = new Data();
        data1.DeviceId = device1;
        data1.Id = 600;
        data1.Direction = 1;
        data1.StartTime = new DateTime(2010, 8, 18, 16, 32, 37);
        data1.EndDateTime = new DateTime(2010, 8, 18, 16, 33, 40);
        dataList.Add(data4);

        //include because time > 15 minutes 
        Data data5 = new Data();
        data1.DeviceId = device1;
        data1.Id = 600;
        data1.Direction = 1;
        data1.StartTime = new DateTime(2010, 8, 18, 16, 58, 42);
        data1.EndDateTime = new DateTime(2010, 8, 18, 16, 58, 50);
        dataList.Add(data5);

        return dataList;
    } 

1 个答案:

答案 0 :(得分:2)

事实证明,这比我希望的要复杂。

我使用了一种自定义的LINQ扩展方法,称为ScanPair,它是我的Scan方法的一种变体,它是APL扫描运算符的一种版本(类似于Aggregate,但返回中间结果)。 ScanPair返回操作的中间结果以及每个原始值。我想我需要考虑如何使所有这些都更通用,因为该模式被我用于按各种条件(例如顺序,运行,测试为真或假)分组的其他一系列扩展方法使用。

public static class IEnumerableExt {
    public static IEnumerable<(TKey Key, T Value)> ScanPair<T, TKey>(this IEnumerable<T> src, Func<T, TKey> seedFn, Func<(TKey Key, T Value), T, TKey> combineFn) {
        using (var srce = src.GetEnumerator()) {
            if (srce.MoveNext()) {
                var seed = (seedFn(srce.Current), srce.Current);

                while (srce.MoveNext()) {
                    yield return seed;
                    seed = (combineFn(seed, srce.Current), srce.Current);
                }
                yield return seed;
            }
        }
    }
}

现在,您可以使用元组作为中间结果来跟踪初始时间戳和组号,并在间隔超过15分钟时递增到下一个(时间戳,组号)。如果您先按互动进行分组,然后计算每次互动少于15分钟的分组,则会得到答案:

var ans = interactionLogs.GroupBy(il => new { il.DeviceId, il.Id, il.Direction })
            .Select(ilg => new {
                ilg.Key,
                Count = ilg.OrderBy(il => il.Timestamp)
                           .ScanPair(il => (firstTimestamp: il.Timestamp, groupNum: 1), (kvp, cur) => (cur.Timestamp - kvp.Key.firstTimestamp).TotalMinutes <= 15 ? kvp.Key : (cur.Timestamp, kvp.Key.groupNum + 1))
                           .GroupBy(ilkvp => ilkvp.Key.groupNum, ilkvp => ilkvp.Value)
                           .Count()
            });

这是ScanPair中间结果样本的一部分-实际结果是带有两个字段的ValueTuple,其中Key是中间结果(即{ ValueTuplefirstTimestamp中的{1}}和groupNum是相应的源(日志)项。使用函数种子版本会将第一个源项目放入种子函数以开始该过程。

Value