Linq 按日期范围分组

时间:2021-07-26 23:16:31

标签: c# linq

如果父键是通用的并且日期字段在彼此之间的 n(例如 2)小时内,我需要对集​​合进行分组。

示例数据:

List<DummyObj> models = new List<DummyObj>()
{
    new DummyObj { ParentKey = 1, ChildKey = 1, TheDate = DateTime.Parse("01/01/2020 00:00:00"), Name = "Single item - not grouped" },
    new DummyObj { ParentKey = 2, ChildKey = 2, TheDate = DateTime.Parse("01/01/2020 01:00:00"), Name = "Should be grouped with line below" },
    new DummyObj { ParentKey = 2, ChildKey = 3, TheDate = DateTime.Parse("01/01/2020 02:00:00"), Name = "Grouped with above" },
    new DummyObj { ParentKey = 2, ChildKey = 4, TheDate = DateTime.Parse("01/01/2020 04:00:00"), Name = "Separate item as greater than 2 hours" },
    new DummyObj { ParentKey = 2, ChildKey = 5, TheDate = DateTime.Parse("01/01/2020 05:00:00"), Name = "Grouped with above" },
    new DummyObj { ParentKey = 3, ChildKey = 6, TheDate = DateTime.Parse("01/01/2020 05:00:00"), Name = "Single item - not grouped" }
};

private class DummyObj
{
    public int ParentKey { set; get; }
    public int ChildKey { set; get; }
    public DateTime TheDate { set; get; }
    public string Name { set; get; }
}

结果分组应该是(子键):

{[1]}, {[2,3]}, {[4,5]}, {[6]}

我可以先按父键分组,然后循环比较组内的各个项目,但希望有一个更优雅的解决方案。

一如既往,非常感谢。

    public static void Test()
    {
        var list = GetListFromDb(); //returns List<DummyObj>;
        var sortedList = new List<DummyObj>();
        foreach(var g in list.GroupBy(x => x.ParentKey))
        {
            if(g.Count() < 2)
            {
                sortedList.Add(g.First());
            }
            else
            {
                var datesInGroup = g.Select(x => x.TheDate);
                var hoursDiff = (datesInGroup.Max() - datesInGroup.Min()).TotalHours;
                if(hoursDiff <= 2)
                {
                    string combinedName = string.Join("; ", g.Select(x => x.Name));
                    g.First().Name = combinedName;
                    sortedList.Add(g.First());
                }
                else
                {
                    //now it's the mess
                    DateTime earliest = g.Select(x => x.TheDate).Min();
                    var subGroup = new List<DummyObj>();
                    foreach(var line in g)
                    {
                        if((line.TheDate - earliest).TotalHours > 2)
                        {   
                            //add the current subgroup entry to the sorted group
                            subGroup.First().Name = string.Join("; ", subGroup.Select(x => x.Name));
                            sortedList.Add(subGroup.First());
                            //new group needed and new earliest date to start the group
                            sortedList = new List<DummyObj>();
                            sortedList.Add(line);
                            earliest = line.TheDate;                                
                        }
                        else
                        {
                            subGroup.Add(line);
                        }
                    }
                    //add final sub group, i.e. when there's none that are over 2 hours apart or the last sub group
                    if(subGroup.Count > 1)
                    {
                        subGroup.First().Name = string.Join("; ", subGroup.Select(x => x.Name));
                        sortedList.Add(subGroup.First());
                    }
                    else if(subGroup.Count == 1)
                    {
                        sortedList.Add(subGroup.First());
                    }                        
                }
            }
        }
    }

1 个答案:

答案 0 :(得分:1)

给你:

List<DummyObj> models = new List<DummyObj>()
{
    new DummyObj { ParentKey = 1, ChildKey = 1, TheDate = DateTime.Parse("01/01/2020 00:00:00"), Name = "Single item - not grouped" },
    new DummyObj { ParentKey = 2, ChildKey = 2, TheDate = DateTime.Parse("01/01/2020 01:00:00"), Name = "Should be grouped with line below" },
    new DummyObj { ParentKey = 2, ChildKey = 3, TheDate = DateTime.Parse("01/01/2020 02:00:00"), Name = "Grouped with above" },
    new DummyObj { ParentKey = 2, ChildKey = 4, TheDate = DateTime.Parse("01/01/2020 04:00:00"), Name = "Separate item as greater than 2 hours" },
    new DummyObj { ParentKey = 2, ChildKey = 5, TheDate = DateTime.Parse("01/01/2020 05:00:00"), Name = "Grouped with above" },
    new DummyObj { ParentKey = 3, ChildKey = 6, TheDate = DateTime.Parse("01/01/2020 05:00:00"), Name = "Single item - not grouped" }
};

List<List<DummyObj>> groups =
    models
        .GroupBy(x => x.ParentKey)
        .Select(xs => xs.OrderBy(x => x.TheDate).ToList())
        .SelectMany(xs => xs.Skip(1).Aggregate(new[] { xs.Take(1).ToList() }.ToList(), (a, x) =>
        {
            if (x.TheDate.Subtract(a.Last().Last().TheDate).TotalHours < 2.0)
            {
                a.Last().Add(x);
            }
            else
            {
                a.Add(new [] { x }.ToList());
            }
            return a;
        }))
        .ToList();

string output =
    String.Join(", ",
        groups.Select(x =>
            $"{{[{String.Join(",", x.Select(y => $"{y.ChildKey}"))}]}}"));

这给了我:

{[1]}, {[2,3]}, {[4,5]}, {[6]}

groups