c# - LINQ GroupBy 连续时间

标签 c# linq linq-to-objects

假设我有一个看起来像这样的简单结构:

public class Range
{
    public DateTime Start { get; set; }
    public DateTime End { get; set; }

    public Range(DateTime start, DateTime end)
    {
        this.Start = start;
        this.End = end;
    }
}

然后我像这样创建一个集合:

var dr1 = new Range(new DateTime(2011, 11, 1, 12, 0, 0), 
    new DateTime(2011, 11, 1, 13, 0, 0));
var dr2 = new Range(new DateTime(2011, 11, 1, 13, 0, 0), 
    new DateTime(2011, 11, 1, 14, 0, 0));
var dr3 = new Range(new DateTime(2011, 11, 1, 14, 0, 0), 
    new DateTime(2011, 11, 1, 15, 0, 0));
var dr4 = new Range(new DateTime(2011, 11, 1, 16, 0, 0), 
    new DateTime(2011, 11, 1, 17, 0, 0));

var ranges = new List<Range>() { dr1, dr2, dr3, dr4 };

我想做的是对连续的范围进行分组 - 即,如果前一个范围的结束值与下一个范围的开始值相同,则它们是连续的。

我们可以假设范围值中没有冲突/重复或重叠。

在发布的示例中,我最终会得到两个组:

2011-11-1 12:00:00 - 2011-11-1 15:00:00

2011-11-1 16:00:00 - 2011-11-1 17:00:00

为此想出一个迭代解决方案相当容易。但是,是否有一些 LINQ 魔法可以让我在一个漂亮的单行代码中获得它?

最佳答案

你最好的选择是使用 yield和扩展方法:

static IEnumerable<Range> GroupContinuous(this IEnumerable<Range> ranges)
{
    // Validate parameters.

    // Can order by start date, no overlaps, no collisions
    ranges = ranges.OrderBy(r => r.Start);

    // Get the enumerator.
    using (IEnumerator<Range> enumerator = ranges.GetEnumerator();
    {
        // Move to the first item, if nothing, break.
        if (!enumerator.MoveNext()) yield break;

        // Set the previous range.
        Range previous = enumerator.Current;

        // Cycle while there are more items.
        while (enumerator.MoveNext())
        {
            // Get the current item.
            Range current = enumerator.Current;

            // If the start date is equal to the end date
            // then merge with the previous and continue.
            if (current.Start == previous.End)
            {
                // Merge.
                previous = new Range(previous.Start, current.End);

                // Continue.
                continue;
            }

            // Yield the previous item.
            yield return previous;

            // The previous item is the current item.
            previous = current;
        }

        // Yield the previous item.
        yield return previous;
    }
}

授予,调用 OrderBy将导致 ranges 序列的完整迭代,但无法避免。一旦您订购了它,您就可以避免在返回之前必须实现您的结果;如果条件要求,您只需 yield 结果即可。

但是,如果您知道序列是有序的,那么您根本不必调用 OrderBy,并且可以在遍历列表时yield 项目并在不同的折叠 Range 实例上中断。

最终,如果序列是无序的,那么您有两个选择:

  • 排序列表然后处理(记住,OrderBy 也是延迟的,但必须使用一个完整的迭代来排序序列),使用 yield 返回当你有一个项目要处理的时候
  • 一次处理整个序列并作为一个完整的物化序列返回

关于c# - LINQ GroupBy 连续时间,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8100076/

相关文章:

c# - 将 session 转换为 int 以进行 linq 查询

c# - Linq 到对象 : Distinct + Concatenation

c# - LINQ 按日期间隔获取对象

c# - 如何在 Entity Framework 中将一个类类型的集合转换为另一种类类型集合

c# - XML 数据过滤和搜索

c# - 将单行从多维数组复制到新的一维数组

c# - Azure DevOps YAML 管道失败并显示 "A sequence was not expected"

c# - 将 foreach 替换为 LINQ 表达式

java - 在java中验证C#中的rsa签名

c# - 如何在 WPF 中的代码中复制资源引用?