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

Rob*_*lly 7 c# ienumerable foreach list

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

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

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

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

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

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

有关哪种技术最好使用的建议?

Ree*_*sey 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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

  • 出于好奇 - 为什么贬低? (2认同)

Tho*_*que 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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

它可能可以改进,但似乎有效......

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

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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)