首页 > 解决方案 > 有效平均(移动平均)

问题描述

我有一个给定(恒定)频率的数据流(整数)。有时我需要计算不同的平均值(预定义)。我正在寻找快速高效的解决方案。

假设:

我现在拥有的:

为了让您更好地了解,我现在有一个代码:

public class Buffer<T> : LinkedList<T>
{
    private readonly int capacity;

    public bool IsFull => Count >= capacity;

    public Buffer(int capacity)
    {
        this.capacity = capacity;
    }

    public void Enqueue(T item)
    {
        if (Count == capacity)
        {
            RemoveFirst();
        }
        AddLast(item);
    }
}


public class MovingAverage
{
    private readonly Buffer<float> Buffer;
    private static readonly object bufferLock = new object();
    public Dictionary<string, float> Sums { get; private set; }
    public Dictionary<string, int> Counts { get; private set; }

    public MovingAverage(List<int> sampleCounts, List<string> names)
    {
        if (sampleCounts.Count != names.Count)
        {
            throw new ArgumentException("Wrong Moving Averages parameters");
        }
        Buffer = new Buffer<float>(sampleCounts.Max());

        Sums = new Dictionary<string, float>();
        Counts = new Dictionary<string, int>();

        for (int i = 0; i < names.Count; i++)
        {
            Sums[names[i]] = 0;
            Counts[names[i]] = sampleCounts[i];
        }
    }


    public void ProcessAveraging(float val)
    {
        lock (bufferLock)
        {
            if (float.IsNaN(val))
            {
                val = 0;
            }
            foreach (var keyVal in Counts.OrderBy(a => a.Value))
            {
                Sums[keyVal.Key] += val;
                if (Buffer.Count >= keyVal.Value)
                {
                    Sums[keyVal.Key] -= Buffer.ElementAt(Buffer.Count - keyVal.Value);
                }

            }
            Buffer.Enqueue(val);
        }
    }

    public float GetLastAverage(string averageName)
    {
        lock (bufferLock)
        {
            if (Buffer.Count >= Counts[averageName])
            {
                return Sums[averageName] / Counts[averageName];
            }
            else
            {
                return Sums[averageName] / Buffer.Count;
            }
        }
    }
}

这非常好用,速度也足够快,但在现实世界中,拥有 100 SPS 并不意味着您将始终在 1 秒内拥有 100 个样本。有时是 100,有时是 99,有时是 101。计算这些平均值对我的系统至关重要,1 个样本或多或少可能会发生很大变化。这就是为什么我需要一个真正的计时器来告诉我样本是否已经超出移动平均窗口。

为每个样本添加时间戳的想法似乎很有希望

标签: c#algorithmmoving-average

解决方案


我不会使用链表,而是使用一些内部函数作为数组副本。在这个答案中,我为您的缓冲区类提供了可能的重写。接管这个想法,在每个位置保持一个总和。

这个缓冲区跟踪所有的总和,但为了做到这一点,它需要用新值对每个项目求和。根据您需要获得该平均值的频率,最好在需要时进行总结并仅保留单个值。

无论如何,我只是想指出如何使用 Array.Copy

public class BufferSum
{
    private readonly int _capacity;
    private readonly int _last;
    private float[] _items;

    public int Count { get; private set; }

    public bool IsFull => Count >= _capacity;

    public BufferSum(int capacity)
    {
        _capacity = capacity;
        _last = capacity - 1;
        _items = new float[_capacity];
    }

    public void Enqueue(float item)
    {
        if (Count == _capacity)
        {
            Array.Copy(_items, 1, _items, 0, _last);
            _items[_last] = 0;
        }
        else
        {
            Count++;
        }

        for (var i = 0; i < Count; i ++)
        {
            _items[i] += item;
        }
    }

    public float Avarage => _items[0] / Count;

    public float AverageAt(int ms, int fps)
    {
        var _pos = Convert.ToInt32(ms / 1000 * fps);
        return _items[Count - _pos] / _pos; 
    }
}

另外要小心锁定语句,这将花费大量时间。


推荐阅读