如何使用Linq获取不在一个时间段内的前两个连续日期时间点
本文关键字:两个 时间 日期 连续 一个 获取 Linq 何使用 时间段 | 更新日期: 2023-09-27 18:12:06
我有一个日期时间值列表。我正在尝试获取前两个连续的日期时间值其位于使用Linq的时间范围之外。我不知道该怎么做。
示例数据(可复制到LinqPad:
List<DateTime> list = new List<DateTime>
{
DateTime.Parse("07/08/2014 01:00 AM"), DateTime.Parse("07/08/2014 02:00 AM"),
DateTime.Parse("07/08/2014 03:00 AM"),DateTime.Parse("07/08/2014 04:00 AM"),DateTime.Parse("07/08/2014 05:00 AM"),
};
DateTime blackoutStartTime = DateTime.Parse("07/08/2014 02:00 AM");
DateTime blackoutEndTime = DateTime.Parse("07/08/2014 03:00 AM");
我试过这个是错误的:
var twoHours = list.Where(e => e <= blackoutStartTime || e >= blackoutEndTime)
.Take(2);
我预计结果是最后两个小时,凌晨4点和5点。任何例子中的两个小时都应该在停电时间范围之前(如果至少有两个小时(或停电时间范围之后(就像这里的例子一样(。
它不是很高效,也不是很可读,但你可以在单个查询中完成(见底部的高效解决方案(:
var twoHours = list.Where(d => d < blackoutStartTime || blackoutEndTime < d)
.OrderBy(d => d) // if sequence is not ordered
.GroupBy(d => blackoutEndTime < d)
.OrderBy(g => g.Key)
.Select(g => g.Take(2))
.Where(g => g.Count() == 2)
.SelectMany(g => g)
.Take(2);
输出:
7/8/2014 04:00:00
7/8/2014 05:00:00
说明:
- 筛选出不在范围内的日期-我们不需要它们
- 将所有日期分为两组-小于范围的日期和大于范围的日期
- 订购两个组,使较小的日期组成为第一个
- 只从每组中选择前两个日期
- 以那些至少有两次约会的小组为例
- 将筛选后的结果投影到统一的日期序列中
- 选择前两个(如果有(
更有效的方法(如果对序列进行排序,否则您应该在查询之前对其进行排序(-Jim Mischel的一个改进建议(为了更好的可读性,我会采用两种查询方式(:
var twoHours = list.TakeWhile(d => d < blackoutStartTime).Take(2).ToList();
if (twoHours.Count < 2)
twoHours = list.SkipWhile(d => d <= blackoutEndTime).Take(2).ToList();
改进之处在于,您不需要将每个查询结果保存到列表中。这将枚举所有符合条件的项,并在内存中创建新的列表。如果在范围之前有许多项目,或者在范围之前只有不到两个项目,在范围之后有许多项目——这不是您想要的。所以,只取前两项并将它们保存到列表中。在理想的世界里,一站只列举前两项。如果没有,那么您将枚举所有项目,直到范围结束+2。
我想你的问题是,当你有以下情况时,你的代码会返回错误的结果:
[out,in,in,in,out,out]
也就是说,一次在范围外,然后是一些在范围内,然后是更多在范围外。您想要两个连续的项目。您还会遇到以下问题:
[out1,out2,out3,in,in,in,out4,out5]
因为如果我没看错你的问题,你想要out2
和out3
。
使用LINQ实现这一点的简单方法是多个查询。我想你的名单是有序的:
var before = list.TakeWhile(d => d <= BlackoutStart).ToList();
if (before.Count >= 2)
{
return before.Skip(before.Count-2);
}
var after = list.SkipWhile(d => d <= BlackoutEnd).ToList();
if (after.Count >= 2)
{
return after.Take(2);
}
// Error here because you didn't have two consecutive items.
顺便说一下,我看不出用一个LINQ查询来实现这一点的方法,尽管可以优化上面的内容。
可以通过循环在列表上进行一次遍历,但逻辑有点混乱。
要获得连续值,请使用Zip
,如下所示:-
// Assume an ordered pair of date times
// if the later is before the start or the earlier is after the end,
// then there is no overlap
Func<DateTime, DateTime, bool> outOfRange = (DateTime a, DateTime b) =>
b < blackoutStartTime || a > blackoutEndTime;
var result = list.Zip(list.Skip(1), (a, b) => new { a, b })
.Where(x => outOfRange(x.a, x.b))
.First();
这样做还可以简化与遮光范围重叠的测试。
这确实假设初始列表是有序的,如果不是,请先对其进行排序。
这个答案还排除了像凌晨1点到4点这样的范围,它完全包围了遮光窗,而大多数其他答案都没有
list.Where(d => d >= blackoutStartTime && d <= blackoutEndTime)
.Sort((a, b) => b.CompareTo(a))
.Take(2);
你可能还想确定你确实拿到了最后2名。