Скользящее среднее / общий алгоритм - PullRequest
2 голосов
/ 30 августа 2011

Мне нужно отслеживать последние 7 дней рабочего дня в цикле чтения плоских файлов.Он используется для измерения «утомляемости» рабочих списков.

Сейчас у меня есть кое-что, что работает, но кажется довольно многословным, и я не уверен, есть ли шаблон, который является более лаконичным.

* 1004В настоящее время у меня есть класс Java со статическим массивом для хранения данных за последние x дней, затем, когда я читаю файл, я отрубаю первый элемент и перемещаю остальные 6 (для итоговой недели) обратно на единицу.Обработка этого статического массива выполняется его собственным методом, т. Е.
/**
 * Generic rolling average/total method. Keeps adding to an array of 
 * last 'x' seen.
 * @param d Datum point you want to add/track.
 * @param i Number of rolling periods to keep track of eg. 7 = last 7 days
 *          NOT USED AT MOMENT DURING TESTING
 * @param initFlag A flag to initialize static data set back to empty.
 * @return The rolling total for i periods.
 */
private double rollingTotal(double d, boolean initFlag) {
    // Initialize running total array eg. for new Employyes
    if (initFlag) {
        runningTotal = null;
    }
    else {
        // move d+1 back to d eg. element 6 becomes element 5
        for (int x = 0; x< 6 ; x++) {
            runningTotal[x] = runningTotal[x+1];
        }
        // Put current datum point at end of array.
        runningTotal[6]= d;
    }
    // Always return sum of array when this method is called.
    double myTotal = 0.0;
    for (int x = 0; x<7; x++) {
        myTotal+= runningTotal[x];
    }
    System.err.print(Arrays.toString(runningTotal)+ '\n' );
    return myTotal;
}

Мой вопрос: это разумный подход к проектированию или есть что-то ослепительно очевидное и простое для выполнения этой задачи?Спасибо, ребята

Ответы [ 7 ]

5 голосов
/ 30 августа 2011

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

Например:

// assume that currentIndex is where you want to add the new item
// You have another value, currentTotal, that is initialized at 0.
currentTotal = currentTotal - runningTotal[currentIndex] + d;
runningTotal[currentIndex] = d;
// increment the index.
currentIndex = (currentIndex + 1) % 7;

При этом используется кольцевой буфер и сохраняется currentTotal, чтобы он всегда был доступен.

4 голосов
/ 30 августа 2011

Я бы сказал, используй очередь, вставляй новое и вставляй старое.Для отслеживания среднего значения вы также можете просто вычесть полученное значение из промежуточного итога и добавить новое (вам понадобится статическая переменная или переменная экземпляра или для передачи старой суммы).Нет необходимости получать доступ к остальным элементам.Кроме того, где выполняется инициализация runningTotal, если нет, когда initFlag имеет значение true?

private double rollingTotal(double d, boolean initFlag) {
    if(initFlag) vals = new Queue<Integer>();
    else {
        if(vals.size() == 7) // replace 7 with i.
            total -= vals.pop().intValue();
        }
        vals.push(d);
        total += d;
    }
    return total;
}

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

2 голосов
/ 30 августа 2011

Было бы проще использовать ArrayList вместо массива.Тогда вы можете просто использовать

ArrayList<Double> runningTotal = new ArrayList<Double>();

....

runningTotal.remove(0);
runningTotal.add(d);
2 голосов
/ 30 августа 2011

Вы можете использовать экспоненциально-взвешенное скользящее среднее . Писать довольно долго, но для сравнения код тривиален. Это также дает более плавные результаты.

double previous;
static final double DAY = 1.0;
static final double WEEK = 6.0;
static final double ALPHA = DAY/WEEK;

private double movingAverage(double d) {
    return previous = ALPHA * d + (1 - ALPHA) * previous ;
}

Примечание: это оптимизированная версия формулы

