4

我之前问过这个问题来收集日期时间对象并按 dayOfweek 和 time 对它们进行分组

所以只是回顾一下:我收集了 DateTime

List<DateTime> collectionOfDateTime = GetDateColletion();

然后按 dayofWeek 和一天中的时间分组

var byDayOfWeek = collectionOfDateTime.GroupBy(dt => dt.DayOfWeek + "-" + dt.Hour + "-" + dt.Minute);

所以在这一点上,我将这些按周(一致的时间)分组,效果很好。

我现在有一个按月而不是按周分组的新要求。当我说“月”时,它不是一个月中的同一天,而是类似于“每个月的第一个星期二”

我试图找出在一个组中使用什么“键”来分组符合每月逻辑的所有项目(每月的第一个星期二,每个月的第二个星期五等)

例如,假设我有这些日期开始;

var date1 = new DateTime(2013, 1, 4).AddHours(8);  // This is the first Friday in Jan
var date2 = new DateTime(2013, 2, 1).AddHours(8);  // This is the first Friday in Feb
var date3 = new DateTime(2013, 1, 5).AddHours(3);  // This is the first Sat in Jan
var date4 = new DateTime(2013, 2, 2).AddHours(3);  // This is the first Sat in Feb
var date5 = new DateTime(2013, 2, 2).AddHours(6);  // This is the first Sat in Feb - different time

如果这些是进入原始数组的日期,我需要一个 groupby 来结束 3 个组。

  • 第一组将包含 date1 和 date2
  • 第二组将包含 date3 和 date4。
  • date5 将是独立的,因为它与不同时间的任何其他组都不匹配

任何人都可以建议按照该标准进行分组吗?

4

3 回答 3

2

我认为这比看起来容易:

var byDayOfMonth = from d in dates
                   let h = (d.Day / 7) + 1
                   group d by new { d.DayOfWeek, h } into g
                   select g;

局部变量h = (d.Day / 7) + 1设置了该DayOfWeek月的实际情况。

我运行它进行测试并收到 2 个组,与您的示例完全相同。该组的键是:

{ DayOfWeek = Friday, h = 1 }
{ DayOfWeek = Saturday, h = 1 }

什么意思,有“每月的第一个星期五”和“每月的第一个星期六”的组。

d.Hour您可以通过和/或d.Minute您喜欢的方式轻松扩展分组键:

var byDayOfMonth = from d in dates
                   let h = (d.Day / 7) + 1
                   group d by new { d.DayOfWeek, h, d.Hour, d.Minute } into g
                   select g;

结果(仅键):

{ DayOfWeek = Friday, h = 1, Hour = 8, Minute = 0 }
{ DayOfWeek = Saturday, h = 1, Hour = 3, Minute = 0 }
{ DayOfWeek = Saturday, h = 1, Hour = 6, Minute = 0 }
于 2013-03-02T13:24:38.230 回答
0

可能有一种更简单的方法可以做到这一点,但这就是我的想法:

我从您的问题中收集到,您需要将“2 月的第一个星期二到 3 月的第一个星期一”等所有内容进行分组,这样您就可以得到这些天数可变的“月份”跨度 - 取决于月份他们开始。如果是这样,那么您确实需要使用一年中的日期将其分解为范围,因此:

Group by the First Wednesday of the Month 2013
Group 0 (0-1) 
All DayOfYear between 0 and 1 2013
Group 1 (2-36) 
The first Wednesday of the month: January is DayOfYear 2. 
The first Wednesday of the month: February is DayOfYear 37. 
etc.

所以第一个范围是一个函数f使得 f(32) = 1 (DayOfYear 是 32),因为它在 2 到 37 的范围内。这个f是一个范围的索引集合,在集合中找到给定 DayOfYear 的项目落入,并将该项目的索引作为组号返回。

您可以通过获取最小和最大日期来动态构建此表,GetDateCollection以确定总体范围。因为围绕日期的逻辑本身就是一个非常复杂的话题,所以我会求助于像NodaTime这样的库(特别是算术文档),从最小日期开始,逐日推进,直到找到第一个合格日(即, “本月的第一个星期一”)并创建一个范围 0 到那一天 - 1 作为组 0 并将其推送到索引集合(ArrayList可能)。然后从该日期循环使用LocalDate.PlusWeeks(1)直到月份更改,构建一个新范围并将该范围推送到同一个索引集合中。

每次跨入新的一年时,您都必须在 DayOfYear 中添加 365(如果前一年是闰年,则为 366),因为 DayOfYear 每年都会重置。

现在,您已经获得了一个范围集合,这些范围作为一个表格,根据 DayOfYear 将天分组为所需的单位。

编写一个遍历表的函数,+ [365|366] * x将给定日期的 DayOfYear(其中 x 是您要比较的日期是从您的最小年份开始的年数)与集合中的项目进行比较,直到找到该日期所在的范围,并且返回该项目的索引作为组号。(或者,如果提供的范围在该范围内,则每个范围都可以Func<DateTime,bool>返回 true DateTime。)

范围集合的另一种数据结构是 ushort 数组,其长度等于您的日期范围内从最小日期到最大日期的所有天数,以及为其分配的组号的每一天的值(使用范围计算,如上所述) . 这将在分组时执行得更快,但如果您使用较小的数据集(只有几百个日期),性能可能并不明显。

于 2013-03-02T04:34:15.233 回答
-2

要使用 Linq 进行分组,也许这段代码会对您有所帮助:

List<DateTime> collectionOfDateTime = GetDateColletion();
collectionOfDateTime.GroupBy(s => Convert.ToInt16(s.DayOfWeek) & s.Hour & s.Minute);
于 2013-03-02T12:23:04.693 回答