2010-09-25 20 views
16

Mam sytuację, w której muszę przetworzyć 5000 próbek z urządzenia co 0,5 sekundy.Czy istnieje funkcja w języku Java, aby uzyskać średnią ruchomą

Powiedzmy, że rozmiar okna wynosi 100, wtedy pojawi się 50 punktów wynikających z średniej ruchomej. Próbuję z konwencjonalną metodą, tj. Z pętlami. Ale jest to bardzo nieefektywny sposób na zrobienie tego. Jakieś sugestie ?

+4

Czy już czytałeś http://en.wikipedia.org/wiki/Moving_average, specjalnie "Ważona średnia ruchoma" –

+0

Ważona średnia krocząca jest znacznie łatwiejsza do obliczenia, znacznie szybsza, aw wielu przypadkach bardziej użyteczna do obliczenia . tj. służy do modelowania wielu systemów. –

Odpowiedz

8

Możesz to zrobić w O (1): zachowaj kolejkę ostatnich 50 wpisów. Po dodaniu wpisu i kolejce jest krótszy 50 elementów, wystarczy zaktualizować sumę i liczbę. Jeśli jest dłuższy niż 50 elementów, zaktualizuj także sumę i liczbę. Pseudokod:

add(double x) { 
    total += x; 
    addToQueue(x); 
    if (queueSize > 50) { 
     total -= removeLastFromQueue(); 
    } else { 
     count++; 
    } 
} 
double getAverage() { 
    return total/count; 
} 
23

Sprawdź bibliotekę Apache Maths. To ma metody robienia dokładnie tego, co chcesz. Aby uzyskać więcej informacji, zobacz DescriptiveStatistics i Mean.

+2

Witam, sprawdziłem bibliotekę. Która z metod ma średnią kroczącą? Nie mogę tego znaleźć. – Snake

+1

DescriptiveStatistics opcjonalnie robi średnią ruchomą – gerardw

18

Oto jeden sposób.

public class Rolling { 

    private int size; 
    private double total = 0d; 
    private int index = 0; 
    private double samples[]; 

    public Rolling(int size) { 
     this.size = size; 
     samples = new double[size]; 
     for (int i = 0; i < size; i++) samples[i] = 0d; 
    } 

    public void add(double x) { 
     total -= samples[index]; 
     samples[index] = x; 
     total += x; 
     if (++index == size) index = 0; // cheaper than modulus 
    } 

    public double getAverage() { 
     return total/size; 
    } 
} 

public class RollingTest extends TestCase { 

    private final static int SIZE = 5; 
    private static final double FULL_SUM = 12.5d; 

    private Rolling r; 

    public void setUp() { 
     r = new Rolling(SIZE); 
    } 

    public void testInitial() { 
     assertEquals(0d, r.getAverage()); 
    } 

    public void testOne() { 
     r.add(3.5d); 
     assertEquals(3.5d/SIZE, r.getAverage()); 
    } 

    public void testFillBuffer() { 
     fillBufferAndTest(); 
    } 

    public void testForceOverWrite() { 
     fillBufferAndTest(); 

     double newVal = SIZE + .5d; 
     r.add(newVal); 
     // get the 'full sum' from fillBufferAndTest(), add the value we just added, 
     // and subtract off the value we anticipate overwriting. 
     assertEquals((FULL_SUM + newVal - .5d)/SIZE, r.getAverage()); 
    } 

    public void testManyValues() { 
     for (int i = 0; i < 1003; i++) r.add((double) i); 
     fillBufferAndTest(); 
    } 


    private void fillBufferAndTest() { 
     // Don't write a zero value so we don't confuse an initialized 
     // buffer element with a data element. 
     for (int i = 0; i < SIZE; i++) r.add(i + .5d); 
     assertEquals(FULL_SUM/SIZE, r.getAverage()); 
    } 
} 
+3

Błąd: jeśli wywołasz metodę dodawania mniejszą niż SIZE (określoną w konstruktorze), otrzymasz złą średnią wartość. Dzieje się tak, ponieważ istnieje podział według SIZE w getAverage(). Prawdopodobnie potrzebujemy kolejnego licznika, aby zająć się tą sprawą. – sscarduzio

+0

@sscarduzio dobry połów. Nie próbowałem tego. Ale przypadek testowy TestOne() również wygląda podejrzanie. Przypuszczam, że może to być dokładne, jeśli wartości na liście zostaną zainicjowane na zero. Jest to jednak dość wymuszony wymóg. ;-) –

+2

Nie jest to niedokładne dla zestawów wartości mniejszych niż SIZE, kod ten jest niedokładny dla ostatnich wartości SIZE * co * zestawu wartości. Nie używaj. –

4

Oto dobra realizacja, przy użyciu BigDecimal:

import java.math.BigDecimal; 
import java.math.RoundingMode; 
import java.util.LinkedList; 
import java.util.Queue; 

public class MovingAverage { 

    private final Queue<BigDecimal> window = new LinkedList<BigDecimal>(); 
    private final int period; 
    private BigDecimal sum = BigDecimal.ZERO; 

    public MovingAverage(int period) { 
     assert period > 0 : "Period must be a positive integer"; 
     this.period = period; 
    } 

    public void add(BigDecimal num) { 
     sum = sum.add(num); 
     window.add(num); 
     if (window.size() > period) { 
      sum = sum.subtract(window.remove()); 
     } 
    } 

    public BigDecimal getAverage() { 
     if (window.isEmpty()) return BigDecimal.ZERO; // technically the average is undefined 
     BigDecimal divisor = BigDecimal.valueOf(window.size()); 
     return sum.divide(divisor, 2, RoundingMode.HALF_UP); 
    } 
} 
4

O ile mi wiadomo, nie ma takiej funkcji (klasa) w Javie. Ale możesz zrobić to samemu. Oto prosty przykład (SMA-Prosta średnia ruchoma):

public class MovingAverage { 
    private int [] window; 
    private int n, insert; 
    private long sum; 

    public MovingAverage(int size) { 
     window = new int[size]; 
     insert = 0; 
     sum = 0; 
    } 

    public double next(int val) { 
     if (n < window.length) n++; 
     sum -= window[insert]; 
     sum += val; 
     window[insert] = val; 
     insert = (insert + 1) % window.length; 
     return (double)sum/n; 
    } 
} 
0
static int[] myIntArray = new int[16]; 
public static double maf(double number) 
{ 
    double avgnumber=0; 
    for(int i=0; i<15; i++) 
    { 
     myIntArray[i] = myIntArray[i+1]; 
    } 
    myIntArray[15]= (int) number; 
    /* Doing Average */ 
    for(int i=0; i<16; i++) 
    { 
     avgnumber=avgnumber+ myIntArray[i]; 
    } 
    return avgnumber/16; 

} 

ten algorytm może być również nazywane jako średnia krocząca filtr, który działa dobrze dla mnie ... I wdrożone ten algo w moim projekcie milimetrowym !

Powiązane problemy