I find myself frequently dealing with an IEnumerable object that I need to loop through an do a calculation for each element that depends on the n immediately prior and following objects.
One common example is calculating a rolling average, but other times the calculation is more complex than that and relies on several fields from each of the elements of the list
I am never sure about the best way to structure my loops. Efficiency matters, but maintainability and readability is more important.
Sometimes I convert to a List and then use a for loop to get elements [i-1],[i],[i+1] and then perform my calculation.
Other times I keep it as an IEnumerable, but I "cache" the previous couple of elements, so that I don't do the calculation for i until I have gotten to [i+1] in the foreach loop.
Any suggestions on which technique would be the best to use?
One option would be to make an extension method that provided a rolling "window" you could use. This would allow you to write your loop in a simple way:
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();
}
}
}
This would then let you write your algorithms simply as:
foreach(var window as collection.CreateRollingWindow(5))
{
double rollingAverage = window.Average(); // window is IList<T> here
}
Here's a simple implementation:
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();
}
}
}
It could probably be improved, but it seems to work...
EDIT: here's a slightly better version (it doesn't need to enumerate the queue to compute the average):
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();
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With