在C#中实现简单的轮询(移动平均)数组

8
作为一种诊断方式,我想在我的应用程序中显示每秒钟的循环次数。(类似于第一人称射击游戏中的每秒帧数。)
但是我不想显示最近的值,也不想显示自启动以来的平均值。我要计算的是最后X个值的平均值。
我的问题是,我想知道存储这些值的最佳方式。我的第一个想法是创建一个固定大小的数组,这样每个新值就会推出最旧的值。这是最好的做法吗?如果是,那么我该如何实现它?
编辑:这是我写的类: RRQueue。它继承了队列,但强制容量并在必要时进行排队。
编辑2:Pastebin已经过时了。现在在 GitHub repo 上。
6个回答

16

最简单的选择可能是使用 Queue<T>,因为它提供了您所需的先进先出行为。只需将您的项目 Enqueue() 到队列中,当您超过 X 个项目时,Dequeue() 多余的项。


我需要将所有值复制到一个数组中才能得到它们的平均值吗? - Tom Wright
@Tom:不,.NET泛型队列实现了IEnumerable<T>,因此您可以枚举元素来计算平均值。 - Ron Warholic
1
@Tom:只需要这样做:double average = myQueue.Average(); // 如果你正在使用 Queue<double> - Reed Copsey
我的错。由于某种原因,平均值在我的智能感知中没有显示出来,但允许我使用它而没有错误。现在这是一个新的问题。 - drharris
@drharris:它并没有针对IEnumerable<T>实现,而是针对IEnumerable<double/int等类型> - 因为它需要特定的类型进行计算。这可能就是你没看到它的原因... - Reed Copsey
显示剩余2条评论

14
一个简单但快速的实现:
private int[] values = new int [10];  // all 0's initially
private int sum = 0;
private int pos = 0;

public void AddValue (int v)
{
   sum -= values[pos];  // only need the array to subtract old value
   sum += v;
   values[pos] = v;     
   pos = (pos + 1) % values.Length;    
}

public int Average => sum / values.Length;

1
一个小的(主观)改进:sum += v - values[pos]; values[pos++] = v; pos %= values.length; - heltonbiker
(你也可以预先计算一个字段 double divisor = 1.0 / values.length,然后使用 return sum * divisor,因为除法比乘法更耗时,但这已经相当谨慎了,我承认...) - heltonbiker
2
由于除法比乘法更昂贵,这仅适用于简单/旧硬件。我会把所有这些微小的优化交给编译器处理。 - H H
工作得很好,但在C#中,Length应该用大写的 L - Couitchy
1
@Couitchy - 已修复。那是一个十年前的拼写错误 )-: - H H

3

可能使用滤波器:

平均值=0.9*平均值+0.1*数值, 其中“数值”是最近的测量结果

可以根据0.9和0.1进行变化(只要这两个数字之和为1)

这不完全是一个平均值,但它可以过滤掉尖峰、瞬变等,并且不需要用数组来存储。

问候, 卡雷尔


1
对于那些不需要正式数学正确性,而是期望平滑行为的应用程序来说,这个想法值得一看! - heltonbiker

1
如果您需要最快的实现,那么使用一个带有单独计数的固定大小数组()将是最快的。

0

我的实现:

class RoundRobinAverage
{
    int[] buffer;
    byte _size;
    byte _idx = 0;
    public RoundRobinAverage(byte size)
    {
        _size = size;
        buffer = new int[size];
    }

    public double Calc(int probeValue)
    {
        buffer[_idx++] = probeValue;
        if (_idx >= _size)
            _idx = 0;

        return buffer.Sum() / _size;
    }
}

使用方法:

private RoundRobinAverage avg = new RoundRobinAverage(10);\
...
var average = avg.Calc(123);

0
你应该看一下 Windows 内置的性能监控 :D。 MSDN 如果你以前没有使用过它,API 会感觉有点奇怪,但它非常快速、强大、可扩展,并且可以快速获得可用的结果。

谢谢Aaron。看起来很有趣,但也许对我所需的有些过度了。 - Tom Wright

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接