使用LINQ按日期对序列进行分组,没有间隙

Cod*_*uth 5 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

我每次都使用一个轴心点,所以将示例枢轴项目设为3,我想在枢轴周围得到以下产生的连续事件:

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

我目前的实施是一个艰难的"走"过去,然后到未来,建立列表:

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

然后,列表relatedActivities应按顺序包含连续事件.

有更好的方法(也许使用LINQ)吗?

我有一个使用的想法,.Aggregate()但是当它找到序列中的间隙时,无法想到如何使聚合突破.

Amy*_*y B 5

这是一个实现:

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);
}
Run Code Online (Sandbox Code Playgroud)

您可以通过减法将日期转换为整数,或者想象一下DateTime版本(轻松).


Luk*_*keH 3

在这种情况下,我认为标准foreach循环可能比 LINQ 查询更具可读性:

var relatedActivities = new List<TActivity>();
bool found = false;

foreach (var item in activities.OrderBy(a => a.ActivityDate))
{
    int count = relatedActivities.Count;
    if ((count > 0) && (relatedActivities[count - 1].ActivityDate.Date.AddDays(1) != item.ActivityDate.Date))
    {
        if (found)
            break;

        relatedActivities.Clear();
    }

    relatedActivities.Add(item);
    if (item.ID == activity.ID)
        found = true;
}

if (!found)
    relatedActivities.Clear();
Run Code Online (Sandbox Code Playgroud)

就其价值而言,这里有一个大致相同但可读性差得多的 LINQ 查询:

var relatedActivities = activities
    .OrderBy(x => x.ActivityDate)
    .Aggregate
    (
        new { List = new List<TActivity>(), Found = false, ShortCircuit = false },
        (a, x) =>
        {
            if (a.ShortCircuit)
                return a;

            int count = a.List.Count;
            if ((count > 0) && (a.List[count - 1].ActivityDate.Date.AddDays(1) != x.ActivityDate.Date))
            {
                if (a.Found)
                    return new { a.List, a.Found, ShortCircuit = true };

                a.List.Clear();
            }

            a.List.Add(x);
            return new { a.List, Found = a.Found || (x.ID == activity.ID), a.ShortCircuit };
        },
        a => a.Found ? a.List : new List<TActivity>()
    );
Run Code Online (Sandbox Code Playgroud)