运行数组中最后n个整数的总和

时间:2014-07-29 20:06:28

标签: c algorithm

假设进程每60秒收到一个新的整数。我想保留最后5个数字的总计。例如:

3 1 99 10 8 0 7 9 --> running total is 10+8+0+7+9==34
       <--------->

60秒后,我们收到一个新的整数。收到的整数列表现在看起来像这样:

3 1 99 10 8 0 7 9 2 --> running total is now 8+0+7+9+2==26
          <-------->

如果您有存储空间来保存最后5个整数,那么实现它很容易。我试图提出一种比这更有效的内存算法。有没有人有任何想法?

7 个答案:

答案 0 :(得分:22)

由于您可以重建最后n个数字,例如,如果您输入n个零,则您执行的任何操作都相当于存储最后n个数字。

假设数字可以是真正随机的并且每个数字是b位长,则任何正确的算法因此可以精确地再现nb个随机位。这需要至少nb位存储。

答案 1 :(得分:10)

我认为你无法解决这个问题。

对于最近两个最近整数的运行总和,必须至少存储第一个整数和当前运行总和,以重建第二个(或最后一个)整数。这意味着存储两个整数。

给出第一个整数:

  

一个<子> 1

最后两个索引 i j 的运行总和 s i,j 可以迭代计算作为整数 a 2 等等进入流中,重用以前的运行总和:

  

s 1,2 = a 1 + a 2

     

s 2,3 = s 1,2 - a 1 + a 3

     

s 3,4 = s 2,3 - (s 1,2 - a 1 ) + a 4

     

s 4,5 = s 3,4 - (s 2,3 - (s 1,2 - a 1 ))+ a 5

     

...

等等,以递归的方式。

如您所见,两个整数的运行总和至少需要 a 1 和运行总和 s i-2,i- 1 ,重建倒数第二个元素。

同样,对于最近三个最近整数的运行总和,必须至少存储前两个整数和当前运行总和,以重建第三个(或倒数第二个)整数。

给出第一个和第二个整数:

  

1 2

最后三个索引 i j 和<的运行总和 s i,j,k em> k 可以迭代计算为整数 a 3 ,然后进入流,重新使用之前的运行总和:

  

s 1,2,3 = a 1 + a 2 + a 3

     

s 2,3,4 = s 1,2,3 - a 1 + a 4

     

s 3,4,5 = s 2,3,4 - a 2 + a 5

     

s 4,5,6 = s 3,4,5 - (s 1,2,3 - a 1 - 2 )+ a 5

     

...

同样,您必须为运行总和存储尽可能多的整数,以便重建缺少的整数。通过归纳,你是否要消除任何一个变量,你将无法概括缺失值。

答案 2 :(得分:9)

  

为了讨论,我正在简化问题。在   练习,将有8000左右这样的名单,我需要保持   最后5,60和3600个元素的运行总和。

听起来你想要过去5秒,60秒和1小时的总数。

你真的需要你的60秒总计准确到秒吗?或者可以每5秒更新一次?同样,您是否需要每小时总计精确到秒,或者每分钟更新一次是否正常?

如果您不需要每分钟和每小时的总计精确到秒,那么您可以在存储上节省很多。在这种情况下,5 + 12 + 60 = 77,而不是3600

然后算法运行如下:

//these are the running totals that will be displayed
int last1 = 0;    //updated every second
int last5 = 0;    //updated every second
int last60 = 0;   //updated every 5 seconds
int last3600 = 0; //updated every minute

// 3 circular buffers:
// last 5 1-second periods (updated every second)
int period1[5] = {0};
// last 12 5-second periods (updated every 5 seconds)
int period5[12] = {0};
// last 60 1-minute periods (updated every minute)
int period60[60] = {0};

//indexes for the circular buffers
int index1 = 0;
int index5 = 0;
int index60 = 0;
while (1) {
    printf("1s 5s 1m 1h\n");
    printf("%2d %2d %2d %2d\n", last1, last5, last60, last3600);

    sleep(1);
    last1 = getNewValue();

    //update last5 by subtracting the expiring period and adding the new one
    last5 -= period1[index1];
    last5 += last1;
    //and save the new period to circular buffer
    period1[index1] = last1;
    index1++;

    //if we get to the end of the circular buffer we must go to the start
    //we have also completed a 5s period so we can update last60
    if (index1 >= 5) {
        index1 = 0;

        //similar to before
        last60 -= period5[index5];
        last60 += last5;

        period5[index5] = last5;
        index5++

        //similar to above, but now we have completed a 60s period
        //so we can update last3600
        if (index5 >= 12) {
            index5 = 0;

            //similar to before
            last3600 -= period60[index60];
            last3600 += last60;

            period60[index60] = last60;
            index60++

            if (index60 >= 60) {
                index60 = 0;
            }
        }
    }
}

正如您所看到的,只需要84个整数,并且不会进行循环,因此性能会很好。

如果您希望每秒更新60秒,而不是每5秒更新一次,则可以执行此操作。你也可以变得更加繁琐,例如:让每1小时的时间每20秒更新一次。但是,代码如此简洁的部分原因是每次完成每个句点时都会更新。

请注意,总计3600秒是使用最多内存的内存,因此您需要特别注意。

