【问题标题】:Fastest way to get a bit-wise median byte?获得按位中位字节的最快方法?
【发布时间】:2015-05-20 00:19:46
【问题描述】:

鉴于我有一大组 n 字节,生成一个字节的最快方法是什么,该字节是该组的“平均值”或“按位中位数”?

更具体地说,如果大多数字节都设置了该位,我希望得到一个结果字节,其中设置了每个位。

示例(带半字节)

Bytes: 
1000
1111
1010

Result: 
1010

我确信有一些小魔法可以帮助我做到这一点,但到目前为止我还没有找到它。到目前为止,我唯一的想法是天真的方法。有什么想法吗?

编辑:如果位的出现次数相等,则可以任意设置该位。

编辑 2: 为了示例,我在这里使用了字节。优选地,建议的方法应该适用于高达 ~128 字节的字节向量。

【问题讨论】:

  • @Austin:他对“平均”的定义在问号后面解释
  • 正确。也许另一个术语会更好? “按位中位数”?
  • 啊错过了。是的,我认为中位数可能是一个更合适的词

标签: binary bit-manipulation byte bit


【解决方案1】:

有一个有点玩弄的方法,但它不是那么漂亮(也不是特别快)。但无论如何我都会解释它,因为你问过,这很有趣。

这个想法是,不是将每个位位置的计数保存在整数中,而是将计数器的位保存在整数中。因此,如果您将计数器视为一个布尔矩阵,每一行都是一个计数器,请将 存储为整数。这样,输入数字是一种奇怪的加法,而不是“与位一样多的增量”。像这样(未测试)(添加c

for (int i = 0; i < counter_bits; i++) {
    counter[i] ^= c;
    c &= ~counter[i]; // counter & c == ~(counter ^ c) & c
}

现在有趣的部分是:中位数是多少?好吧,如果 n(项目数)是 2 减一的幂,而 counter 是一个长度正好足以“垂直”表达 n 的数组,那么中位数就是“高位”计数器(只要看到一个位至少 (n+1)/2 次就会出现 1)。

更有趣的情况是“否则”。它仍然是可修复的,要让它再次正常工作所需要的只是用一个偏差初始化计数器,以便在正确的时刻准确设置最高位。例如,如果n = 5,那么计数器应该初始化为1(垂直1,所以counter[0] = -1,所有其他计数器都是0),所以当添加3个时,它会变为4,即在这种情况下最高位。另一个例子:如果n = 17,那么最高位的权重应该是 16,但是 9 足以在中位数中设置一个位,所以计数器应该初始化为 7(所以,counter[0] = counter[ 1] = 计数器[2] = -1,其余为 0)。

这种方法显然可以以一种简单的方式推广到更宽的位向量,因为对变得更宽的事物的所有操作都是逐点位操作。


示例(以防万一有人对该算法中发生的事情感到困惑)

输入:

1000
1111
1010

3 个项目,大多数需要 2 个,我们数到最多 3 个,所以计数中只有 2 位(权重 1 和 2),不需要偏差。

init: counter = { 0000, 0000 }
put in 1000
counter = { 1000, 0000 }
put in 1111
counter = { 0111, 1000 } (the leftmost bit carried into the high counter)
put in 1010
counter = { 1101, 1010 } (the second bit from the right carried into the high counter)
result: the upper counter, so 1010

现在,在现实世界中,我不会这样做。根据情况,我可能会这样做:

有一个表(或pdep,如果有的话)将字节映射到“展开”版本,例如10010101b -&gt; 0x10010101,然后添加这些(通过正常添加),然后从半字节的高位(pext 如果你有它,否则它会更棘手)。偏见技巧仍然有效。缺点:仅适用于n &lt; 16。尽管存在巨大的劣势,但我仍然在现实生活中使用它(对于二元谜题,修剪搜索空间)。当然,这仍然适用于“更多的传播”,这会给你一个更高的最大值n(例如,将 8 个计数器放在 uin64 中得到 n &lt; 256,将 4 个放在 uint64 中得到 n &lt; 65536 等等在)。使用 LeleDumbo 的答案中的数组,这真的是同构的,除了数组是在一个单一的 int 中(事实上,如果你把传播提高到 11,它就变得完全相同)。这也可以扩展到非常大的向量(只需在数组中使用其中几个计数器)。

或者:使用正确的 SIMD,而不是这个假 SIMD。使提取位更容易(因为可以提取所有最高位的掩码)。它甚至不需要偏置技巧,因为您可以进行 SIMD 比较。它还使传播更容易,因为它不必使用查找表来完成 - 将项目广播到所有通道,屏蔽每个通道中的适当位(这很方便忽略了一些细节),比较然后 减去 这个来自计数(因为现在它是 -1 时为真,而不是 1)。例如(未测试)

vpbroadcastb xmm0, [item]   ; put 8bit item in all lanes
vpand xmm0, [lane_bit_mask] ; { 1, 2, 4, 8, ...
vpcmpeqb xmm0, [lane_bit_mask] ; -1 if the bit is set
vpsubb xmm1, xmm0           ; subtract from total

这有点浪费,只使用了 16 条(或 32 条)车道中的 8 条,但它显示了基本思想。使用更多通道时,需要花费更多精力来解包。

【讨论】:

    【解决方案2】:

    一个字节只有 8 位。由于它足够小,您可以使用降序基数排序(LSB first)策略:

    // this assumes 0 based indexing with LSB at index 0
    for i := 0 to 7
      initialize bucket 0 and 1 with 0
      for j := 0 to N - 1
        increment bucket[bit i of byte j]
      bit i of resulting byte = max(bucket[0],bucket[1]) ? 0 : 1
    

    这是 O(8 * N) = O(N) 的时间复杂度,应该很快。

    P.S.:如果 0 和 1 的出现次数相等,您还没有说明会发生什么

    【讨论】:

      猜你喜欢
      • 2021-01-05
      • 1970-01-01
      • 2021-08-27
      • 2013-03-13
      • 1970-01-01
      • 1970-01-01
      • 2015-03-03
      • 2022-12-24
      相关资源
      最近更新 更多