Расчет скользящих средних переменных параметров - PullRequest
2 голосов
/ 09 декабря 2011

У меня есть целочисленное свойство, которое обновляется каждую секунду со значением силы сигнала в диапазоне от 0 до 100.

Я хотел бы иметь возможность постоянно измерять скользящую среднюю за последние10, 25, 50 измерений.

Какой самый эффективный способ сделать это?

В настоящее время я думаю о реализации набора очередей FIFO с использованием NSMutableArray и установке ведущего значения каждый раз, когда добавляю новое в конце, как только массив получает необходимое количество записей.Однако я не уверен, есть ли более эффективный способ сделать это или нет.

Ответы [ 3 ]

6 голосов
/ 23 апреля 2013

Я написал простой класс, названный MovingAverage, чтобы справиться с этим. Вы запускаете метод с количеством поддерживаемых периодов, и он отслеживает остальные, используя модуль счетчика выборок, чтобы знать, в какой из статических слотов его вставить.

Инициализировать с

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
4 голосов
/ 09 декабря 2011

Очередь - верный путь.Реальная эффективность зависит от того, как вы пересчитываете среднее значение.

Это должно быть сделано с помощью:

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

то есть новое скользящее среднее это просто старое среднее значение минус вес самого старого значения, которое выупал плюс вес самого нового значения, которое вы поставили в очередь.

1 голос
/ 09 декабря 2011

Я думаю, у вас есть правильное решение.

Если вы действительно заботитесь о производительности, вместо того, чтобы перемещать объекты в динамически изменяемый массив и выходить из него, вы можете использовать массив статического размера и отслеживать текущий индекс.

т.е. если N - размер массива, а% - оператор по модулю (я не являюсь объективным программистом C):

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

Среднее значение = сумма / N. Вы должны рассматривать период прогрева отдельно (до того, как у вас будет N образцов).

Это может быть намного быстрее в зависимости от того, как NSMutableArray обрабатывает выделение памяти.

...