如何使用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点。任何例子中的两个小时都应该在停电时间范围之前(如果至少有两个小时(或停电时间范围之后(就像这里的例子一样(。

如何使用Linq获取不在一个时间段内的前两个连续日期时间点

它不是很高效,也不是很可读,但你可以在单个查询中完成(见底部的高效解决方案(:

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

说明:

  1. 筛选出不在范围内的日期-我们不需要它们
  2. 将所有日期分为两组-小于范围的日期和大于范围的日期
  3. 订购两个组,使较小的日期组成为第一个
  4. 只从每组中选择前两个日期
  5. 以那些至少有两次约会的小组为例
  6. 将筛选后的结果投影到统一的日期序列中
  7. 选择前两个(如果有(

更有效的方法(如果对序列进行排序,否则您应该在查询之前对其进行排序(-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]

因为如果我没看错你的问题,你想要out2out3

使用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名。