2013-10-17 122 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 })),並在最後階段根據索引進行排序,然後再選擇適當的類型。