简体   繁体   English

linq Group By 并取 first 和 lats 记录

[英]linq Group By and take first and lats records

I am dealing with probably simple yet difficult problem for me.我正在处理对我来说可能简单但困难的问题。 I am taking photos of parked cars and save them by car's plate and timestamp it.我正在为停放的汽车拍照,并用车牌保存它们并加盖时间戳。 Same car could be photographed several times during the day.同一辆车可以在白天被拍到好几次。

Sample Data样本数据

Plate盘子 Brand Model Model InsertDate插入日期
99AA111 99AA111 Tesla特斯拉 S小号 2022-01-17 04:00:00 2022-01-17 04:00:00
99AA111 99AA111 Tesla特斯拉 S小号 2022-01-17 04:30:00 2022-01-17 04:30:00
99AA111 99AA111 Tesla特斯拉 S小号 2022-01-17 05:00:00 2022-01-17 05:00:00
59TA3312 59TA3312 Nissan日产 Skyline天际线 2022-01-17 04:00:00 2022-01-17 04:00:00
59TA3312 59TA3312 Nissan日产 Skyline天际线 2022-01-17 04:30:00 2022-01-17 04:30:00
129EA512 129EA512 Subaru斯巴鲁 Impreza翼豹 2022-01-17 03:30:00 2022-01-17 03:30:00

What i am trying to achieve is;我想要实现的是;

Plate盘子 Brand Model Model FirstPhotoDate第一张照片日期 SecondPhotoDate第二张照片日期
99AA111 99AA111 Tesla特斯拉 S小号 2022-01-17 04:00:00 2022-01-17 04:00:00 2022-01-17 04:30:00 2022-01-17 04:30:00
99AA111 99AA111 Tesla特斯拉 S小号 2022-01-17 05:00:00 2022-01-17 05:00:00 - -
59TA3312 59TA3312 Nissan日产 Skyline天际线 2022-01-17 04:00:00 2022-01-17 04:00:00 2022-01-17 04:30:00 2022-01-17 04:30:00
129EA512 129EA512 Subaru斯巴鲁 Impreza翼豹 2022-01-17 03:30:00 2022-01-17 03:30:00 - -

I have came up with;我想出了;

var groupedResult = resultList.GroupBy(f => f.Plate).Select(f => new ResultListView
{
    Plate = f.Key,
    FirstDate = f.Min(f => f.InsertDate),
    SecondDate = f.Max(f => f.InsertDate),
    Brand = f.FirstOrDefault().Brand,
    Model = f.FirstOrDefault().Model,
    TimeDifference = (f.Max(f => f.InsertDate) - f.Min(f => f.InsertDate)).TotalMinutes
});

But as it's obvious in the code, it only gives me the first and the last record but not as I expected.但正如代码中显而易见的那样,它只给了我第一条和最后一条记录,但并不像我预期的那样。 I am trying to group by plates and if same plate goes more than once, match it with the next one.我正在尝试按盘子分组,如果同一个盘子不止一次,请将其与下一个相匹配。 if only there is one photo or three photo, second date should be null.如果只有一张照片或三张照片,第二个日期应该是 null。

Of course different logic's could be applied but I believe this is more clear way.当然可以应用不同的逻辑,但我相信这是更清晰的方法。

I thougt that looping inside grouped result.我认为在分组结果中循环。 Take first and the second and skip two.拿第一个和第二个,跳过两个。 But this is not I am looking for.但这不是我要找的。

Thank you for your help.谢谢您的帮助。

Well, having (let use named tuple to demo):好吧,有(让我们使用命名元组来演示):

var resultList = new (string Plate, string Brand, string Model, DateTime InsertDate)[] {
  ("99AA111",  "Tesla",  "S",       new DateTime(2022,01,17, 04,00,00)),
  ("99AA111",  "Tesla",  "S",       new DateTime(2022,01,17, 04,30,00)),
  ("99AA111",  "Tesla",  "S",       new DateTime(2022,01,17, 05,00,00)),
  ("59TA3312", "Nissan", "Skyline", new DateTime(2022,01,17, 04,00,00)),
  ("59TA3312", "Nissan", "Skyline", new DateTime(2022,01,17, 04,30,00)),
  ("129EA512", "Subaru", "Impreza", new DateTime(2022,01,17, 03,30,00)),
};

You can GroupBy twice :您可以GroupBy两次

var groupedResult = resultList
  .GroupBy(item => item.Plate)
  .SelectMany(group => group
     .OrderBy(item => item.InsertDate)
     .Select((item, index) => (item: item, index: index / 2))
     .GroupBy(pair => pair.index)
     .Select(g => (
        Plate: g.First().item.Plate,
        Brand: g.First().item.Brand,
        Model: g.First().item.Model,
        FirstPhotoDate: g.First().item.InsertDate,
        SecondPhotoDate: (g.Count() == 1 ? null : (DateTime?)(g.Last().item.InsertDate))
     ))
   );

Let's have a look:我们来看一下:

string report = string.Join(Environment.NewLine, groupedResult
  .Select(r => $"{r.Plate,-8} : {r.Brand,-6} : {r.Model,-7} : {r.FirstPhotoDate} : {r.SecondPhotoDate}"));

Console.Write(report);

Outcome:结果:

99AA111  : Tesla  : S       : 17.01.2022 4:00:00 : 17.01.2022 4:30:00
99AA111  : Tesla  : S       : 17.01.2022 5:00:00 : 
59TA3312 : Nissan : Skyline : 17.01.2022 4:00:00 : 17.01.2022 4:30:00
129EA512 : Subaru : Impreza : 17.01.2022 3:30:00 : 

Here's the cleanest approach I can think of:这是我能想到的最干净的方法:

var resultList = new (string Plate, string Brand, string Model, DateTime InsertDate)[]
{
    ("99AA111",  "Tesla",  "S",       new DateTime(2022,01,17, 04,00,00)),
    ("99AA111",  "Tesla",  "S",       new DateTime(2022,01,17, 04,30,00)),
    ("99AA111",  "Tesla",  "S",       new DateTime(2022,01,17, 05,00,00)),
    ("59TA3312", "Nissan", "Skyline", new DateTime(2022,01,17, 04,00,00)),
    ("59TA3312", "Nissan", "Skyline", new DateTime(2022,01,17, 04,30,00)),
    ("129EA512", "Subaru", "Impreza", new DateTime(2022,01,17, 03,30,00)),
};

var query =
    from r in resultList
    group (DateTime?)r.InsertDate by new { r.Plate, r.Brand, r.Model } into grs
    from bgrs in grs.OrderBy(x => x).Buffer(2)
    select new
    {
        grs.Key.Plate,
        grs.Key.Brand,
        grs.Key.Model,
        FirstPhotoDate = bgrs.First(),
        SecondPhotoDate = bgrs.Skip(1).Any() ? bgrs.Last() : null,
    };

I used the Buffer operator from Microsoft's System.Interactive NuGet.我使用了 Microsoft System.Interactive NuGet 中的Buffer运算符。

That gives me:这给了我:

询问

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM