C# 如何按时间范围分组(闭合时间分组在一起)

C# 如何按时间范围分组(闭合时间分组在一起),c#,linq,c#-4.0,group-by,linq-to-objects,C#,Linq,C# 4.0,Group By,Linq To Objects,假设我们有以下记录: NID CId PushedAt 120 796 2015-09-04 18:00:53.6012627 +00:00 120 967 2015-09-04 18:00:51.9891748 +00:00 119 669 2015-09-04 17:45:56.8179094 +00:00 119 955 2015-09-04 17:45:55.2078154 +00:00 119 100 2015-0

假设我们有以下记录:

NID    CId    PushedAt
120    796    2015-09-04 18:00:53.6012627 +00:00
120    967    2015-09-04 18:00:51.9891748 +00:00
119    669    2015-09-04 17:45:56.8179094 +00:00
119    955    2015-09-04 17:45:55.2078154 +00:00
119    100    2015-09-04 17:45:53.5867187 +00:00
116    384    2015-09-04 17:01:01.5375630 +00:00
116    155    2015-09-04 17:00:59.9284665 +00:00
116    517    2015-09-04 17:00:58.3193725 +00:00
113    109    2015-09-04 16:00:53.5269438 +00:00
113    111    2015-09-04 16:00:51.9168442 +00:00
107    603    2015-09-04 13:45:59.9994496 +00:00
我想按时间范围(而不是特定时间)对它们进行分组。如果我按时间对它们进行分组:

var grouped = list.GroupBy(t => new {
    t.PushedAt.Year, 
    t.PushedAt.Month, 
    t.PushedAt.Day, 
    t.PushedAt.Hour, 
    t.PushedAt.Minute
});
然后我会想念那些有不同的
分钟
,但实际上是在同一组的小组。例如,这些行:

116    384    2015-09-04 17:01:01.5375630 +00:00
116    155    2015-09-04 17:00:59.9284665 +00:00
116    517    2015-09-04 17:00:58.3193725 +00:00
将转到以下组:

// group 1:
116    384    2015-09-04 17:01:01.5375630 +00:00
// group 2:
116    155    2015-09-04 17:00:59.9284665 +00:00
116    517    2015-09-04 17:00:58.3193725 +00:00
但是,我要找的是这个群体:

// group 1:
116    384    2015-09-04 17:01:01.5375630 +00:00
116    155    2015-09-04 17:00:59.9284665 +00:00
116    517    2015-09-04 17:00:58.3193725 +00:00
也就是说,这3行应该组合在一起。例如,5分钟范围内的所有行应分组在一起。完整输出如下所示:

// group 1:
120    796    2015-09-04 18:00:53.6012627 +00:00
120    967    2015-09-04 18:00:51.9891748 +00:00
// group 2:
119    669    2015-09-04 17:45:56.8179094 +00:00
119    955    2015-09-04 17:45:55.2078154 +00:00
119    100    2015-09-04 17:45:53.5867187 +00:00
// group 3:
116    384    2015-09-04 17:01:01.5375630 +00:00
116    155    2015-09-04 17:00:59.9284665 +00:00
116    517    2015-09-04 17:00:58.3193725 +00:00
// group 4:
113    109    2015-09-04 16:00:53.5269438 +00:00
113    111    2015-09-04 16:00:51.9168442 +00:00
// group 5:
107    603    2015-09-04 13:45:59.9994496 +00:00
你知道吗

注意:NID
字段不能分组

更新:

var list = new List<myClass>();
list.Add(new myClass(120, 796, new DateTime(2015, 09, 04, 18, 00, 53)));
list.Add(new myClass(120, 967, new DateTime(2015, 09, 04, 18, 03, 51)));
list.Add(new myClass(119, 669, new DateTime(2015, 09, 04, 17, 45, 56)));
list.Add(new myClass(119, 955, new DateTime(2015, 09, 04, 17, 42, 55)));
list.Add(new myClass(119, 100, new DateTime(2015, 09, 04, 17, 41, 53)));
list.Add(new myClass(116, 384, new DateTime(2015, 09, 04, 17, 01, 01)));
list.Add(new myClass(116, 155, new DateTime(2015, 09, 04, 17, 00, 59)));
list.Add(new myClass(116, 517, new DateTime(2015, 09, 04, 17, 00, 58)));
list.Add(new myClass(113, 109, new DateTime(2015, 09, 04, 16, 02, 53)));
list.Add(new myClass(113, 111, new DateTime(2015, 09, 04, 16, 00, 51)));
list.Add(new myClass(107, 603, new DateTime(2015, 09, 04, 13, 45, 59)));

