关于linq:根据C#列表中下一个列表项中的开始日期设置结束日期

Setting EndDate based on StartDate in next list item in a C# List

我有一个C清单项目如下-

1
List<MyClass> All_Items = GetListItems();

GetListItems()返回结果如下-

1
2
3
4
5
6
Category    StartDate    EndDate
AA          2008-05-1    
AA          2012-02-1
BB          2009-09-1
BB          2010-08-1
CC          2009-10-1

All_Items上使用linq,我希望以如下方式更新enddate列:

  • 如果当前类别的开始日期小于同一类别中下一个较大日期项目的开始日期,则使用比较大日期少一天的日期。
  • 如果没有更大的剩余日期,则更新至2099-12-31
  • 最终结果如下-

    1
    2
    3
    4
    5
    6
    Category    StartDate    EndDate
    AA          2008-05-1    2012-01-31
    AA          2012-02-1    2099-12-31
    BB          2009-09-1    2010-07-31
    BB          2010-08-1    2099-12-31
    CC          2009-10-1    2099-12-31

    我只能想用太多的循环来完成它。更好的选择是什么?


    LINQ不适合处理序列元素之间的依赖关系,当然也不用于更新。

    以下是实现目标的简单有效的方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    var groups = All_Items.OrderBy(item => item.StartDate).GroupBy(item => item.Category);
    foreach (var group in groups)
    {
        MyClass last = null;
        foreach (var item in group)
        {
            if (last != null) last.EndDate = item.StartDate.AddDays(-1);
            last = item;
        }
        last.EndDate = new DateTime(2099, 12, 31);
    }

    因此,我们使用LINQ只是按StartDate对元素进行排序,并按Category对结果进行分组(保留了每组内部的排序)。然后简单地迭代LINQ查询结果并相应地更新EndDate


    试试这个代码。它循环遍历所有项,并为同一类别选择下一个较大的item.startDate。如果这样的项目不可用,它将设置默认日期。

    当我在手机上写代码时,我无法测试代码,所以欢迎进行任何更正。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    foreach(var item in All_Items)
    {
        var nextItem = (from i in All_Items
                              where i != null &&
                                          i.Category == item.Category &&
                                          i.StartDate > item.StartDate
                              orderby i.StartDate
                              select i).FirstOrDefault();
       item.EndDate = nextItem != null ? nextItem.StartDate.AddDays(-1) : new DateTime(2099,12,31);
    }


    您可以为每个类别选择日期,并将其放入字典中,以便以后节省时间。

    然后,根据您的要求,检查所有项目的开始日期是否小于类别中的下一个。

    这里是:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
            var categoryDictionary = All_Items
                                        .GroupBy(i => i.Category)
                                        .ToDictionary(
                                            g => g.Key,
                                            g => g.Select(i => i.StartDate));
            var defaultDate = DateTime.Parse("2099-12-31");
            foreach (var item in All_Items)
            {
                var nextDateInCategory = categoryDictionary[item.Category]
                                            .Where(i => i > item.StartDate)
                                            .OrderBy(i => i)
                                            .FirstOrDefault();
                item.EndDate =
                    nextDateInCategory != default(DateTime)
                        ? nextDateInCategory.AddDays(-1)
                        : defaultDate;
            }


    在单个LINQ语句中(maxEndDate是2099-12-31):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    All_Items.GroupBy(category => category.Category).Select(key =>
                    {
                        var maxCategoryStartDate = key.Max(value => value.StartDate);
                        return key.Select(v => {
                            if (DateTime.Equals(v.StartDate, maxCategoryStartDate))
                            {
                                v.EndDate = maxEndDate;
                            }
                            else
                            {
                                v.EndDate = maxCategoryStartDate - TimeSpan.FromDays(1);
                            }
                            return v;
                            });
                    }
                ).SelectMany(x => x);

    假设你的MyClass看起来是这样的:

    1
    2
    3
    4
    5
    6
    public class MyClass
    {
        public string Category { get; set; }
        public DateTime StartDate { get; set; }
        public DateTime EndDate { get; set; }
    }

    下面是您的方法,请参阅代码中的注释以获取解释。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    IEnumerable<MyClass> All_Items = new List<MyClass>
    {
        new MyClass { Category ="AA", StartDate = new DateTime(2008, 5, 1) },
        new MyClass { Category ="AA", StartDate = new DateTime(2012, 2, 1) },
        new MyClass { Category ="BB", StartDate = new DateTime(2009, 9, 1) },
        new MyClass { Category ="BB", StartDate = new DateTime(2010, 8, 1) },
        new MyClass { Category ="CC", StartDate = new DateTime(2009, 10, 1) }
    }
        // Group by category
        .GroupBy(c => c.Category)
        // Colapse the groups into a single IEnumerable
        .SelectMany(g =>
        {
            // Store the already used dates
            List<DateTime> usedDates = new List<DateTime>();

            // Get a new MyClass that has the EndDate set, from each MyClass in the category
            return g.Select(c =>
            {
                // Get all biggerDates that were not used already
                var biggerDates = g.Where(gc => gc.StartDate > c.StartDate && !usedDates.Any(ud => ud == gc.StartDate));
                // Set the endDate to the default one
                DateTime date = new DateTime(2099, 12, 31);

                // If a bigger date was found, mark it as used and set the EndDate to it
                if (biggerDates.Any()) {
                    date = biggerDates.Min(gc => gc.StartDate).AddDays(-1);
                    usedDates.Add(date);
                }

                return new MyClass
                {
                    Category = c.Category,
                    StartDate = c.StartDate,
                    EndDate = date
                };
            });
        });