鉴于我有一大组n个字节,生成字节的最快方法是"平均值"或者可能是"逐位中位数"那套?
更具体地说,我希望得到一个结果字节,如果大多数字节都设置了该位,则每个位都会置位。
示例(带半字节)
Bytes:
1000
1111
1010
Result:
1010
我确定有一点点魔力可以帮助我做到这一点,但到目前为止我还没能找到它。到目前为止,我唯一的想法是天真的方法。有什么想法吗?
编辑:在比特出现次数相等的情况下,可以任意设置比特。
编辑2:为了示例,我在这里使用了字节。优选地,建议的方法应该保持最多~128字节的字节向量。
答案 0 :(得分:1)
一个字节只有8位。由于这个足够小,你可以使用递减基数排序(LSB优先)策略:
// 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次出现相等会发生什么
答案 1 :(得分:1)
有一种有点混乱的方式,但它不是那么漂亮(也不是特别快)。但无论如何,我会解释它,因为你问,这很有趣。
这个想法是,不是将每个位的计数保持在一个整数中,而是以整数形式保存计数器的位。因此,如果将计数器视为布尔矩阵,每行都是计数器,则将列存储在整数中。这样,输入数字是一种奇怪的补充,而不是“与位数一样多的增量”。像这样(未经测试)(加入c
)
for (int i = 0; i < counter_bits; i++) {
counter[i] ^= c;
c &= ~counter[i]; // counter & c == ~(counter ^ c) & c
}
现在有趣的部分:中位数是多少?好吧,如果n
(项目数)是2减1的幂,而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] = counter [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 -> 0x10010101
,然后添加(带有正常添加),然后提取最后的结果来自半字节的高位(pext
,如果你有它,否则它更棘手)。偏见技巧仍然有效。缺点:仅适用于n < 16
。尽管存在巨大的劣势,我仍然在现实生活中使用过这个(对于二进制谜题,修剪搜索空间)。当然,这仍然适用于“更多点差”,这会为您提供更高的最大值n
(例如,将{8}中的8个计数器放入uin64
,将{4}放入n < 256
给出uint64
等等。这与使用LeleDumbo的答案中的数组实际上是同构的,除了数组在单个int中(事实上如果你将扩展转换为11,它变得完全相同)。这也可以扩展到非常大的向量(只需在数组中使用其中几个计数器)。
或者:使用正确的SIMD,而不是假的SIMD。使得提取位更容易(因为可以提取所有顶部位的掩码)。它甚至不需要偏置技巧,因为你可以进行SIMD比较。它也使得传播更容易,因为它不必使用查找表 - 将项目广播到所有通道,屏蔽每个通道中的相应位(这方便地忽略了一些细节),比较然后减去< / em>这来自计数(因为现在它是-1时为真,而不是1)。例如(未测试)
n < 65536
这有点浪费,仅使用16个(或32个)车道中的8个,但它显示了基本想法。使用更多通道时,需要花费更多精力来解包位。