java 移动平均/总算法

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/7244880/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-30 19:15:22  来源:igfitidea点击:

Moving average/total algorithm

javaalgorithmmoving-average

提问by Pete855217

I need to keep track of the last 7 days work hours in a flat file reading loop. It's being used to measure 'fatigueability' of work rosters.

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

Right now I have something that works, but it seems rather verbose and I'm not sure whether there's a pattern that's more succinct.

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

Currently, I have a Java class with a static array to hold the last x days data, then as I read through the file, I chop off the first element and move the other 6 (for a week rolling total) back by one. The processing of this static array is done in its own method ie.

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

My question: is this a reasonable design approach, or is there something blindingly obvious and simple to do this task? Thanks guys

我的问题:这是一种合理的设计方法,还是有一些非常明显和简单的方法可以完成这项任务?多谢你们

回答by Jim Mischel

That certainly works, but you're doing a little more work than you have to. You can avoid moving all that data around, and you can set it up so computing the next total is a matter of subtracting the oldest value, and adding the new value.

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

For example:

例如:

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

This uses a circular buffer and keeps the currentTotalso that it's always available.

这使用一个循环缓冲区并保持currentTotal它始终可用。

回答by Kevin

I'd say use a queue and push the new and pop the old. For keeping track of the average, you could also just subtract the popped value from the running total and add the new one (you'd need a static or instance variable or to pass the old sum in). No need to access the rest of the elements. Also, where is runningTotal being initialized if not when the initFlag is true?

我会说使用队列并推送新的并弹出旧的。为了跟踪平均值,您还可以从运行总数中减去弹出的值并添加新的值(您需要一个静态或实例变量或传递旧的总和)。无需访问其余元素。另外,如果 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;
}

I believe Queue is abstract, so you'll need to figure out which implementation to use. I suggest a linked-list-based one.

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

回答by JCooper

You might try using a circular buffer instead of moving all the data with every addition:

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

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

So nextIndexis always pointing to the oldest datum. You can still sum from the beginning to the end as before.

所以nextIndex总是指向最旧的数据。您仍然可以像以前一样从头到尾求和。

回答by Peter Lawrey

You could use an exponential weighted moving average. Its rather long to write but the code is trivial by comparison. It tends to give smoother results as well.

您可以使用指数加权移动平均线。写起来相当长,但相比之下,代码是微不足道的。它也倾向于提供更平滑的结果。

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

Note: this is an optimized version of the formula

注意:这是公式的优化版本

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

In this case, the later formula is more accurate and as alpha doesn't change the overhead of Math.expisn't important. If alpha can change, and is typically small, I suggest using the first formula.

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

回答by mamboking

It would be easier to use an ArrayList instead of an array. Then you could just use

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

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

....

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

回答by luis.espinal

Why do you initialize runningTotalto null? What is its type? Where it is declared? It would do well if you put some code samples that resemble actual Java code.

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

Moving on, my critique would be the following: your function does too much. A function, or method, should be cohesive. More appropriately, they should do one thing and one thing only.

继续,我的批评如下:你的功能做得太多了。一个函数或方法应该是内聚的。更恰当地说,他们应该做一件事,只做一件事。

Worse still, what happens in your for loop when x = 5? You copy runningTotal[6]into runningTotal[5], but then you have two copies of the same value at position 5 and 6.

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

In your design, your function

在您的设计中,您的功能

  1. moves/shuffles the items in your array
  2. calculates the total
  3. prints stuff to standard error
  4. returns the total
  1. 移动/洗牌阵列中的项目
  2. 计算总数
  3. 将东西打印到标准错误
  4. 返回总数

It does too much.

它做得太多了。

My first suggestion is not to move stuff around in the array. Instead, implement a circular bufferand use it instead of the array. It will simplify your design. My second suggestion is to break down things into functions that are cohesive:

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

  1. have a data structure (a circular buffer) that allows you to add to it (and that drops the oldest entry whenever it reaches its capacity.)
  2. have the data structure implement an interator
  3. have a function that calculates the total on the iterator (you don't care if you are calculating the total out of an array, list or circular bufer.)
  4. don't call it total. Call it sum, which is what you are computing.
  1. 有一个数据结构(一个循环缓冲区),允许您向其中添加(并且在达到其容量时删除最旧的条目。)
  2. 让数据结构实现一个interator
  3. 有一个计算迭代器总数的函数(你不关心你是在计算数组、列表还是循环缓冲区中的总数。)
  4. 不要称之为全部。称之为总和,这就是你正在计算的。

That's what I'd do :)

这就是我要做的:)

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

回答by daniloquio

Your task is too simple and the aproach you have adopted is certainly good for the job. However, if you want to use a better design, you must get rid of all that number movement; you better use a FIFO queue and make good use of push and pop methods; that way the code wont reflect any data movement, just the two logic actions of "new data" and "remove data older than 7 days".

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