我发现自己经常处理一个IEnumerable对象,我需要循环执行每个元素的计算,这些元素依赖于前面和后面的n对象。
一个常见的例子是计算滚动平均值,但有时计算比这更复杂,并且依赖于列表中每个元素的几个字段
我永远不确定构建循环的最佳方法。效率很重要,但可维护性和可读性更重要。
有时我转换为List然后使用for循环来获取元素[i-1],[i],[i + 1],然后执行我的计算。
其他时候我把它保存为IEnumerable,但是我“缓存”前面几个元素,所以我不会为i做计算,直到我在foreach中得到[i + 1]循环。
关于哪种技术最适合使用的任何建议?
答案 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();
}
}
}