繁体   English   中英

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

[英]How to get first two consecutive datetime points which do not reside within a time period using Linq?

我有一个日期时间值列表。 我试图使用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

说明:

  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个。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM