C#循环使用IEnumerable进行计算,该计算使用n个前n个元素和n个后续元素

时间:2012-08-27 23:16:18

标签: c# list foreach ienumerable

我发现自己经常处理一个IEnumerable对象,我需要循环执行每个元素的计算,这些元素依赖于前面和后面的n对象。

一个常见的例子是计算滚动平均值,但有时计算比这更复杂,并且依赖于列表中每个元素的几个字段

我永远不确定构建循环的最佳方法。效率很重要,但可维护性和可读性更重要。

  • 有时我转换为List然后使用for循环来获取元素[i-1],[i],[i + 1],然后执行我的计算。

  • 其他时候我把它保存为IEnumerable,但是我“缓存”前面几个元素,所以我不会为i做计算,直到我在foreach中得到[i + 1]循环。

  • 我还考虑使用链表,以便我可以使用.Previous和.Next方法。

关于哪种技术最适合使用的任何建议?

2 个答案:

答案 0 :(得分:6)

一种选择是制作一个扩展方法,提供一个可以使用的滚动“窗口”。这将允许您以一种简单的方式编写循环:

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

这样您就可以将算法编写为:

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

答案 1 :(得分:2)

这是一个简单的实现:

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

它可能会有所改善,但似乎有效......

编辑:这是一个稍好的版本(它不需要枚举队列来计算平均值):

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