Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C# Looping through an IEnumerable for a calculation that uses the n previous and n following elements

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.

  • I also considered using a linked list, so that I could use the .Previous and .Next methods.

Any suggestions on which technique would be the best to use?

like image 898
Rob Donnelly Avatar asked Aug 27 '12 23:08

Rob Donnelly


2 Answers

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
}
like image 199
Reed Copsey Avatar answered Sep 22 '22 20:09

Reed Copsey


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();
        }
    }
}
like image 21
Thomas Levesque Avatar answered Sep 22 '22 20:09

Thomas Levesque