c# - 有效平均(移动平均)
问题描述
我有一个给定(恒定)频率的数据流(整数)。有时我需要计算不同的平均值(预定义)。我正在寻找快速高效的解决方案。
假设:
- 采样率是恒定的(预定义的),可能在 125-500 SPS 之间
- 我需要计算的平均值是预定义的,它可能是一个或多个平均值(例如仅过去 200 毫秒的平均值或过去 250 毫秒和最后 500 毫秒)。可能有很多平均值,但它们是预定义的!
- 在任何时候我都需要能够计算当前平均值(实时)
我现在拥有的:
- 我假设在特定的时间范围内总会有相同数量的数据。所以频率为 100SPS 我假设一秒钟正好包含 100 个值
- 创建具有恒定长度的队列(类似于缓冲区)
- 对于每个定义的平均值,创建 Sum 变量
- 每次新样品到达时,我都会将其放入队列中。
- 每次我在队列中有新样本时,我将其值添加到我拥有的每个 Sum 变量中,并删除窗口外元素的值(基于队列中的位置)
- 一旦我需要计算平均值,我只需将特定的 Sum 变量除以 Sum 应包含的元素数
为了让您更好地了解,我现在有一个代码:
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 个样本或多或少可能会发生很大变化。这就是为什么我需要一个真正的计时器来告诉我样本是否已经超出移动平均窗口。
为每个样本添加时间戳的想法似乎很有希望
解决方案
我不会使用链表,而是使用一些内部函数作为数组副本。在这个答案中,我为您的缓冲区类提供了可能的重写。接管这个想法,在每个位置保持一个总和。
这个缓冲区跟踪所有的总和,但为了做到这一点,它需要用新值对每个项目求和。根据您需要获得该平均值的频率,最好在需要时进行总结并仅保留单个值。
无论如何,我只是想指出如何使用 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;
}
}
另外要小心锁定语句,这将花费大量时间。
推荐阅读
- swift - 我想做一个可以在后台快速继续的下载,但是当我切换到后台时,蓝牙文件传输停止
- php - 如何在 WordPress 帖子编辑屏幕的元框中显示自定义帖子类型的自定义字段值
- r - 我在尝试将多个 XLS 表导出到 R 中自己的 csv 文件时遇到问题
- php - 在表单和提交按钮之前调用 php 查询
- java - Spring Data Streamable vs Stream
- python - Flask中的多对多关系插入记录
- javascript - BST 和表设置反应
- html - 网页末尾的页脚(内容之后)
- sql - 处理请求时发生未处理的异常 SqlException
- xpath - 如何使用 xpath/scrapy 根据 id 标签选择两个特定项目之间的所有项目