2013-10-17 125 views
3

我有对象的列表结合连续的日期,范围

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"} 
+0

如果连续范围既有x又有y?他们是否应该分成两组? – nawfal

+0

它是否必须在Linq?一个简单的循环将更清洁。 –

+0

循环也很好。是的,他们应该在两个不同的组 – RRR

回答

8

这里有一个非LINQ的方式来做到这一点:

List<sampleWithIntervals> groups = new List<sampleWithIntervals>(); 
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 
         }) 
+0

当同一日期有两个不同内容的样本时,这将不起作用。它会创造更多的时间间隔。 – RRR

+0

@RaghaJ - 这将工作,如果你首先按'content'对'samples'进行排序,然后按'Date'排序 – mbeckish

+0

这太棒了! – nawfal

0

我有这个SplitBy扩展方法,你可以在哪里sp明确收集将被拆分的分隔符谓词,就像string.Split

public static IEnumerable<IEnumerable<T>> SplitBy<T>(this IEnumerable<T> source, 
                Func<T, bool> delimiterPredicate, 
                bool includeEmptyEntries = false, 
                bool includeSeparator = false) 
{ 
    var l = new List<T>(); 
    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<T>(); 
     } 
    } 
    if (l.Count != 0 || includeEmptyEntries) 
     yield return l; 
} 

因此,现在如果您可以指定连续的条纹分隔符,则分割很容易。为此,您可以订购相邻商品的收款和邮政编码,因此现在两个结果列中的日期差异可以作为分隔符。

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<T> Append<T>(this IEnumerable<T> source, params T[] items) 
{ 
    return source.Concat(items); 
} 

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