计算可变参数的移动平均数

3
我有一个整数属性,该属性每秒更新一次,其信号强度值范围从0到100。
我希望能够持续地测量过去10、25、50次的移动平均值。
最有效的方法是什么?
我目前考虑使用NSMutableArray实现一组FIFO队列,在数组具有所需条目数时,每次添加新值时从末尾弹出前导值。然而,我不确定是否有更有效的方法。
3个回答

6
我写了一个简单的类叫做MovingAverage来处理这个问题。你可以使用init方法初始化该方法,并保持其余部分的样本计数的模来知道要将其放入哪一个静态插槽中。
使用以下内容进行初始化:
MovingAverage *avg5periods = [[MovingAverage alloc] initWithSize:5];

添加项目:

[avg5periods addSample:1.0];
NSLog(@"1.2f",[avg5periods movingAverage]); //1.0
[avg5periods addSample:2.0];
NSLog(@"1.2f",[avg5periods movingAverage]); //1.5
[avg5periods addSample:3.0];
NSLog(@"1.2f",[avg5periods movingAverage]); //2.0
[avg5periods addSample:4.0];
NSLog(@"1.2f",[avg5periods movingAverage]); //2.5
[avg5periods addSample:5.0];
NSLog(@"1.2f",[avg5periods movingAverage]); //3.0
[avg5periods addSample:6.0];
NSLog(@"1.2f",[avg5periods movingAverage]); //4.0

头文件:

#import <Foundation/Foundation.h>

@interface MovingAverage : NSObject {
    NSMutableArray *samples;
    int sampleCount;
    int averageSize;
}
-(id)initWithSize:(int)size;
-(void)addSample:(double)sample;
-(double)movingAverage;
@end

实现文件:

#import "MovingAverage.h"

@implementation MovingAverage
-(id)initWithSize:(int)size {
    if (self = [super init]) {
        samples = [[NSMutableArray alloc] initWithCapacity:size];
        sampleCount = 0;
        averageSize = size;
    }
    return self;
}
-(void)addSample:(double)sample {
    int pos = fmodf(sampleCount++, (float)averageSize);
    [samples setObject:[NSNumber numberWithDouble:sample] atIndexedSubscript:pos];
}
-(double)movingAverage {
    return [[samples valueForKeyPath:@"@sum.doubleValue"] doubleValue]/(sampleCount > averageSize-1?averageSize:sampleCount);
}
@end

嗨!我刚尝试了你的解决方案。只有在样本大小等于5时才能很好地运作。将值设置为大于5的值(如10、20或更高)会导致该值减少。例如:每次我添加一个const == 10。当样本大小等于5时,移动平均返回10。当样本大小为10时,结果为5;当样本大小为20时,结果为2.5。这是正确的吗? - Yevhen Dubinin
1
我刚刚编辑了答案,从: int pos = fmodf(sampleCount ++,5.0); 到 int pos = fmodf(sampleCount ++,(float)averageSize); 基本上,fmodf应该按移动平均值中的样本数进行模运算。有意义吗? - earnshavian
那很有道理!谢谢。 - Yevhen Dubinin

4

队列是正确的方式。真正的效率取决于如何重新计算平均值。

应该使用以下方法进行操作:

avg = avg + newSample/N - [queue dequeue]/N
[queue enqueue:newSample]

即,新的移动平均值只是旧的平均值减去您删除的最旧值的权重,再加上您排队的最新值的权重。

[queue dequeue] 是什么意思? - progrmr
@progrmr:我的Objective-C语法有点生疏。我想发送消息“dequeue”到对象“queue”。也就是说,我假设他以某种方式实现了一个队列,或者使用了现有的队列类(如果有的话)。 - ArjunShankar

1

我认为你已经找到了正确的解决方案。

如果你真的关心性能,而不是将东西移进和移出一个动态调整大小的数组,你可以使用一个静态大小的数组,并跟踪当前索引。

即,如果N是数组的大小,%是模运算符(我不是Objective C程序员):

values[current] = get_current_sample()
previous = (current + N - 1) % N
sum = sum + values[current] - values[previous]
current = (current + 1) % N

平均值 = 总和 / N。您必须单独处理预热期(在获得N个样本之前)。

这取决于NSMutableArray如何处理内存分配,可能会更快。


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