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

时间:2011-12-05 23:51:56

标签: c# algorithm

假设我有阵列

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

如果我的chunck 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;
     }
 }

例如,我不希望有类似的东西......为了提高代码效率

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

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

修改

我问这个问题的原因是因为我需要为我的数据结构类实现校验和滚动。我基本上打开一个文件进行阅读。然后我有一个字节数组。然后我将对文件的某些部分执行哈希函数。假设文件是​​100个字节。我把它分成10个字节的块。我在每个chunck中执行哈希函数,因此我得到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.Max大,因此它不会起作用....

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

编辑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
    }

5 个答案:

答案 0 :(得分:2)

你可以把它归结为一个for循环,虽然这可能不够好。要做到这一点,请注意c[i+1] = c[i]-a[i-k+1]+a[i+1];其中a是原始数组,c是粗块数组,k是块的大小。

答案 1 :(得分:2)

为结果添加数组r,并使用从0到chunk的循环初始化其第一个chunk-1成员。现在请注意,要获得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];
}

答案 2 :(得分:2)

我知道你想要计算一个滚动哈希函数来散列每个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

答案 3 :(得分:0)

如何逐步存储最后chunk_size个值?

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

答案 4 :(得分:0)

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