c# - 使用 LINQ 按日期对序列进行无间隙分组

标签 c# linq sequence

我正在尝试选择一个列表的子组,其中的项目具有连续的日期,例如

ID  StaffID  Title              ActivityDate
--  -------  -----------------  ------------
 1       41  Meeting with John    03/06/2010
 2       41  Meeting with John    08/06/2010
 3       41  Meeting Continues    09/06/2010
 4       41  Meeting Continues    10/06/2010
 5       41  Meeting with Kay     14/06/2010
 6       41  Meeting Continues    15/06/2010

I'm using a pivot point each time, so take the example pivot item as 3, I'd like to get the following resulting contiguous events around the pivot:

ID  StaffID  Title              ActivityDate
--  -------  -----------------  ------------
 2       41  Meeting with John    08/06/2010
 3       41  Meeting Continues    09/06/2010
 4       41  Meeting Continues    10/06/2010

My current implementation is a laborious "walk" into the past, then into the future, to build the list:

var activity = // item number 3: Meeting Continues (09/06/2010)

var orderedEvents = activities.OrderBy(a => a.ActivityDate).ToArray();

// Walk into the past until a gap is found
var preceedingEvents = orderedEvents.TakeWhile(a => a.ID != activity.ID);
DateTime dayBefore;
var previousEvent = activity;
while (previousEvent != null)
{
    dayBefore = previousEvent.ActivityDate.AddDays(-1).Date;
    previousEvent = preceedingEvents.TakeWhile(a => a.ID != previousEvent.ID).LastOrDefault();
    if (previousEvent != null)
    {
        if (previousEvent.ActivityDate.Date == dayBefore)
            relatedActivities.Insert(0, previousEvent);
        else
            previousEvent = null;
    }
}


// Walk into the future until a gap is found
var followingEvents = orderedEvents.SkipWhile(a => a.ID != activity.ID);
DateTime dayAfter;
var nextEvent = activity;
while (nextEvent != null)
{
    dayAfter = nextEvent.ActivityDate.AddDays(1).Date;
    nextEvent = followingEvents.SkipWhile(a => a.ID != nextEvent.ID).Skip(1).FirstOrDefault();
    if (nextEvent != null)
    {
        if (nextEvent.ActivityDate.Date == dayAfter)
            relatedActivities.Add(nextEvent);
        else
            nextEvent = null;
    }
}

relatedActivities 列表应按顺序包含连续的事件。

为此有更好的方法(可能使用 LINQ)吗?

我想到了使用 .Aggregate()但想不出如何让聚合在发现序列中的空缺时爆发。

最佳答案

这是一个实现:

public static IEnumerable<IGrouping<int, T>> GroupByContiguous(
  this IEnumerable<T> source,
  Func<T, int> keySelector
)
{
   int keyGroup = Int32.MinValue;
   int currentGroupValue = Int32.MinValue;
   return source
     .Select(t => new {obj = t, key = keySelector(t))
     .OrderBy(x => x.key)
     .GroupBy(x => {
       if (currentGroupValue + 1 < x.key)
       {
         keyGroup = x.key;
       }
       currentGroupValue = x.key;
       return keyGroup;
     }, x => x.obj);
}

您可以通过减法将日期转换为整数,或者想象一个 DateTime 版本(很容易)。

关于c# - 使用 LINQ 按日期对序列进行无间隙分组,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/3006679/

相关文章:

c# - 带整数字符串的枚举

c# - Linq:执行链查询顺序

c# - 如何按 Dictionary<string, int> 对列表<string> 进行排序

algorithm - 如何生成匹配模式的数字序列?

python - 在 Python 中使用序列匹配器查找最长的公共(public)字符串

swift - 什么是 Swift 中的多次序列?

c# - 将 WPF Datagrid 列设置为 Combobox itemssource

c# - 单击按钮后更改按钮的效果 WPF

c# - 在三元/条件运算符中转换时出现奇怪的编译器错误

c# - 列出 LINQ 中的唯一转换