计算字节数组中位数总和的最快方法

时间:2010-11-18 19:38:43

标签: c# arrays byte bit

我有两个长度相同的字节数组。我需要在每个字节之间执行XOR运算,然后计算位数之和。

例如:

11110000^01010101 = 10100101 -> so 1+1+1+1 = 4

我需要对字节数组中的每个元素执行相同的操作。

9 个答案:

答案 0 :(得分:11)

使用查找表。 XORing后只有256个可能的值,所以它不会花费很长时间。与izb的解决方案不同,我不建议手动输入所有值 - 在启动时使用其中一个循环答案计算查找表一次

例如:

public static class ByteArrayHelpers
{
    private static readonly int[] LookupTable =
        Enumerable.Range(0, 256).Select(CountBits).ToArray();

    private static int CountBits(int value)
    {
        int count = 0;
        for (int i=0; i < 8; i++)
        {
           count += (value >> i) & 1;
        }
        return count;
    }

    public static int CountBitsAfterXor(byte[] array)
    {
        int xor = 0;
        foreach (byte b in array)
        {
            xor ^= b;
        }
        return LookupTable[xor];
    }
}

(你可以使它成为一种扩展方法,如果你真的想...)

请注意byte[]方法中CountBitsAfterXor的使用 - 您可以使其成为IEnumerable<byte>以获得更多的通用性,但迭代数组(这是在编译时已知是一个数组)会更快。可能只是在显微镜下更快,但是,嘿,你要求最快的方式:)

我几乎肯定实际将其表达为

public static int CountBitsAfterXor(IEnumerable<byte> data)

在现实生活中,但看哪哪个更适合你。

另请注意xor变量的类型为int。事实上,没有为byte值定义XOR运算符,如果你使xor成为byte,由于复合赋值运算符的性质,它仍会编译,但它会执行在每次迭代中投射 - 至少在IL中。 JIT很可能会解决这个问题,但是没有必要甚至要求它:)

答案 1 :(得分:9)

最快的方式可能是256个元素的查找表...

int[] lut
{
    /*0x00*/ 0,
    /*0x01*/ 1,
    /*0x02*/ 1,
    /*0x03*/ 2
    ...
    /*0xFE*/ 7,
    /*0xFF*/ 8
}

e.g。

11110000^01010101 = 10100101 -> lut[165] == 4

答案 2 :(得分:6)

这通常被称为比特计数。实际上有几十种不同的算法。 Here是一个列出一些更为人熟知的方法的网站。甚至还有CPU特定的指令来执行此操作。

从理论上讲,Microsoft可以添加一个BitArray.CountSetBits函数,该函数可以使用针对该CPU架构的最佳算法进行JITed。举个例子,我会欢迎这样的补充。

答案 3 :(得分:3)

据我所知,你想要对左右字节之间每个XOR的位进行求和。

for (int b = 0; b < left.Length; b++) {
  int num = left[b] ^ right[b];
  int sum = 0;

  for (int i = 0; i < 8; i++) {
    sum += (num >> i) & 1;
  }

   // do something with sum maybe?
}

答案 4 :(得分:2)

我不确定你的意思是总和字节还是比特。 要对一个字节内的位求和,这应该有效:

int nSum = 0;
for (int i=0; i<=7; i++)
{
   nSum += (byte_val>>i) & 1;
}

然后你需要xoring,并且当然要围绕它进行数组循环。

答案 5 :(得分:1)

以下内容应该

int BitXorAndSum(byte[] left, byte[] right) {
  int sum = 0;
  for ( var i = 0; i < left.Length; i++) { 
    sum += SumBits((byte)(left[i] ^ right[i]));
  }
  return sum;
}

int SumBits(byte b) {
  var sum = 0;
  for (var i = 0; i < 8; i++) {
    sum += (0x1) & (b >> i);
  }
  return sum;
}

答案 6 :(得分:1)

可以将其重写为ulong并使用unsafe指针,但byte更容易理解:

static int BitCount(byte num)
{
    // 0x5 = 0101 (bit) 0x55 = 01010101
    // 0x3 = 0011 (bit) 0x33 = 00110011
    // 0xF = 1111 (bit) 0x0F = 00001111
    uint count = num;
    count = ((count >> 1) & 0x55) + (count & 0x55);
    count = ((count >> 2) & 0x33) + (count & 0x33);
    count = ((count >> 4) & 0xF0) + (count & 0x0F);
    return (int)count;
}

答案 7 :(得分:0)

计算位的一般函数可能如下所示:

int Count1(byte[] a)
{
  int count = 0;
  for (int i = 0; i < a.Length; i++)
  {
    byte b = a[i];
    while (b != 0)
    {
      count++;
      b = (byte)((int)b & (int)(b - 1));
    }
  }
  return count;
}

1位越少,效果越快。它只是循环遍历每个字节,并切换该字节的最低1位,直到字节变为0.必须使用强制转换,以便编译器停止抱怨类型扩展和缩小。

然后可以使用以下方法解决您的问题:

int Count1Xor(byte[] a1, byte[] a2)
{
  int count = 0;
  for (int i = 0; i < Math.Min(a1.Length, a2.Length); i++)
  {
    byte b = (byte)((int)a1[i] ^ (int)a2[i]);
    while (b != 0)
    {
      count++;
      b = (byte)((int)b & (int)(b - 1));
    }
  }
  return count;
}

答案 8 :(得分:0)

查找表应该是最快的,但是如果你想在没有查找表的情况下这样做,这将只适用于10个操作中的字节。

public static int BitCount(byte value) {
    int v = value - ((value >> 1) & 0x55);
    v = (v & 0x33) + ((v >> 2) & 0x33);
    return ((v + (v >> 4) & 0x0F));
}

这是Sean Eron Anderson's bit fiddling site所述的通用位计数功能的字节版本。