【问题标题】:Group by date range , count and sort within each group LINQ按日期范围分组,在每组 LINQ 中进行计数和排序
【发布时间】:2013-09-17 14:07:34
【问题描述】:

我的对象中存储了一组日期。这是样本数据。实时,日期将来自服务调用,我不知道会返回哪些日期和多少:

        var ListHeader = new List<ListHeaderData>
        {
            new ListHeaderData
            {
                EntryDate = new DateTime(2013, 8, 26)
            },
            new ListHeaderData
            {
                EntryDate = new DateTime(2013, 9, 11)
            },
            new ListHeaderData
            {
                EntryDate = new DateTime(2013, 1, 1)
            },
            new ListHeaderData
            {
                EntryDate = new DateTime(2013, 9, 15)
            },
            new ListHeaderData
            {
                EntryDate = new DateTime(2013, 9, 17)
            },
            new ListHeaderData
            {
                EntryDate = new DateTime(2013, 9, 5)
            },
        };

我现在需要像这样按日期范围分组:

   Today (1) <- contains the date 9/17/2013 and count of 1
   within 2 weeks (3) <- contains dates 9/15,9/11,9/5 and count of 3
   More than 2 weeks (2) <- contains dates 8/26, 1/1 and count of 2

这是我的 LINQ 语句,它没有达到我的需要,但我认为我在球场上(如果我不是,请善待):

                   var defaultGroups = from l in ListHeader
                                    group l by l.EntryDate into g
                                    orderby g.Min(x => x.EntryDate)
                                    select new { GroupBy = g };

这按单独的日期分组,所以我有 6 个组,每个组有 1 个日期。如何按日期范围分组,在每个组内计数和排序?提前致谢。

