2

我需要在平面文件读取循环中跟踪过去 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;
}

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

4

7 回答 7

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它始终可用。

于 2011-08-30T14:48:14.440 回答
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 是抽象的,所以你需要弄清楚要使用哪个实现。我建议一个基于链表的。

于 2011-08-30T14:46:57.263 回答
3

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

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

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

于 2011-08-30T14:42:42.673 回答
3

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

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 可以改变,并且通常很小,我建议使用第一个公式。

于 2011-08-30T14:43:30.593 回答
2

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

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

....

runningTotal.remove(0);
runningTotal.add(d);
于 2011-08-30T14:46:31.993 回答
1

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

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

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

在您的设计中,您的功能

  1. 移动/打乱数组中的项目
  2. 计算总数
  3. 将内容打印到标准错误
  4. 返回总数

它做得太多了。

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

  1. 有一个数据结构(一个循环缓冲区),允许您添加到它(并且只要它达到其容量,它就会丢弃最旧的条目。)
  2. 让数据结构实现一个交互器
  3. 有一个计算迭代器总数的函数(你不在乎你是在计算数组、列表还是循环缓冲区的总数。)
  4. 不要称之为总。称其为总和,这就是您正在计算的内容。

这就是我会做的:)

// 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() );
}
于 2011-08-30T15:03:55.500 回答
0

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

于 2011-08-30T14:49:44.283 回答