LINQ GroupBy 连续时间
假设我有一个如下所示的简单结构:
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 魔法来以漂亮的单行代码实现这一点吗?
Assuming I have a simple structure that looks like this:
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;
}
}
And I create an collection like so:
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 };
What I want to do is group the ranges where they are continuous - i.e. they are continuous if the End value of the previous Range is the same as the Start of the next.
We can assume that there are no collisions/duplicates or overlaps in the Range values.
In the example posted, I would end up with two groups:
2011-11-1 12:00:00 - 2011-11-1 15:00:00
2011-11-1 16:00:00 - 2011-11-1 17:00:00
It's fairly easy to come up with an iterative solution for this. But is there some LINQ magic I can use to get this in a pretty one-liner?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(7)
您最好的选择是使用
yield
和扩展方法:授予,对
OrderBy
的调用正在进行引起充分ranges
序列的迭代,但这是无法避免的。一旦您订购了它,您就可以避免在返回结果之前必须实现结果;如果条件允许,您只需产生
结果即可。但是,如果您知道序列是有序的,那么您根本不必调用
OrderBy
,并且可以在遍历列表并中断时yield
项不同的折叠Range
实例。最终,如果序列是无序的,那么您有两个选择:
OrderBy
也会延迟,但必须使用一次完整迭代来对序列进行排序),当您有一个要处理的项目时,使用yield
返回该项目Your best bet is to use
yield
and an extension method:Granted, the call to
OrderBy
is going to cause a full iteration of theranges
sequence, but there's no avoiding that. Once you have it ordered, you can prevent having to materialize your results before returning them; you simplyyield
the results if the conditions dictate.If you know that the sequence is ordered, however, then you don't have to call
OrderBy
at all, and you canyield
items as you traverse the list and break on different collapsedRange
instances.Ultimately, if the sequence is unordered, then you have two options:
OrderBy
is deferred as well, but will have to use one full iteration to order the sequence), usingyield
to return the item when you have one to processcasperOne 扩展方法的通用版本,如下使用:
扩展方法的实现
A generic version of casperOne's extension method, used as such:
Implementation of extension method
您可以使用 Aggregate() 方法和 lambda 将它们组合在一起。正如您所说,假设没有重复或重叠:
现在,如果您知道范围是有序的,则可以始终将下一个与我们处理的最后一个进行比较,如下所示:
You could use the
Aggregate()
method and a lambda to group them together. This is, as you say, assuming no duplicates or overlaps:Now, if you know the ranges are ordered, you could get away with always comparing the next against the last one we processed, like so:
这是另一个 LINQ 解决方案。它使用一个查询找到每个连续范围的开头,使用另一个查询找到每个连续范围的结尾,然后遍历这些对来构造新的范围。
它可以重写为一行代码,但单独的版本更清晰且更易于维护:
Here is another LINQ solution. It finds the start of each continuous range with one query, the end of each continuous range with another, and then goes through the pairs to construct new ranges.
It could be rewritten into a one-liner, but the separate version is clearer and easier to maintain:
以下内容有效,但实际上是对 LINQ 的滥用:
该代码执行以下操作:
首先选择:
其中:仅选择那些标记新连续范围开始的元素
第二次选择:
Range
对象。请注意:
我会坚持使用迭代解决方案,因为上面的代码不可读,不可维护,并且它比仅仅输入一个循环花费了我更多的时间一个如果...
The following works but it really is an abuse of LINQ:
The code does the following:
First select:
Where: Only select those elements that mark the start of a new continuous range
Second select:
Range
object with the start date of the saved start value and the end value of the previous item.Please note:
I would stick with the iterative solution, because the above code is unreadable, unmaintainable and it took me significantly more time than just typing a loop and an if...
下面的代码以查询理解语法执行此操作。
我认为我实际上不会在生产代码中执行此操作,因为我觉得它违反了最小惊喜规则。 linq 语句通常没有副作用,而这是有效的,因为它有副作用。然而我认为值得发帖来表明它确实可以使用 O(n) 中的查询理解语法来解决
The below code does it in query comprehension syntax.
I don't think I would actually do this in production code because I feel it violates the rule of least surprise. Where linq statements usually has no side effects, this works because it has side effects. However I thought it worthwhile to post to show that indeed it can be solved using query comprehension syntax in O(n)