2012-08-27 19 views
7

Me encuentro frecuentemente tratando con un objeto IEnumerable que necesito recorrer y hacer un cálculo para cada elemento que depende de la n inmediatamente anterior y siguientes objetosC# Looping a través de un IEnumerable para un cálculo que usa los elementos n anterior y n

Un ejemplo común es el cálculo de una media móvil, pero otras veces el cálculo es más complejo que eso y se basa en varios campos de cada uno de los elementos de la lista

nunca estoy seguro sobre la mejor manera de estructurar mis bucles La eficiencia es importante, pero la capacidad de mantenimiento y la legibilidad son más importantes.

  • A veces se convierten en una lista y luego utilizar un bucle para obtener elementos de [i-1], [i], [i + 1] y luego realizo mis cálculos.

  • Otras veces lo conservo como un IEnumerable, pero guardo en "caché" el par de elementos anteriores, por lo que no hago el cálculo hasta que llegué a [i + 1] en el ciclo foreach .

  • También consideré el uso de una lista vinculada, por lo que podría utilizar los métodos .Previous y .Next.

¿Alguna sugerencia sobre qué técnica sería la mejor para usar?

+0

No va a opinar en este momento (muchos pros y los contras una respuesta corta, y no tengo tiempo para una respuesta larga), pero otra posibilidad para algunos casos es que se muevan dos indicios en tándem. 'i' comienza en, por ejemplo, 0 y 'j' en 3, haces cosas con' k' yendo de 'i' a' j' (medio abierto, golpeando así a 0, 1 y 2), luego mueves '++ i; ++ j; 'y repita' k' de nuevo, hasta '++ j> list.Count' (por lo que j puntos justo fuera de la lista en el último pase). –

Respuesta

2

Aquí es una implementación simple:

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(); 
     } 
    } 
} 

Probablemente se podría mejorar, pero parece que funciona ...

EDIT: he aquí una versión ligeramente mejor (no es necesario enumerar la cola para calcular el promedio):

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

una opción sería hacer un método de extensión que proporciona una "ventana" de laminación se puede utilizar. Esto permitiría que usted escriba su bucle de una manera sencilla:

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(); 
     } 
    } 
} 

Esto entonces permitirá escribir sus algoritmos simplemente como:

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

Por curiosidad, ¿por qué la votación negativa? –

+0

Probablemente alguien que no entendió su respuesta ... De todos modos, me gusta el hecho de que es genérico y reutilizable para diferentes escenarios, pero me pregunto si hay una manera de hacerlo más eficiente ... Si puede suponer que el rendimiento la lista se consumirá antes de la próxima iteración, probablemente pueda devolverla directamente, lo que evitaría copiar los datos en una nueva lista. –

+0

@ThomasLevesque Sí. Usé deliberadamente 'LinkedList ' en lugar de 'Queue ' para la llamada ToList(), pero eso le permite ser más genérico y seguro, ya que puede usarlo en escenarios de ejecución diferida, PLINQ, etc. –

Cuestiones relacionadas