7

我发现自己经常处理一个 IEnumerable 对象,我需要遍历该对象,并为每个元素进行计算,该元素取决于 n 个紧接在前面和后面的对象。

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

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

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

  • 其他时候,我将其保留为 IEnumerable,但我“缓存”了前几个元素,因此在 foreach 循环中到达 [i+1] 之前,我不会为 i 进行计算。

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

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

4

2 回答 2

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
}
于 2012-08-27T23:29:03.153 回答
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();
        }
    }
}
于 2012-08-27T23:28:25.483 回答