如何分组时间范围(封闭时间组合在一起)

时间:2015-09-04 18:39:12

标签: 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-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
});

然后我会错过具有不同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字段不可分组。

更新

我知道我可以通过迭代项来解决问题(如 juharr 在评论中所述)。但是,我正在寻找一个LINQ解决方案,如果有的话。感谢。

3 个答案:

答案 0 :(得分:1)

我认为这样的事情可能有所帮助:

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

答案 1 :(得分:1)

使用.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);

我得到了这些结果:

results

答案 2 :(得分:-1)

根据pasty的评论,我弄清楚我可以将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();
    }

}

这给了我正是我正在寻找的东西。