移动平均/总算法

时间:2011-08-30 14:33:02

标签: java algorithm moving-average

我需要在平面文件读取循环中跟踪最近7天的工作时间。它被用来衡量工作名单的“疲劳性”。

现在我有一些有用的东西,但它似乎相当冗长,我不确定是否有一种更简洁的模式。

目前,我有一个带有静态数组的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 个答案:

答案 0 :(得分:5)

这当然有效,但你做的工作比你要多一些。您可以避免移动所有数据,并且可以对其进行设置,以便计算下一个总数是减去最旧值并添加新值。

例如:

// 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,以便它始终可用。

答案 1 :(得分:4)

我会说使用一个队列并推送新的并弹出旧的。为了跟踪平均值,您还可以从运行总计中减去弹出值并添加新值(您需要一个静态或实例变量或传递旧的总和)。无需访问其余元素。另外,如果initFlag为真,那么runningTotal在哪里被初始化?

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;
}

我相信Queue是抽象的,所以你需要弄清楚要使用哪种实现。我建议使用基于链表的。

答案 2 :(得分:2)

您可以尝试使用循环缓冲区,而不是每次添加都移动所有数据:

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

所以nextIndex始终指向最早的基准面。你仍然可以像以前一样从头到尾求和。

答案 3 :(得分:2)

您可以使用exponential weighted moving average。它写得相当长,但相比之下,代码是微不足道的。它也倾向于给出更平滑的结果。

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 ;
}

在这种情况下,后面的公式更准确,因为alpha不会改变Math.exp的开销并不重要。如果alpha可以改变,并且通常很小,我建议使用第一个公式。

答案 4 :(得分:2)

使用ArrayList而不是数组会更容易。然后你可以使用

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

....

runningTotal.remove(0);
runningTotal.add(d);

答案 5 :(得分:1)

为什么要将runningTotal初始化为null?它的类型是什么?声明的地方?如果您放置一些类似于实际Java代码的代码示例,它会很好。

继续,我的批评将如下:你的功能做得太多了。功能或方法应该具有凝聚力。更恰当的是,他们应该做一件事,一件事。

更糟糕的是,当x = 5时,你的for循环会发生什么?您将runningTotal[6]复制到runningTotal[5],但在第5和第6位有两个相同值的副本。

在您的设计中,您的功能

  1. 移动/随机播放数组中的项目
  2. 计算总数
  3. 将内容打印为标准错误
  4. 返回总数
  5. 它做得太多了。

    我的第一个建议是不要在阵列中移动东西。相反,实现circular buffer并使用它而不是数组。它将简化您的设计。我的第二个建议是将事情分解为具有凝聚力的功能:

    1. 有一个数据结构(一个循环缓冲区),允许你添加它(当它达到容量时,它会丢弃最旧的条目。)
    2. 让数据结构实现一个interator
    3. 有一个计算迭代器总数的函数(你不在乎计算数组,列表或循环缓冲区的总数。)
    4. 不要总称它。称之为总和,这就是你在计算的东西。
    5. 这就是我要做的事情:)。

      // 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() );
      }
      

答案 6 :(得分:0)

你的任务太简单了,你所采用的方法肯定对这份工作有好处。但是,如果你想使用更好的设计,你必须摆脱所有数字运动;你最好使用FIFO队列并充分利用push和pop方法;这样代码就不会反映任何数据移动,只是“新数据”和“删除超过7天的数据”的两个逻辑操作。