我有一个用于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;
}
答案 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
是中间结果(即{ ValueTuple
,firstTimestamp
中的{1}}和groupNum
是相应的源(日志)项。使用函数种子版本会将第一个源项目放入种子函数以开始该过程。
Value