通过一次传递存储数组块的总和

本文关键字:数组 存储 一次 | 更新日期: 2023-09-27 18:16:47

假设我有一个数组

1,2,3,4,5,6,7,8,9,10,11,12

如果我的chunk size = 4

那么我希望有一个方法可以输出一个int数组int[] a =

a[0] = 1
a[1] = 3
a[2] = 6
a[3] = 10
a[4] = 14
a[5] = 18
a[6] = 22
a[7] = 26
a[8] = 30
a[9] = 34
a[10] = 38
a[11] = 42

注意a[n] = a[n] + a[n-1] + a[n-2] + a[n-3],因为块大小是4,所以我对最后4项求和

我需要有方法without嵌套循环

 for(int i=0; i<12; i++)
 {
     for(int k = i; k>=0 ;k--)
     {
         // do sumation
         counter++;
         if(counter==4)
           break;
     }
 }
例如,

我不想有这样的东西…为了使代码更高效

块大小也可能改变,所以我不能这样做:

a[3] = a[0] + a[1] + a[2] + a[3]

编辑

我问这个问题的原因是因为我需要为我的数据结构类实现校验和滚动。我基本上是打开一个文件来阅读。然后我就有了一个字节数组。然后我将对文件的部分执行哈希函数。假设这个文件是100字节。我把它分成10个字节的块。我对每个块执行一个哈希函数,因此我得到10个哈希值。然后我需要将这些哈希值与另一个类似的文件进行比较。假设第二个文件有同样的100个字节,但增加了5个字节,所以它总共包含105个字节。因为这些额外的字节可能在文件的中间如果我对第一个文件执行相同的算法它将不起作用。希望我的解释正确。因为有些文件很大。在我的算法中使用嵌套循环是不高效的。

实际上滚动哈希函数是非常复杂的。它们大多数都是用c++编写的,我很难理解它们。这就是为什么我想创建自己的哈希函数,非常简单,只是为了演示校验和滚动是如何工作的…

编辑2

        int chunckSize = 4;
        int[] a = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12 }; // the bytes of the file
        int[] b = new int[a.Length]; // array where we will place the checksums
        int[] sum = new int[a.Length]; // array needed to avoid nested loop
        for (int i = 0; i < a.Length; i++)
        {
            int temp = 0;
            if (i == 0)
            {
                temp = 1;
            }
            sum[i] += a[i] + sum[i-1+temp];
            if (i < chunckSize)
            {
                b[i] = sum[i];
            }
            else
            {
                b[i] = sum[i] - sum[i - chunckSize];
            }
        }

这个算法的问题是,对于大文件,总和在某一点上将大于int。最大,因此它不会工作....

但至少知道它更有效。摆脱嵌套循环帮助很大!

3

编辑

根据编辑二,我已经算出来了。它不适用于大文件,而且校验和算法非常糟糕。但至少我认为它解释了我试图解释的哈希滚动…

    Part1(@"A:'fileA.txt");
    Part2(@"A:'fileB.txt", null);

    // split the file in chuncks and return the checksums of the chuncks
    private static UInt64[] Part1(string file)
    {
        UInt64[] hashes = new UInt64[(int)Math.Pow(2, 20)];
        var stream = File.OpenRead(file);

        int chunckSize = (int)Math.Pow(2, 22); // 10 => kilobite   20 => megabite  30 => gigabite etc..
        byte[] buffer = new byte[chunckSize];
        int bytesRead;    // how many bytes where read
        int counter = 0;  // counter
        while ( // while bytesRead > 0
                    (bytesRead =
                        (stream.Read(buffer, 0, buffer.Length)) // returns the number of bytes read or 0 if no bytes read
                    ) > 0)
        {                
            hashes[counter] = 0;
            for (int i = 0; i < bytesRead; i++)
            {
                hashes[counter] = hashes[counter] + buffer[i]; // simple algorithm not realistic to perform check sum of file                    
            }
            counter++;
        }// end while loop     
        return hashes;
    }

    // split the file in chuncks rolling it. In reallity this file will be on a different computer..       
    private static void Part2(string file, UInt64[] hash)
    {            
        UInt64[] hashes = new UInt64[(int)Math.Pow(2, 20)];
        var stream = File.OpenRead(file);
        int chunckSize = (int)Math.Pow(2, 22); // chunks must be as big as in pervious method
        byte[] buffer = new byte[chunckSize];
        int bytesRead;    // how many bytes where read
        int counter = 0;  // counter
        UInt64[] sum = new UInt64[(int)Math.Pow(2, 20)];
        while ( // while bytesRead > 0
                    (bytesRead =
                        (stream.Read(buffer, 0, buffer.Length)) // returns the number of bytes read or 0 if no bytes read
                    ) > 0)
        {
            for (int i = 0; i < bytesRead; i++)
            {
                int temp = 0;
                if (counter == 0)
                    temp = 1;
                sum[counter] += (UInt64)buffer[i] + sum[counter - 1 + temp];
                if (counter < chunckSize)
                {
                    hashes[counter] = (UInt64)sum[counter];
                }else
                {
                    hashes[counter] = (UInt64)sum[counter] - (UInt64)sum[counter - chunckSize];
                }
                counter++;                    
            }

        }// end while loop
        // mising to compare hashes arrays
    }