var grouped = list.GroupBy(t =>
    t.PushedAt.ToString("yyyyMMddHH") +
    ((int)(t.PushedAt.Minute / 5)).ToString("00")
);

foreach (var g in grouped) {
    Console.WriteLine(g.Key);
    foreach (var itm in g) {
        Console.WriteLine(String.Format("{0}\t{1}\t{2}", itm.CId, itm.NID, itm.PushedAt));
    }
}
201509041800
796     120     9/4/2015 6:00:53 PM
967     120     9/4/2015 6:03:51 PM
201509041709
669     119     9/4/2015 5:45:56 PM
201509041708
955     119     9/4/2015 5:42:55 PM
100     119     9/4/2015 5:41:53 PM
201509041700
384     116     9/4/2015 5:01:01 PM
155     116     9/4/2015 5:00:59 PM
517     116     9/4/2015 5:00:58 PM
201509041600
109     113     9/4/2015 4:02:53 PM
111     113     9/4/2015 4:00:51 PM
201509041309
603     107     9/4/2015 1:45:59 PM
我知道我可以通过迭代项目来解决这个问题(正如juharr在评论中所说)。但是,我正在寻找一种
LINQ
解决方案,如果有的话。谢谢。

根据的评论,我找到了可以将
IEqualityComparer
传递给
GroupBy
方法的方法。所以,我这样做了:

var grouped = list.GroupBy(t => t.PushedAt, new MyComparer());
使用此比较器:

internal class MyComparer : IEqualityComparer<DateTime> {

    private static readonly TimeSpan Span = TimeSpan.FromMinutes(5);

    public bool Equals(DateTime x, DateTime y){
        return (x - y).Duration() <= Span;
    }

