2013-03-14 52 views
13

Ben List<DateTime> dates;Gruplama Bitişik

var tarihler ben olan bir sınıf var:

class NonWorkingDay 
{ 
    public DateTime Start; 
    public int Days; 
} 

Gruba onları temiz bir yol bulmaya çalışıyorum.

public List<NonWorkingDay> GetContiguousDates(List<DateTime> dates) 
{ 

} 

Not: Cuma günü bir NWD varsa ve bir sonraki Pazartesi ise gruplandırılmalıdır. Hafta sonları dikkate alınmaz. Örneğin

Ben

September 3 2013 
September 20 2013 
September 23 2013 
September 24 2013 
September 30 2013 
October 1 2013 

varsa çıkış olacaktır:

Start = September 3 2013, Days = 1 
Start = September 20 2013, Days = 3 //weekend got skipped 
Start = September 30 2013, Days = 2 

bunu yapmak için herhangi bir yolu (sayaç değişkenlerin bir grup var olmadan) var mıdır ve .Select veya kullanma. Nerede falan.

Teşekkür

+4

Güzel bulmaca! ... – spender

+0

GrupBir hafta sonra Gruptaki öğeleri sayabilir misiniz? Haftalığa göre gruplama burada bulunabilir http://stackoverflow.com/questions/8561782/how-to-group-dates-by-week – bUKaneer

+0

Hayır, çünkü orada pazartesi-cuma 2.5 haftadır diyebiliriz ki orada – jmasterx

cevap

17

Yani, biz bu jenerik yineleyici fonksiyonu ile dışarı başlayacağız. Bir diziyi ve iki öğeyi kabul eden ve bir boole döndüren bir yüklemi alır. Kaynaktaki öğelerde okunacak ve bir öğe, önceki öğeyle birlikte, yüklemeye dayalı olarak true değerini döndürür, sonraki öğe "sonraki grupta" olacaktır. Yanlış döndürürse, önceki grup dolu ve sonraki grup başlatılır.

public static IEnumerable<IEnumerable<T>> GroupWhile<T>(this IEnumerable<T> source 
    , Func<T, T, bool> predicate) 
{ 
    using (var iterator = source.GetEnumerator()) 
    { 
     if (!iterator.MoveNext()) 
      yield break; 

     List<T> currentGroup = new List<T>() { iterator.Current }; 
     while (iterator.MoveNext()) 
     { 
      if (predicate(currentGroup.Last(), iterator.Current)) 
       currentGroup.Add(iterator.Current); 
      else 
      { 
       yield return currentGroup; 
       currentGroup = new List<T>() { iterator.Current }; 
      } 
     } 
     yield return currentGroup; 
    } 
} 

Ayrıca, bir sonraki çalışma gününü bir tarihe dayanan bu basit yardımcı yönteme de ihtiyacımız var. Tatilleri de birleştirmek istiyorsanız, önemsizden oldukça zoruna gider, ama mantığın gideceği yer burasıdır.

public static DateTime GetNextWorkDay(DateTime date) 
{ 
    DateTime next = date.AddDays(1); 
    if (next.DayOfWeek == DayOfWeek.Saturday) 
     return next.AddDays(2); 
    else if (next.DayOfWeek == DayOfWeek.Sunday) 
     return next.AddDays(1); 
    else 
     return next; 
} 

Şimdi hepsini bir araya getirmek için. Önce günleri sipariş ediyoruz. (Her zaman emrettiğinizden emin olursanız, o bölümü kaldırabilirsiniz.) Ardından, her bir öğe bir önceki işin sonraki iş günü iken ardışık öğeleri gruplandırırız.

Yapmamız gereken tek şey, bir IEnumerable<DateTime> ardışık tarihini NonWorkingDay'a dönüştürmektir. Bunun için başlangıç ​​tarihi ilk tarihtir ve Days dizinin sayısıdır. Normalde iki kez kaynak dizisini yineleme olur hem First ve Count kullanırken, biz GroupWhile tarafından döndürülen dizi aslında başlık altında bir List olduğunu biliyor, bu yüzden birden çok kez yineleme bir sorun değildir ve Count alma hatta O (olduğu 1).

public IEnumerable<NonWorkingDay> GetContiguousDates(IEnumerable<DateTime> dates) 
{ 
    return dates.OrderBy(d => d) 
      .GroupWhile((previous, next) => GetNextWorkDay(previous).Date == next.Date) 
      .Select(group => new NonWorkingDay 
       { 
        Start = group.First(), 
        Days = group.Count(), 
       }); 
} 
+5

Absolute Genius, aferin efendim! o) – bUKaneer

+0

@plutonix, bazıları hemen belli olmayabilir, ancak bu durumda pratik olarak sadece kopyala/yapıştır ... – Servy