double previous;
static final double DAY = 1.0;
static final double WEEK = 6.0;
static final double ALPHA = 1 - Math.exp(-DAY/WEEK);

private double movingAverage(double d) {
    return previous = ALPHA * d + (1 - ALPHA) * previous ;
}

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

2 голосов
/ 30 августа 2011

Вы можете попробовать использовать циклический буфер вместо перемещения всех данных при каждом добавлении:

runningTotal[nextIndex] = d;
nextIndex+=1;
if (nextIndex>=7) nextIndex = 0;

Так что nextIndex всегда указывает на самый старый элемент данных. Вы можете суммировать от начала до конца, как и раньше.

1 голос
/ 30 августа 2011

Почему вы инициализируете runningTotal в ноль? Каков его тип? Где это заявлено? Было бы хорошо, если бы вы поместили несколько примеров кода, которые напоминают реальный код Java.

Если двигаться дальше, моя критика будет следующей: ваша функция делает слишком много. Функция или метод должны быть связными. Точнее, они должны делать одно и только одно.

Что еще хуже, что происходит в цикле for, когда x = 5? Вы копируете runningTotal[6] в runningTotal[5], но затем у вас есть две копии одного и того же значения в позициях 5 и 6.

В вашем дизайне ваша функция

  1. перемещает / перетасовывает элементы в вашем массиве
  2. рассчитывает сумму
  3. печатает материал со стандартной ошибкой
  4. возвращает сумму

Это слишком много.

Мое первое предложение - не перемещать вещи в массиве. Вместо этого реализуйте кольцевой буфер и используйте его вместо массива. Это упростит ваш дизайн. Мое второе предложение состоит в том, чтобы разбить вещи на функции, которые являются связными:

  1. имеет структуру данных (кольцевой буфер), которая позволяет вам добавлять к ней (и удаляет самую старую запись, когда она достигает своей емкости).
  2. имеет структуру данных для реализации интегратора
  3. имеет функцию, которая вычисляет сумму на итераторе (вам все равно, вычисляете ли вы сумму из массива, списка или кругового буфера.)
  4. не называйте это всего. Назовите это сумма, которую вы вычисляете.

Вот что я бы сделал :) 1038 *

// java pseudocode below - might not compile.

// assume you have a class called CircularBuffer, of say, doubles,
public class CircularBuffer
{
  public CircularBuffer(final int capacity) {...}
  public int getSize(){ ... return # of elements in it ... }
  public add(final Double d){ ... add to the end, drop from the front if we reach capacity... }
  public Iterator<Double> iterator(){ ... gets an interator over the content of the buffer ...}
}

// somewhere else, in another class... NOT ON CircularBuffer

public class Calculator
{
  //assume none of the double values is null
  static public Double sum(final Double ... doubles )
  {
    double sum= 0;
    for( Double d : doubles )
    {
      total += d.doubleValue();
    }
    return sum;
  }

 // you can calculate other things too
 static public Double avg(final Double ... doubles ){...}
 static public Double std(final Double ... doubles ){...}
}

/// somewhere else
{
  CircularBuffer buffer = new CircularBuffer(7);

  while( readingAndReadingAndReading )
  {
    // drops oldest values as it reaches capacity
    // always keeping the latest 7 readings
    buffer.add( getLatestValueFromSomewhere() );
  }

  System.out.println( "total=" + Calculator.sum() );
  System.out.println( "average=" + Calculator.avg() );
  System.out.println( "standard deviation=" + Calculator.std() );
}
0 голосов
/ 30 августа 2011

Ваша задача слишком проста, и принятый вами подход, безусловно, хорош для этой работы.Однако, если вы хотите использовать лучший дизайн, вы должны избавиться от всего этого движения чисел;вам лучше использовать очередь FIFO и хорошо использовать методы push и pop;таким образом, код не будет отражать любое перемещение данных, только два логических действия: «новые данные» и «удаление данных старше 7 дней».

...