C# 使用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 然后,列表C# 使用LINQ按日期对序列进行分组,无间隔,c#,linq,sequence,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
relatedActivities
应按顺序包含连续事件
是否有更好的方法(可能使用LINQ)实现此目的?
我有一个使用的想法,但不知道如何让聚合在序列中发现一个间隙时爆发。不知何故,我不认为LINQ真正用于双向一维深度优先搜索,但我使用聚合构建了一个工作LINQ。对于这个例子,我将使用列表而不是数组。另外,我将使用
Activity
来引用存储数据的任何类。将其替换为适合您的代码的任何内容
在我们开始之前,我们需要一个小函数来处理一些事情List.Add(T)
返回null,但我们希望能够在列表中累积并返回此聚合函数的新列表。因此,您只需要一个简单的函数,如下所示
private List<T> ListWithAdd<T>(List<T> src, T obj)
{
src.Add(obj);
return src;
}
接下来,我们将以类似的方式构建以下事件,并对其进行聚合
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();
var nextEvents=orderedEvents.SkipWhile(a=>a.ID!=activity.ID);
relatedActivities=nextEvents.Aggregate(relatedActivities,(items,nextItem)=>nextItem.ActivityDate.Subtract(items.OrderBy(a=>a.ActivityDate).Last().ActivityDate.Days.Equals(1)?ListWithAdd(items,nextItem):items.ToList();
您可以在之后对结果进行适当排序,因为现在relatedActivities应该包含所有没有间隙的活动。当它到达第一个间隙时不会立即断裂,不,但我不认为你真的可以突破LINQ。因此,它只是忽略了它发现的任何超过间隙的东西
请注意,此示例代码仅对实际时间差进行操作。您的示例输出似乎暗示您需要一些其他比较因素,但这应该足以让您开始。只需在两个条目中的日期减法比较中添加必要的逻辑。在这种情况下,我认为标准的
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();
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>()
);
var relatedActivities=new List();
bool-found=false;
foreach(activities.OrderBy中的var项(a=>a.ActivityDate))
{
int count=relatedActivities.count;
如果((计数>0)和(&(relatedActivities[count-1].ActivityDate.Date.AddDays(1)!=item.ActivityDate.Date))
{
如果(找到)
打破
相关活动。清除();
}
相关活动。添加(项目);
if(item.ID==activity.ID)
发现=真;
}
如果(!找到)
相关活动。清除();
值得一提的是,这里有一个大致等效的、可读性差得多的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();
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>()
);
var relatedActivities=活动
.OrderBy(x=>x.ActivityDate)
总数的
(
新建{List=new List(),Found=false,ShortCircuit=false},
(a,x)=>
{
如果(a.短路)
返回a;
int count=a.List.count;
如果((计数>0)&(a.List[count-1].ActivityDate.Date.AddDays(1)!=x.ActivityDate.Date))
{
如果(a.Found)
返回新的{a.List,a.Found,ShortCircuit=true};
a、 List.Clear();
}
a、 增加(x);
返回新的{a.List,Found=a.Found | |(x.ID==activity.ID),a.ShortCircuit};
},
a=>a.Found?a.List:newlist()
);
以下是一个实现:
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);
}
公共静态IEnumerable GroupByContinental(
这是一个数不清的来源,
Func键选择器
)
{
int keyGroup=Int32.MinValue;
int currentGroupValue=Int32.MinValue;
返回源
.Select(t=>new{obj=t,key=keySelector(t))
.OrderBy(x=>x.key)
.GroupBy(x=>{
如果(currentGroupValue+1x.obj);
}
您可以通过减法将日期转换为整数,也可以(轻松地)想象一个日期时间版本。precedIngeEvents.TakeWhile(a=>a.ID!=previousEvent.ID)的用途是什么?在您的示例中,ID似乎总是唯一的。该行仅从
先前事件
项之前的可枚举项中获取项。将其视为.Previous()
method。对示例进行了一些清理,使其仅包括会议。经过再三考虑,显示“年假”可能更好,但您得到了我希望的要点:sAs ccomet还提到,您问题中显示的示例结果与您的规范或代码不匹配。我的代码应生成与您相同的结果(即,它们应与您的规范匹配)。如果希望结果与示例结果相匹配,您可能需要一些附加逻辑。这比我的要干净得多。可能要快得多,而且看起来肯定更花哨。“您问题中显示的示例结果与您的规范或代码不匹配”嗯,我一直在使用它,它工作得很好。我不确定你的意思。@codesluth:你的示例源数据显示了从2010年6月7日到2010年6月11日的连续运行日期。如果你只在ActivityDate
上分组,那么你的结果将包含记录1到5(如果透视ID为3)。您的示例结果仅包含记录2、3和4-要实现这一点,您还需要对其他内容进行分组。(键入
可能?)啊哈,我现在明白了。对不起,我不应该把类型
留在问题中-到目前为止,我已经把它们过滤掉了。我将重新构造这个问题,使其更有意义。“您的示例输出似乎暗示您需要一些其他比较因素”正如我在问题中指出的,有一个起点:ID 3。结果应该只有与第3项相邻的事件。LukeH刚刚为我澄清了这一点,对不起。我已经更改了q
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>()
);
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);
}