【问题讨论】:

    标签: c# linq


    【解决方案1】:

    引入数组,其中包含您要分组的范围。这是两个范围 - 今天(零天)和 14 天(两周):

    var today = DateTime.Today;
    var ranges = new List<int?> { 0, 14 };
    

    现在按您的项目所属的范围对您的项目进行分组。如果没有合适的范围(所有日期超过两周),则将使用默认的null 范围值:

    var defaultGroups = 
          from h in ListHeader
          let daysFromToday = (int)(today - h.EntryDate).TotalDays
          group h by ranges.FirstOrDefault(range => daysFromToday <= range) into g
          orderby g.Min(x => x.EntryDate)
          select g;
    

    更新:为分组添加自定义范围:

    var ranges = new List<int?>();
    ranges.Add(0); // today
    ranges.Add(7*2); // two weeks
    ranges.Add(DateTime.Today.Day); // within current month
    ranges.Add(DateTime.Today.DayOfYear); // within current year
    ranges.Sort();
    

    【讨论】:

    • 根据 OP 的 cmets,似乎天的分组并不完全是固定的天数,即他提到了上个月,这是一个可变的天数。
    • @Servy 谢谢,我还没有看到其他答案的 OP cmets。让我检查一下。
    • @Servy 我认为用 OP 需要的任何值填充范围列表没有任何问题。例如。对于 currentMonth,您应该将 DateTime.Today.Day 添加到列表中。此外,如果不知道要添加哪些范围,则应在查询之前对范围列表进行排序。
    • @lazyberezovsky - 我真的很喜欢这种方法,但是我如何对各个组进行 asc/desc 排序?
    • @lazyberezovsky - 这正是我想要的。非常感谢
    【解决方案2】:

    这样做怎么样?

    引入一个用于分组和分组的新属性。

    class ListHeaderData
    {
        public DateTime EntryDate;
        public int DateDifferenceFromToday
        {
            get
            {
                TimeSpan difference = DateTime.Today - EntryDate.Date;
                if (difference.TotalDays == 0)//today
                {
                    return 1;
                }
                else if (difference.TotalDays <= 14)//less than 2 weeks
                {
                    return 2;
                }
                else
                {
                    return 3;//something else
                }
            }
        }
    }
    

    编辑:正如 @servy 在 cmets 中指出的那样,其他开发人员可能会混淆 int,使用 enum 会更具可读性。

    所以,你的类的修改版本看起来像这样

    class ListHeaderData
    {
        public DateTime EntryDate;
        public DateRange DateDifferenceFromToday
        {
            get
            {
                //I think for this version no comments needed names are self explanatory
                TimeSpan difference = DateTime.Today - EntryDate.Date;
                if (difference.TotalDays == 0)
                {
                    return DateRange.Today;
                }
                else if (difference.TotalDays <= 14)
                {
                    return DateRange.LessThanTwoWeeks;
                }
                else
                {
                    return DateRange.MoreThanTwoWeeks;
                }
            }
        }
    }
    
    enum DateRange
    { 
        None = 0,
        Today = 1,
        LessThanTwoWeeks = 2,
        MoreThanTwoWeeks = 3
    }
    

    并像这样使用它

     var defaultGroups = from l in ListHeader
         group l by l.DateDifferenceFromToday into g // <--Note group by DateDifferenceFromToday
         orderby g.Min(x => x.EntryDate)
         select new { GroupBy = g };
    

    【讨论】:

    • @JimBallard 刚刚看到您的个人资料。 OMG 30 多年的软件经验?我还没有超过 30 岁!
    • 哈!我的第一个代码是在输入 IBM 360 的打孔卡上。360 是全新的尖端技术!
    • 我会避免为此使用int;代码的读者将不知道它的含义。为此使用enum 之类的东西;对计算机来说都是一样的,但它会更加更具可读性。此外,没有理由仅仅为了将组放入其中而创建新的匿名类型。如果您只需要该组,只需选择该组即可。
    • @Servy yes 匿名类型在这里是多余的,我只是复制了 OP 的代码。我会在我的回答中考虑枚举
    • @Servy 使用枚举更新。我希望现在它看起来更具可读性
    【解决方案3】:

    您是否特别想以这种方式实现解决方案?此外,您真的想在您的类中引入虚假属性来满足这些要求吗?

    这三行将满足您的要求,并且对于大型集合将具有更高的性能。

    var todays = listHeader.Where(item => item.EntryDate == DateTime.Today);
    
    var twoWeeks = listHeader.Where(item => item.EntryDate < DateTime.Today.AddDays(-1) 
                                    && item.EntryDate >= DateTime.Today.AddDays(-14));
    
    var later = listHeader.Where(item => item.EntryDate < DateTime.Today.AddDays(-14));
    

    您还可以在不影响班级的情况下获得不同分组的灵活性。

    [编辑:响应订购查询]

    利用上面提供的 Enum,您可以应用 Union 子句和 OrderBy 子句 Linq 扩展方法,如下所示:

    var ord = todays.Select(item => new {Group = DateRange.Today, item.EntryDate})
              .Union(
              twoWeeks.Select(item => new {Group = DateRange.LessThanTwoWeeks, item.EntryDate}))
              .Union(
              later.Select(item => new {Group = DateRange.MoreThanTwoWeeks, item.EntryDate}))
              .OrderBy(item => item.Group);
    

    请注意,我正在通过 Linq Select 和匿名类添加 Grouping 以再次动态推送 Group 属性而不影响原始类。这会根据原始帖子产生以下输出:

    团体参赛日期
    今天 17/09/2013 00:00:00
    少于两周 11/09/2013 00:00:00
    少于两周 15/09/2013 00:00:00
    少于两周 05/09/2013 00:00:00
    MoreThanTwoWeeks 26/08/2013 00:00:00
    MoreThanTwoWeeks 01/01/2013 00:00:00 

    并通过计数获得分组日期范围:

    var ord = todays.Select(item => new {Group = DateRange.Today, Count=todays.Count()})
              .Union(
              twoWeeks.Select(item => new {Group = DateRange.LessThanTwoWeeks, Count=twoWeeks.Count()}))
              .Union(
              later.Select(item => new {Group = DateRange.MoreThanTwoWeeks, Count=later.Count()}))
              .OrderBy(item => item.Group);
    

    输出是:

    
    组数
    今天 1
    少于两周 3
    MoreThanTwoWeeks 2 

    【讨论】:

    • 感谢您的回复。如何对组进行排序(排序)?
    【解决方案4】:

    我想这取决于您计划使用它的程度。我有/有很多报告要生成,所以我创建了一个模型IncrementDateRange,其中StartTimeEndTimeTimeIncrement 作为枚举。

    时间增量处理程序有很多基于开关的函数,根据小时/天/周/月/季度/年等,吐出开始和结束范围之间的时间列表。

    然后你会得到你的IncrementDateRange 列表和 linq 中的任何一个:

    TotalsList = times.Select(t => new RetailSalesTotalsListItem()
    {
        IncrementDateRange = t,
        Total = storeSales.Where(s => s.DatePlaced >= t.StartTime && s.DatePlaced <= t.EndTime).Sum(s => s.Subtotal),
    })
    

    TotalsList = storeSales.GroupBy(g => g.IncrementDateRange.StartTime).Select(gg => new RetailSalesTotalsListItem()
    {
        IncrementDateRange = times.First(t => t.StartTime == gg.Key),
        Total = gg.Sum(rs => rs.Subtotal),
    }).ToList(),
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-04-15
      • 2019-02-19
      • 1970-01-01
      • 2021-06-09
      • 2021-03-05
      • 2015-09-28
      • 2013-11-04
      相关资源
      最近更新 更多