答案 3 :(得分:6)

我不相信你能做到这一点。您需要一个能够容纳最后n个值的滑动窗口。

关于您可以做的最好的事情是使用模运算来将数组视为循环缓冲区,保持运行总和并随时计数,以避免迭代整个缓冲区来计算值的总和。像这样:

#include <stdlib.h>
#include <string.h>
#include <stdio.h>

#define WINDOW_SIZE 5

static int   *window      ;
static int    i           ;
static double sum         ;
static double cnt         ;

double record_value( int value )
{
  double mean ;

  i          = (i+1) % WINDOW_SIZE ;
  sum        = sum - window[i] + value ;
  cnt       += cnt < WINDOW_SIZE ? 1 : 0 ;
  window[i]  = value ;

  mean = sum/cnt ;
  return mean ;
}

void log_message( double avg )
{
  int x = 0 ;

  printf( "%f = ( " , avg ) ;
  for ( int x = 0 ; x < cnt ; ++x )
  {
    printf( "%s%d" , x > 0 ? " + " : "" , window[x] ) ;
  }
  printf( " ) / %d\r\n" , (int)cnt ) ;
  return ;
}

int main( int argc, char* argv[] )
{
  int j ;

  window      = calloc( WINDOW_SIZE , sizeof(window[0]) ) ;
  i           = WINDOW_SIZE - 1 ;
  sum         = 0 ;
  cnt         = 0 ;

  for ( j = 0 ; j < 100 ; ++j )
  {
    int    v   = rand() ;
    double avg = record_value( v ) ;

    log_message( avg ) ;

  }

  return 0 ;
}

答案 4 :(得分:2)

如果你的输入有一些限制,也许有很多方法可以解决这个问题。

char占用1个字节。根据您的输入示例,如果您的整数是正值且长度小于三位, ie 在0到99之间,那么您可以通过将整数减少到char流来节省一些空间由分隔符分割。

给出如下数字流的尾随总和:

3 1 99 10 8 0 7 9

也许这可以简化为存储两个元素:最后五个元素作为常量realloc - 编辑char *,总和作为int

"10|8|0|7|9" (10 bytes)
34 (4 bytes)

这需要总共14个字节,比存储5个int值所需的20个字节少6个字节。

您需要编写代码来标记化并从char *中提取元素以重新计算总和,然后您realloc并在新元素进入和缓冲区长度时重写字符缓冲区变化,以便您始终最大限度地节省空间。

另请注意char *上缺少NULL终止符 - 您不希望将其视为字符串,以最大限度地提高存储效率。 NULL是一个浪费的字节。

您还需要仔细重写char *,这样您就不必在中间存储上浪费空间。对于非常大的char *,您可能会在四字节size_t上浪费空间来记录流的真正开始的偏移,这样您就不会浪费时间重写它,并且四-byte size_t值,以便您知道何时到达终点并需要环绕(或者您在NULL上浪费一个字节,并测试它)。

具有四个分隔符且没有NULL的五个一位或两位整数的流将需要 - 最多 - 16个字节,并且少至9个字节。存储为int的累积和将占用4个字节。最糟糕的情况是,您使用的存储空间与五个int变量相同。在最好的情况下,您使用的是13个字节 - 比最差情况少7个字节。

假设并非所有整数都是两位数,那么可能会看到一些空间节省。但是,假设从0到99的整数随机均匀流,您可以预期90%的随机数长度为两位数。因此,平均而言,大多数情况下,这可能会使用接近20个字节。

如果你真的想成为小气鬼,请将累积金额存储为三字节char *。最大总和(给定相同的约束)将是99 + 99 + 99 + 99 + 99 = 495.值"495"可以存储在三个字节中。所以这是额外的节省。

请注意,这并未考虑操作系统的字长以及可能填充数据结构等的其他优化。因此,这个非常简单且受限制的示例最终可能无法真正节省尽可能多的空间。 / p>

如果您正在处理非常大的流,请考虑使用块级压缩算法的类似方法,如bzip2或gzip。根据数据的规模,您可以获得比压缩开销损失更多的存储空间。您可能希望避免需要提取整个流以仅恢复第一个整数的编码方案。

答案 5 :(得分:1)

如果您不得不连续迭代新值,我认为您可以获得少于5个存储的变量。如果所有整数都很小,那么将所有5个值存储在一个更合适的类型(char)中是有意义的,它将使用比int更少的空间。

答案 6 :(得分:0)

让我们做一个cs风格的减少。

我将假设你的问题是可能的,并表明我们可以创建一个无损压缩算法,其输出总是比输入短。

压缩算法(以5字节块压缩):
将5个字节加在一起,存储在新的11位整数中。我猜我们可以使用2个整体字节。它还在压缩。

解压缩算法(需要2个字节,返回5个字节):
调用我们的运行总数,即2字节数。

在&#34;列表中添加0&#34; (引用因为没有列表。我们只有我们的总计)。将新运行总计与旧运行总计进行比较。区别在于第一个字节。

在列表中添加另一个0。再次比较。

再重复3次。你有5个字节。

从这里我们看到我们肯定需要额外的记忆。因为我们知道这种压缩算法是不可能的。