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

C#循环通过IEnumerable进行计算,该计算使用n个前元素和n个后元素,c#,list,foreach,ienumerable,C#,List,Foreach,Ienumerable,我发现自己经常处理一个IEnumerable对象,我需要通过一个循环对每个元素进行计算,该元素依赖于前面和后面的n个对象 一个常见的例子是计算滚动平均值,但其他情况下,计算比这更复杂,并且依赖于列表中每个元素的多个字段 我从不确定构造循环的最佳方法。效率很重要,但可维护性和可读性更重要 有时我转换成一个列表,然后使用for循环来获取元素[I-1]、[I]、[I+1],然后执行我的计算 其他时候,我将它作为IEnumerable保存,但我“缓存”了前面的两个元素,这样在foreach循环中到达[

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

一个常见的例子是计算滚动平均值,但其他情况下,计算比这更复杂,并且依赖于列表中每个元素的多个字段

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

  • 有时我转换成一个列表,然后使用for循环来获取元素[I-1]、[I]、[I+1],然后执行我的计算

  • 其他时候,我将它作为IEnumerable保存,但我“缓存”了前面的两个元素,这样在foreach循环中到达[I+1]之前,我不会对I进行计算

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

关于哪种技术最适合使用有什么建议吗?

下面是一个简单的实现:

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();
        }
    }
}
公共静态IEnumerable RollingAverage(此IEnumerable值,int计数)
{
var queue=新队列();
foreach(值中的var v)
{
排队。排队(v);
if(queue.Count==Count)
{
收益返回队列。平均值();
queue.Dequeue();
}
}
}
它可能会得到改进,但似乎有效

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

公共静态IEnumerable RollingAverage(此IEnumerable值,int计数)
{
var queue=新队列();
双和=0.0;
foreach(值中的var v)
{
总和+=v;
排队。排队(v);
if(queue.Count==Count)
{
收益回报总额/计数;
sum-=queue.Dequeue();
}
}
}

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

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();
        }
    }
}
IEnumerable CreateRollingWindow(IEnumerable项,整数大小)
{
LinkedList=新建LinkedList();
foreach(项目中的var项目)
{
列表。添加最后一个(项目);
if(list.Count==大小)
{
收益返回列表.ToList();
list.RemoveFirst();
}
}
}
这样,您就可以简单地将算法编写为:

foreach(var window as collection.CreateRollingWindow(5))
{
    double rollingAverage = window.Average(); // window is IList<T> here
}
foreach(变量窗口作为集合。CreateRollingWindow(5))
{
double rollingAverage=window.Average();//这里是IList窗口
}

我现在不想发表意见(简短的回答有太多的利弊,我没有时间回答太长的问题),但在某些情况下,另一种可能是两种迹象同时出现
i
从0开始,而
j
从3开始,你用
k
i
j
(半开,所以点击0、1和2),然后移动
+i++j并再次循环
k
,直到
++j>列表。计数(因此j在最后一次通过时刚好在列表之外)。出于好奇-为什么要进行向下投票?可能有人不明白你的答案。。。无论如何,我喜欢它的通用性和可重用性,但我想知道是否有一种方法可以使它更有效。。。如果可以假设生成的列表将在下一次迭代之前被使用,则可以直接返回它,这样可以避免将数据复制到新列表中。@thomaslevsque Yes。我特意用
LinkedList
而不是
Queue
来调用ToList(),但这使得它更加通用和安全,因为您可以在延迟执行场景、PLINQ等中使用它。
foreach(var window as collection.CreateRollingWindow(5))
{
    double rollingAverage = window.Average(); // window is IList<T> here
}