将连续日期组合到范围中

我有一个对象列表

public class sample { public DateTime Date; public string content; } 

我希望能够创建一个新对象列表

 public class sampleWithIntervals { public DateTime startDate; public DateTime endDate; public string content; } 

应根据内容将样本对象分组为间隔。 间隔可以仅包括原始样本列表中包含的那些日期。 我不知道如何在Linq做到这一点。

样本数据:

 {"10/1/2013", "x"} {"10/2/2013", "x"} {"10/2/2013", "y"} {"10/3/2013", "x"} {"10/3/2013", "y"} {"10/10/2013", "x"} {"10/11/2013", "x"} {"10/15/2013", "y"} {"10/16/2013", "y"} {"10/20/2013", "y"} This should give me {"10/1/2013","10/3/2013", "x"} {"10/2/2013","10/3/2013", "y"} {"10/10/2013","10/11/2013", "x"} {"10/15/2013","10/16/2013", "y"} {"10/20/2013","10/20/2013", "y"} 

这是一种非Linq方式:

 List groups = new List(); sampleWithIntervals curGroup = null; foreach(sample s in samples.OrderBy(sa => sa.content).ThenBy(sa => sa.Date)) { if(curGroup == null || // first group s.Date != curGroup.endDate.AddDays(1) || s.content != curGroup.content // new group ) { curGroup = new sampleWithIntervals() {startDate = s.Date, endDate = s.Date, content = s.content}; groups.Add(curGroup); } else { // add to current group curGroup.endDate = s.Date; } } 

你可以使用一个技巧来对Linq执行此操作,该技巧将项目按日期减去索引分组以对连续项目进行分组:

 samples.OrderBy(s => s.content) .ThenBy(s => s.Date) // select each item with its index .Select ((s, i) => new {sample = s, index = i}) // group by date miuns index to group consecutive items .GroupBy(si => new {date = si.sample.Date.AddDays(-si.index), content = si.sample.content}) // get the min, max, and content of each group .Select(g => new sampleWithIntervals() { startDate = g.Min(s => s.sample.Date), endDate = g.Max(s => s.sample.Date), content = g.First().sample.content }) 

我有这个SplitBy扩展方法,你可以在其中指定一个分隔符谓词,用于分割集合,就像string.Split一样。

 public static IEnumerable> SplitBy(this IEnumerable source, Func delimiterPredicate, bool includeEmptyEntries = false, bool includeSeparator = false) { var l = new List(); foreach (var x in source) { if (!delimiterPredicate(x)) l.Add(x); else { if (includeEmptyEntries || l.Count != 0) { if (includeSeparator) l.Add(x); yield return l; } l = new List(); } } if (l.Count != 0 || includeEmptyEntries) yield return l; } 

因此,如果您可以指定连续的条纹分隔符,现在拆分很容易。 为此,您可以使用相邻项目对集合和zip进行排序,因此现在两个结果列中日期的差异可以作为分隔符。

 var ordered = samples.OrderBy(x => x.content).ThenBy(x => x.Date).ToArray(); var result = ordered.Zip(ordered.Skip(1).Append(new sample()), (start, end) => new { start, end }) .SplitBy(x => x.end.Date - x.start.Date != TimeSpan.FromDays(1), true, true) .Select(x => x.Select(p => p.start).ToArray()) .Where(x => x.Any()) .Select(x => new sampleWithIntervals { content = x.First().content, startDate = x.First().Date, endDate = x.Last().Date }); 

new sample()是一个用于正确获取Zip的虚拟实例。 Append方法是将项附加到IEnumerable<>序列,它是这样的:

 public static IEnumerable Append(this IEnumerable source, params T[] items) { return source.Concat(items); } 

注意:这不保留初始订单。 如果你想要原始订单,最初选择索引并立即形成一个匿名类( Select((x, i) => new { x, i }) )并在最后阶段根据索引进行排序,然后选择合适的类型。