2013-07-29 23 views
7

Czy istnieje eleganckie rozwiązanie do przechodzenia przez uporządkowaną listę w celu wykonania niektórych obliczeń na bieżącym i następnym obiekcie? Musi istnieć lepszy sposób z LINQ aby wykonać następujące czynności:Używanie LINQ do wykonywania obliczeń bieżącego i następnego obiektu

public static List<double> GetHoursBetweenDates(List<DateTime> aDates) 
{ 
    List<double> lst = new List<double>(); 
    var olst = aDates.OrderByDescending(d => d).ToList(); 
    for (int i = 0; i < olst.Count - 1; i++) 
    { 
     lst.Add(olst[i].Subtract(olst[i+1]).TotalHours); 
    } 
    return lst; 
} 
+1

Uważaj na różnicę obiektów 'DateTime' na ślepo. '.Kind' może wpływać na wyniki. Zobacz [tę odpowiedź] (http://stackoverflow.com/a/17950820/634824) i [ten artykuł] (http://codeofmatt.com/2013/04/25/the-case-against-datetime-now/). –

+0

Dzięki @Matt Będę o tym pamiętać. – Seatech

Odpowiedz

11

Najłatwiej porównać każdy kolejny element listy jest coś takiego:

var sorted = aDates.OrderByDescending(d => d); 
var results = 
    sorted.Zip(sorted.Skip(1), (a, b) => a.Subtract(b).TotalHours); 

Alternatywnie, można to zrobić:

var sorted = aDates.OrderByDescending(d => d).ToArray(); 
var results = 
    from i in Enumerable.Range(0, sorted.Length - 1) 
    select sorted[i].Subtract(sorted[i + 1]).TotalHours; 

Ale ta druga metoda będzie działać tylko List<T>, T[] lub dowolnego typu, który obsługuje indeksujących tablica stylu.

4

Można użyć Incremental metodę rozszerzenia z moreLINQ Biblioteka:

public static List<double> GetHoursBetweenDates(List<DateTime> aDates) 
{ 
    return aDates.OrderByDescending(d => d) 
       .Incremental((p,n) => p.Subtract(n).TotalHours) 
       .ToList(); 
} 

Robi dokładnie to, czego potrzebujesz:

/// <summary> 
/// Computes an incremental value between every adjacent element in a sequence: {N,N+1}, {N+1,N+2}, ... 
/// </summary> 
/// <remarks> 
/// The projection function is passed the previous and next element (in that order) and may use 
/// either or both in computing the result.< 
/// If the sequence has less than two items, the result is always an empty sequence. 
/// The number of items in the resulting sequence is always one less than in the source sequence. 
/// </remarks> 
7

Jako alternatywę dla roztworu przy użyciu LINQ w Zip wyliczający, która wymaga od Ciebie powtórz dwukrotnie twoją listę, oto niestandardowy operator LINQ, który iteruje po sekwencji i zwraca "ruchomą parę" elementów:

static IEnumerable<Tuple<T, T>> Pairwise<T>(this IEnumerable<T> xs) 
{ 
    using (IEnumerator<T> enumerator = xs.GetEnumerator()) 
    { 
     if (!enumerator.MoveNext()) yield break; 
     T current = enumerator.Current; 
     while (enumerator.MoveNext()) 
     { 
      T previous = current; 
      current = enumerator.Current; 
      yield return Tuple.Create(previous, current); 
     } 
    } 
} 

Można wtedy zastosować go do sekwencji DateTime następująco:

dates.Pairwise().Select(_ => _.Item2.Subtract(_.Item1).TotalHours); 
9

Inną opcją jest skorzystanie z funkcji zbiorczej, i wraca jako kruszywo bieżącego elementu. Ta dodatkowa korzyść polega na iteracji kolekcji tylko jeden raz:

public static List<double> GetHoursBetweenDates(List<DateTime> aDates) 
{ 
    List<double> lst = new List<double>(); 
    aDates.OrderByDescending(d => d).Aggregate((prev, curr) => { lst.Add(prev.Subtract(curr).TotalHours); return curr; }); 
    return lst; 
}