我正在尝试选择项目具有连续日期的列表子组,例如
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;
}
}
然后,列表relatedActivities
应按顺序包含连续事件。
有更好的方法(可能使用LINQ)吗?
我有一个使用.Aggregate()
的想法,但是在找到序列中的间隙时无法想到如何使聚合突破。
答案 0 :(得分: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);
}
您可以通过减法将日期转换为整数,或者想象一下DateTime版本(轻松)。
答案 1 :(得分:2)
不知何故,我不认为LINQ真的意味着用于双向一维深度优先搜索,但我使用Aggregate构建了一个有效的LINQ。对于这个例子,我将使用List而不是数组。此外,我将使用Activity
来引用您存储数据的任何类。将其替换为适合您代码的任何类。
在我们开始之前,我们需要一个小功能来处理某些事情。 List.Add(T)
返回null,但我们希望能够在列表中累积并返回此聚合函数的新列表。所以你需要的只是一个简单的函数,如下所示。
private List<T> ListWithAdd<T>(List<T> src, T obj)
{
src.Add(obj);
return src;
}
首先,我们获取所有活动的排序列表,然后初始化相关活动列表。该初始列表仅包含目标活动,以便开始。
List<Activity> orderedEvents = activities.OrderBy(a => a.ActivityDate).ToList();
List<Activity> relatedActivities = new List<Activity>();
relatedActivities.Add(activity);
我们必须将此分为两个列表,过去和未来就像您目前所做的那样。
我们将从过去开始,建筑应该看起来很熟悉。然后我们将所有这些汇总到相关的活动中。这使用我们之前写的ListWithAdd
函数。你可以将它压缩成一行并跳过将previousEvents声明为它自己的变量,但是我为这个例子保留了它。
var previousEvents = orderedEvents.TakeWhile(a => a.ID != activity.ID).Reverse();
relatedActivities = previousEvents.Aggregate<Activity, List<Activity>>(relatedActivities, (items, prevItem) => items.OrderBy(a => a.ActivityDate).First().ActivityDate.Subtract(prevItem.ActivityDate).Days.Equals(1) ? ListWithAdd(items, prevItem) : items).ToList();
接下来,我们将以类似的方式构建以下事件,并同样聚合它。
var nextEvents = orderedEvents.SkipWhile(a => a.ID != activity.ID);
relatedActivities = nextEvents.Aggregate<Activity, List<Activity>>(relatedActivities, (items, nextItem) => nextItem.ActivityDate.Subtract(items.OrderBy(a => a.ActivityDate).Last().ActivityDate).Days.Equals(1) ? ListWithAdd(items, nextItem) : items).ToList();
您可以在之后对结果进行适当的排序,因为现在相关的活动应该包含所有没有间隙的活动。当它遇到第一个缺口时它不会立即破坏,不,但我不认为你可以从字面上突破LINQ。所以它只是忽略了它找到的任何差距。
请注意,此示例代码仅对实际的时间差异进行操作。您的示例输出似乎意味着您需要一些其他比较因素,但这应该足以让您入门。只需在两个条目中的日期减法比较中添加必要的逻辑。
答案 2 :(得分:2)
在这种情况下,我认为标准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();
对于它的价值,这是一个大致相当的 - 并且可读性差得多 - 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>()
);