The*_*man 10 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;
}
}
Run Code Online (Sandbox Code Playgroud)
我创建了一个像这样的集合:
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 };
Run Code Online (Sandbox Code Playgroud)
我想要做的是将它们连续的范围分组 - 即如果前一个范围的结束值与下一个范围的开始相同,它们是连续的.
我们可以假设Range值中没有碰撞/重复或重叠.
在发布的示例中,我最终会得到两个组:
2011-11-1 12:00:00 - 2011-11-1 15:00:00
2011-11-1 16:00:00 - 2011-11-1 17:00:00
Run Code Online (Sandbox Code Playgroud)
为此提出迭代解决方案相当容易.但是有没有一些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;
}
}
Run Code Online (Sandbox Code Playgroud)
当然,调用OrderBy将导致ranges序列的完整迭代,但是没有避免这种情况.订购后,您可以在退回之前防止必须实现结果; yield如果条件决定,你只需要结果.
但是,如果您知道序列是有序的,那么您根本不需要调用OrderBy,并且可以yield在遍历列表时使用项目并在不同的折叠Range实例上中断.
最终,如果序列是无序的,那么您有两个选择:
OrderBy也是延期,但必须使用一个完整的迭代来订购序列),yield当你有一个要处理时,使用返回项目casperOne扩展方法的通用版本,用于:
var items = new[]
{
// Range 1
new { A = 0, B = 1 },
new { A = 1, B = 2 },
new { A = 2, B = 3 },
new { A = 3, B = 4 },
// Range 2
new { A = 5, B = 6 },
new { A = 6, B = 7 },
new { A = 7, B = 8 },
new { A = 8, B = 9 },
};
var ranges = items.ContinousRange(
x => x.A,
x => x.B,
(lower, upper) => new { A = lower, B = upper });
foreach(var range in ranges)
{
Console.WriteLine("{0} - {1}", range.A, range.B);
}
Run Code Online (Sandbox Code Playgroud)
实施扩展方法
/// <summary>
/// Calculates continues ranges based on individual elements lower and upper selections. Cannot compensate for overlapping.
/// </summary>
/// <typeparam name="T">The type containing a range</typeparam>
/// <typeparam name="T1">The type of range values</typeparam>
/// <param name="source">The ranges to be combined</param>
/// <param name="lowerSelector">The range's lower bound</param>
/// <param name="upperSelector">The range's upper bound</param>
/// <param name="factory">A factory to create a new range</param>
/// <returns>An enumeration of continuous ranges</returns>
public static IEnumerable<T> ContinousRange<T, T1>(this IEnumerable<T> source, Func<T, T1> lowerSelector, Func<T, T1> upperSelector, Func<T1, T1, T> factory)
{
//Validate parameters
// Can order by start date, no overlaps, no collisions
source = source.OrderBy(lowerSelector);
// Get enumerator
using(var enumerator = source.GetEnumerator())
{
// Move to the first item, if nothing, break.
if (!enumerator.MoveNext()) yield break;
// Set the previous range.
var previous = enumerator.Current;
// Cycle while there are more items
while(enumerator.MoveNext())
{
// Get the current item.
var current = enumerator.Current;
// If the start date is equal to the end date
// then merge with the previoud and continue
if (lowerSelector(current).Equals(upperSelector(previous)))
{
// Merge
previous = factory(lowerSelector(previous), upperSelector(current));
// 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;
}
}
Run Code Online (Sandbox Code Playgroud)