    public int GetHashCode(DateTime obj) {
        return obj.Year.GetHashCode() ^ obj.Month.GetHashCode() ^ obj.Day.GetHashCode();
    }

}
内部类MyComparer:IEqualityComparer{
专用静态只读TimeSpan=TimeSpan.FromMinutes(5);
公共布尔等于(日期时间x,日期时间y){

return(x-y).Duration()我认为这样做可能会有所帮助:

var list = new List<myClass>();
list.Add(new myClass(120, 796, new DateTime(2015, 09, 04, 18, 00, 53)));
list.Add(new myClass(120, 967, new DateTime(2015, 09, 04, 18, 03, 51)));
list.Add(new myClass(119, 669, new DateTime(2015, 09, 04, 17, 45, 56)));
list.Add(new myClass(119, 955, new DateTime(2015, 09, 04, 17, 42, 55)));
list.Add(new myClass(119, 100, new DateTime(2015, 09, 04, 17, 41, 53)));
list.Add(new myClass(116, 384, new DateTime(2015, 09, 04, 17, 01, 01)));
list.Add(new myClass(116, 155, new DateTime(2015, 09, 04, 17, 00, 59)));
list.Add(new myClass(116, 517, new DateTime(2015, 09, 04, 17, 00, 58)));
list.Add(new myClass(113, 109, new DateTime(2015, 09, 04, 16, 02, 53)));
list.Add(new myClass(113, 111, new DateTime(2015, 09, 04, 16, 00, 51)));
list.Add(new myClass(107, 603, new DateTime(2015, 09, 04, 13, 45, 59)));

var grouped = list.GroupBy(t =>
    t.PushedAt.ToString("yyyyMMddHH") +
    ((int)(t.PushedAt.Minute / 5)).ToString("00")
);

foreach (var g in grouped) {
    Console.WriteLine(g.Key);
    foreach (var itm in g) {
        Console.WriteLine(String.Format("{0}\t{1}\t{2}", itm.CId, itm.NID, itm.PushedAt));
    }
}
201509041800
796     120     9/4/2015 6:00:53 PM
967     120     9/4/2015 6:03:51 PM
201509041709
669     119     9/4/2015 5:45:56 PM
201509041708
955     119     9/4/2015 5:42:55 PM
100     119     9/4/2015 5:41:53 PM
201509041700
384     116     9/4/2015 5:01:01 PM
155     116     9/4/2015 5:00:59 PM
517     116     9/4/2015 5:00:58 PM
201509041600
109     113     9/4/2015 4:02:53 PM
111     113     9/4/2015 4:00:51 PM
201509041309
603     107     9/4/2015 1:45:59 PM

使用
.Ticks
属性可以很容易地做到这一点

如果您从问题的输入开始:

var records = new[]
{
    new { NID = 120, PID = 796, PushedAt = DateTime.Parse("2015-09-04 18:00:53.6012627") },
    new { NID = 120, PID = 967, PushedAt = DateTime.Parse("2015-09-04 18:00:51.9891748") },
    new { NID = 119, PID = 669, PushedAt = DateTime.Parse("2015-09-04 17:45:56.8179094") },
    new { NID = 119, PID = 955, PushedAt = DateTime.Parse("2015-09-04 17:45:55.2078154") },
    new { NID = 119, PID = 100, PushedAt = DateTime.Parse("2015-09-04 17:45:53.5867187") },
    new { NID = 116, PID = 384, PushedAt = DateTime.Parse("2015-09-04 17:01:01.5375630") },
    new { NID = 116, PID = 155, PushedAt = DateTime.Parse("2015-09-04 17:00:59.9284665") },
    new { NID = 116, PID = 517, PushedAt = DateTime.Parse("2015-09-04 17:00:58.3193725") },
    new { NID = 113, PID = 109, PushedAt = DateTime.Parse("2015-09-04 16:00:53.5269438") },
    new { NID = 113, PID = 111, PushedAt = DateTime.Parse("2015-09-04 16:00:51.9168442") },
    new { NID = 107, PID = 603, PushedAt = DateTime.Parse("2015-09-04 13:45:59.9994496") },
};
下面是如何进行分组:

var results =
    records
        .GroupBy(x => x.PushedAt.Ticks / TimeSpan.TicksPerMinute / 5);
我得到以下结果:


我不明白13:45.59/../96如何符合最后一组的条件?这是故意的吗?似乎您首先要在日期时间之前下单,然后循环并将第一个范围设置为从第一个条目的时间开始到其时间+5分钟。然后继续拖动,直到一个条目超出范围,并将下一个范围定义为从该e开始ntry的时间到了,重复一遍。@DanielHoffmann Mitscherling不,这是我的错误。我更新了问题。谢谢你的观点;)@Javad_Amiry我认为没有LINQ解决方案。
GroupBy
需要一个可以为每个条目定义的密钥,但是你的“密钥”根据条目集进行更改。LINQ并不总是解决方案。不幸的是,您的规范不明确。例如,您是否尝试将记录划分为五分钟的间隔?或者您是否希望与另一条记录间隔五分钟内的任何记录与另一条记录在同一组中?如果是后者,您是否关心以下问题:至少可以(取决于数据)导致所有记录在同一组中结束,即使是相隔几天?您可能可以让LINQ对记录进行分组,但首先您必须定义一个精确的规范,允许您根据记录应在的组为每个记录计算一个唯一的确定键。不,这是不正确的。IEqualityComparer的规则是t如果两个项相等,则它们具有相同的哈希代码。您无法在此处确保这一点。在某些情况下,这并不意味着它实际上是一个有用的解决方案。您只是在为自己设置一个很难找到的错误,当您的数据稍有不同时。正如我在对问题的评论中提到的,您需要返回to在绘图板上,找出一个实际的规范来描述您想要的行为。目前,您没有足够具体的问题陈述。@PeterDuniho Yep:(是的。从所有GetHashCode返回0(或任何其他常量)怎么样?
public int GetHashCode(DateTime obj){return 0;}
你可以,但是你会有糟糕的哈希表性能。而且,这只会修复哈希代码方面的问题。另一个相等规则是,如果A==B和B==C,那么A==C。你的比较器在这里也不能保证这一点。底线是,除非你改进了问题,提供了精确的规范,否则你不会去做注意,你有两种不同的建议以一种特定的方式解释你的问题(即划分为5分钟的间隔),但不清楚你想要的是哪种方式。