2011-11-30 35 views
7

我有一個場景,其中有一個對象列表,其中包含對象中的日期時間字段。我試圖找出是否有一種方法可以使用LINQ按照順序日期時間對列表進行分組,並將順序日期時間作爲範圍返回列表的子集。在列表上使用LINQ返回基於連續日期的子集列表

public virtual IList<LineItem> LineItems { get; set; } 
... 
public class LineItem 
{ 
    public virtual string Status { get; set; } 
    public virtual DateTime TransDate { get; set; } 
    ... 
} 

所以如果我有6個LineItem s的Status = P所有和

TransDate = { 8/1/2011 , 8/2/2011 , 8/3/2011 , 8/5/2011 , 8/6/2011 , 8/9/2011 } 

分別,我想返回以下列表:

{ (P, 8/1/2011-8/3/2011) , (P,8/5/2011-8/6/2011) , (P,8/9/2011) } 

有什麼想法?我可以通過手動遍歷列表和檢查TransDate來查看它是否是順序的,我只是尋找一種更優雅(最好是LINQ)的方式來做到這一點。謝謝!

+0

日期會不同嗎?另外,他們是否保證按照時間順序排序? – Ani

回答

6

我會用一個輔助方法是這樣的:

private static IEnumerable<ICollection<T>> PartitionByPredicate<T>(
    this IEnumerable<T> seq, Func<T, T, bool> split) 
{ 
    var buffer = new List<T>(); 

    foreach (var x in seq) 
    { 
     if (buffer.Any() && split(buffer.Last(), x)) 
     { 
      yield return buffer; 
      buffer = new List<T>(); 
     } 

     buffer.Add(x); 
    } 

    if (buffer.Any()) 
     yield return buffer; 
} 

然後:

var sorted = LineItems.OrderBy(i => i.TransDate); 
var split = sorted.PartitionByPredicate(
    (x, y) => (y.TransDate.Date - x.TransDate.Date).TotalDays > 1) 

(編輯:稍微清理一下,我的第一個版本很愚蠢。)

+0

我明白了,所以您要放入有序序列,當謂詞爲真時,「PartitionByPredicate」方法啓動下一個分區,在這種情況下,這是在日期中存在空隙的情況。 +1非常聰明;很好,一般,LINQy解決方案。 –

+0

稍微偏離主題,但是,我認爲我在Linq是合理的,我讀了你的答案,而我幾乎沒有理解它的20%,如果只有一件事顯示我還有很多東西要學,任何指向書的指針或有用的文章來了解你的答案中的一些更高級的東西,例如yield和helper謂詞? – Chris

+0

我還沒有閱讀任何C#書籍,但我可以推薦爲高質量的Eric Lippert的博客或Bart de Smet的博客 - 他們經常寫長篇文章,介紹C#語言和LINQ的內容 - 您可能會發現它們很難,但它們值得您時間。我個人發現,使用Scheme的經驗使得輕鬆獲取C#結構的功能,但這可能是一個非常迂迴的專業知識之路。對於練習,我還建議您在業餘時間嘗試解決人員在堆棧溢出問題上的問題:) – mquander

0

我不認爲這是非常優雅,但它的LINQ和它的作品:)

var list = new[] { 
    new DateTime(2011, 1, 1), 
    new DateTime(2011, 1, 2), 
    new DateTime(2011, 1, 3), 
    new DateTime(2011, 1, 5), 
    new DateTime(2011, 1, 6), 
    new DateTime(2011, 1, 8), 
    new DateTime(2011, 1, 10), 
}; 
var ordered = list.OrderBy(d => d); 
var accum = ordered.Aggregate(new Dictionary<DateTime, List<DateTime>>(), (dic, val) => { 
    if (!dic.Any()) 
    { 
     dic.Add(val, new List<DateTime> { val }); 
    } 
    else 
    { 
     if ((val - dic[dic.Keys.Last()].Last()).Days <= 1) 
      dic[dic.Keys.Last()].Add(val); 
     else 
      dic.Add(val, new List<DateTime> { val }); 
    } 
    return dic; 
}); 

在結果ACCUM將有4組:1-3,5-6,8和10

+0

這通常不起作用,因爲'dic.Values.Last()'實際上可能不是你放入字典的最後一件東西。 – mquander

+0

@mquander現在應該始終工作。 –

+1

我很確定'dic.Keys.Last()'不會做得更好。字典沒有按照插入順序或任何其他外部明顯的屬性進行排序。它們是散列表,並且他們也無法跟蹤插入順序而不會影響其性能或使用大量額外的內存。 – mquander

1

我建議你用@mquander建議的迭代器塊實現。

但這裏有一個有趣的,純粹的LINQ解決方案,將工作(儘管低效),假設日期是不同的時間:

var groups = from item in LineItems 
      let startDate = item.TransDate 
      group item by LineItems.Select(lineItem => lineItem.TransDate) 
            .SkipWhile(endDate => endDate < startDate)           
            .TakeWhile((endDate, index) => 
               startDate.AddDays(index) == endDate) 
            .Last(); 

//If required: 
var groupsAsLists = groups.Select(g => g.ToList()).ToList(); 

這是通過在任何時間選擇最後連續日期序列作爲該序列的關鍵。

+0

嗨安妮 - 感謝帖子,非常有趣。我最終使用了以前的解決方案(如您所建議的)。我不得不爲匿名函數添加額外的邏輯(不一定是唯一的日期),但仍然非常感謝輸入。 – sgeddes