使用 LINQ 按日期对序列进行无间隙分组
我正在尝试选择列表的一个子组,其中项目具有连续的日期,例如,
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;
}
}
列表latedActivities
应该按顺序包含连续的事件。
是否有更好的方法(也许使用 LINQ)?
我有一个使用 .Aggregate()
但无法想象当它在序列中发现间隙时如何让聚合突破。
I'm trying to select a subgroup of a list where items have contiguous dates, e.g.
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;
}
}
The list relatedActivities
should then contain the contiguous events, in order.
Is there a better way (maybe using LINQ) for this?
I had an idea of using .Aggregate()
but couldn't think how to get the aggregate to break out when it finds a gap in the sequence.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
这是一个实现:
您可以通过减法将日期转换为整数,或者想象一个 DateTime 版本(简单)。
Here's an implementation:
You can either convert the dates to ints by means of subtraction, or imagine a DateTime version (easily).
在这种情况下,我认为标准的 foreach 循环可能比 LINQ 查询更具可读性:
就其价值而言,这里有一个大致等效但可读性差得多的 LINQ 查询:
In this case I think that a standard
foreach
loop is probably more readable than a LINQ query:For what it's worth, here's a roughly equivalent -- and far less readable -- LINQ query:
不知何故,我不认为 LINQ 真正适合用于双向一维深度优先搜索,但我使用 Aggregate 构建了一个可用的 LINQ。对于这个例子,我将使用列表而不是数组。另外,我将使用
Activity
来引用您存储数据的任何类。将其替换为适合您的代码的任何类。在开始之前,我们需要一个小函数来处理一些事情。
List.Add(T)
返回 null,但我们希望能够在列表中累积并返回此聚合函数的新列表。因此,您所需要的只是一个如下所示的简单函数。首先,我们获取所有活动的排序列表,然后初始化相关活动的列表。该初始列表将仅包含要启动的目标活动。
我们必须将其分为两个列表,过去和未来,就像您现在所做的那样。
我们将从过去开始,结构看起来应该很熟悉。然后我们将把所有这些聚合到 relatedActivities 中。这使用了我们之前编写的
ListWithAdd
函数。您可以将其压缩为一行并跳过将 previousEvents 声明为其自己的变量,但在本示例中我将其分开。接下来,我们将以类似的方式构建以下事件,并同样对其进行聚合。
之后您可以对结果进行正确排序,因为现在 relatedActivities 应包含所有没有间隙的活动。当它遇到第一个间隙时,它不会立即中断,不,但我不认为你可以从字面上突破 LINQ。因此,它只是忽略它发现的任何超出间隙的东西。
请注意,此示例代码仅对实际时间差进行操作。您的示例输出似乎暗示您需要一些其他比较因素,但这应该足以让您开始。只需在两个条目中的日期减法比较中添加必要的逻辑即可。
Somehow, I don't think LINQ was truly meant to be used for bidirectional-one-dimensional-depth-first-searches, but I constructed a working LINQ using Aggregate. For this example I'm going to use a List instead of an array. Also, I'm going to use
Activity
to refer to whatever class you are storing the data in. Replace it with whatever is appropriate for your code.Before we even start, we need a small function to handle something.
List.Add(T)
returns null, but we want to be able to accumulate in a list and return the new list for this aggregate function. So all you need is a simple function like the following.First, we get the sorted list of all activities, and then initialize the list of related activities. This initial list will contain the target activity only, to start.
We have to break this into two lists, the past and the future just like you currently do it.
We'll start with the past, the construction should look mostly familiar. Then we'll aggregate all of it into relatedActivities. This uses the
ListWithAdd
function we wrote earlier. You could condense it into one line and skip declaring previousEvents as its own variable, but I kept it separate for this example.Next, we'll build the following events in a similar fashion, and likewise aggregate it.
You can properly sort the result afterwards, as now relatedActivities should contain all activities with no gaps. It won't immediately break when it hits the first gap, no, but I don't think you can literally break out of a LINQ. So it instead just ignores anything which it finds past a gap.
Note that this example code only operates on the actual difference in time. Your example output seems to imply that you need some other comparison factors, but this should be enough to get you started. Just add the necessary logic to the date subtraction comparison in both entries.