2012-08-27 8 views
7

Często znajduję się w obiekcie IEnumerable, którego potrzebuję, aby wykonać pętlę, wykonując obliczenia dla każdego elementu, który zależy od n bezpośrednio poprzedzającego i następujące obiekty.C# Pętla przez IEnumerable dla obliczeń, które używa n poprzednich i następnych elementów

Jednym z typowych przykładów jest obliczenie średnia krocząca, ale inne czasy obliczenie jest bardziej skomplikowane niż to i opiera się na kilku dziedzinach, z każdego z elementów listy

nigdy nie jestem pewien co do najlepszego sposobu uporządkowania moje pętle. Wydajność ma znaczenie, ale ważniejsza jest łatwość konserwacji i czytelność.

  • Czasami przekonwertować do listy, a następnie użyć pętli for, aby elementy [i-1], [i], [i + 1], a następnie wykonać moje obliczenia.

  • Innym razem zachowuję go jako IEnumerable, ale "buforuję" poprzednią parę elementów, więc nie wykonuję obliczeń dla i dopóki nie dotrę do [i + 1] w pętli foreach .

  • Rozważałem również użycie połączonej listy, aby móc korzystać z metod .Poprzednia i .Next.

Jakieś sugestie, która technika byłaby najlepsza w użyciu?

+0

nie zamierza być zdania, Right Now (zbyt wiele plusy i minusy dla krótka odpowiedź i nie mam czasu na długą odpowiedź), ale inną możliwością w niektórych przypadkach są dwa wskaźniki poruszane w tandemie. "i" zaczyna się od np. 0 i 'j' w 3, robisz rzeczy z' k' idąc od 'i' do' j' (półotwartym, więc trafiając 0, 1 i 2), przenosisz '++ i; ++ j; 'i ponownie zapętlaj' k', aż do '++ j> list.Count' (więc j wskazuje tuż poza listą na ostatnim przejściu). –

Odpowiedz

2

Oto proste wdrożenie:

public static IEnumerable<double> RollingAverage(this IEnumerable<double> values, int count) 
{ 
    var queue = new Queue<double>(); 
    foreach (var v in values) 
    { 
     queue.Enqueue(v); 
     if (queue.Count == count) 
     { 
      yield return queue.Average(); 
      queue.Dequeue(); 
     } 
    } 
} 

To może być prawdopodobnie lepsze, ale wydaje się działać ...

EDIT: tu jest nieznacznie lepsza wersja (nie trzeba wyliczyć kolejka, aby obliczyć średnią):

public static IEnumerable<double> RollingAverage(this IEnumerable<double> values, int count) 
{ 
    var queue = new Queue<double>(); 
    double sum = 0.0; 
    foreach (var v in values) 
    { 
     sum += v; 
     queue.Enqueue(v); 
     if (queue.Count == count) 
     { 
      yield return sum/count; 
      sum -= queue.Dequeue(); 
     } 
    } 
} 
6

Jedną opcją byłoby wykonanie metody rozszerzenia, która zapewniałoby ruchome "okno", którego można użyć. Pozwoliłoby to napisać pętlę w prosty sposób:

IEnumerable<IList<T>> CreateRollingWindow(IEnumerable<T> items, int size) 
{ 
    LinkedList<T> list = new LinkedList<T>(); 

    foreach(var item in items) 
    { 
     list.AddLast(item); 
     if (list.Count == size) 
     { 
      yield return list.ToList(); 
      list.RemoveFirst(); 
     } 
    } 
} 

To pozwól piszesz algorytmy prostu jako:

foreach(var window as collection.CreateRollingWindow(5)) 
{ 
    double rollingAverage = window.Average(); // window is IList<T> here 
} 
+2

Z ciekawości - dlaczego upadek? –

+0

Prawdopodobnie ktoś, kto nie zrozumiał twojej odpowiedzi ... W każdym razie podoba mi się to, że jest generyczny i nadaje się do wielokrotnego użytku w różnych sytuacjach, ale zastanawiam się, czy istnieje sposób, aby uczynić go bardziej wydajnym ... Jeśli możesz założyć, że uległy lista zostanie zużyta przed następną iteracją, prawdopodobnie możesz zwrócić ją bezpośrednio, co pozwoli uniknąć kopiowania danych do nowej listy. –

+0

@ThomasLevesque Tak. Celowo użyłem 'LinkedList ' zamiast 'Queue ' dla wywołania ToList() - ale to pozwala na to, aby był bardziej ogólny i bezpieczny, ponieważ można go używać w scenariuszach opóźnionej realizacji, PLINQ itp. –