通过一次传递存储数组块的总和

为结果添加一个数组r,并使用从0到chunk-1的循环初始化它的第一个chunk成员。现在观察到,要得到r[i+1],您可以将a[i+1]加到r[i]上,然后减去a[i-chunk+1]。现在,您可以在一个非嵌套循环中完成其余项:

for (int i=chunk+1 ; i < N-1 ; i++) {
    r[i+1] = a[i+1] + r[i] - a[i-chunk+1];
}

您可以将其简化为单个for循环,尽管这可能不够好。为此,只需注意c[i+1] = c[i]-a[i-k+1]+a[i+1];,其中a是原始数组,c是块数组,k是块的大小。

我知道你想要计算一个滚动哈希函数来哈希每个n-gram(其中n是你所说的"块大小")。滚动哈希有时被称为"递归哈希"。维基百科上有关于这个主题的条目:

http://en.wikipedia.org/wiki/Rolling_hash

解决这个问题的一个常用算法是Karp-Rabin。下面是一些伪代码,你应该能够很容易地在c#中实现:

B←37
s←empty First-In-First-Out (FIFO) structure (e.g., a linked-list)
x←0(L-bit integer) 
z←0(L-bit integer) 
for each character c do
  append c to s
  x ← (B x−B^n z + c ) mod 2^L
  yield x
  if length(s) = n then
     remove oldest character y from s 
     z ← y
  end if 
end for

注意,因为B^n是一个常量,主循环只做两次乘法,一次减法和一次加法。"mod 2^L"操作可以非常快地完成(例如,使用掩码,或L=32或L=64的无符号整数)。

具体来说,你的c#代码可能看起来像这样,其中n是"块"大小(只需设置B=37, Btothen =37 ^ n)

r[0] = 0
for (int i=1 ; i < N ; i++) {
    r[i] = a[i] + B * r[i-1] - Btothen * a[i-n];
}
然而,Karp-Rabin并不理想。我写了一篇论文,讨论了更好的解决方案:

Daniel Lemire和Owen Kaser,递归n-gram哈希是两两独立的,至多,计算机语音&;语言24(4),698-710页,2010。http://arxiv.org/abs/0705.4676

我还发布了源代码(Java和c++,唉,没有c#,但从Java到c#应该不难):

https://github.com/lemire/rollinghashjava

https://github.com/lemire/rollinghashcpp

如何在您的步骤中存储最后的chunk_size值?

分配一个大小为chunk_size的数组,将它们全部设置为零,然后在i的每次迭代中将当前元素设置为i % chunk_size,然后将所有值相加?

using System;
class Sample {
    static void Main(){
        int chunckSize = 4;
        int[] a = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 };
        int[] b = new int[a.Length];
        int sum = 0;
        int d = chunckSize*(chunckSize-1)/2;
        foreach(var i in a){
            if(i < chunckSize){
                sum += i;
                b[i-1]=sum;
            } else {
                b[i-1]=chunckSize*i -d;
            }
        }
        Console.WriteLine(String.Join(",", b));//1,3,6,10,14,18,22,26,30,34,38,42
    }
}