获取整数数组汉明距离的最快方法

Posted

技术标签:

【中文标题】获取整数数组汉明距离的最快方法【英文标题】:Fastest way to get hamming distance for integer array 【发布时间】:2017-04-14 00:05:46 【问题描述】:

设 a 和 b 是大小相同的向量,包含 8 位整数 (0-255)。我想计算这些向量不同的位数,即由这些数字的二进制表示串联形成的向量之间的汉明距离。例如:

a = [127,255]
b= [127,240]

使用 numpy 库

np.bitwise_xor(a,b)
# Output: array([ 0, 15])

我现在需要的是二进制表示上述数组的每个元素,并计算数组所有元素中 1 的数量。上面的例子将给出 0+4 = 4 的汉明距离。在 Python 中有什么快速而优雅的解决方案吗?

【问题讨论】:

那不是0 + 1,因为 254 除了一位之外全是 1,而 255 全是 1? 可能只需要一个标准的 popcount 配方,通过数组广播它,然后对结果求和。您可以通过将数组的缓冲区视为更大的 dtype 来获得加速。 @Divakar 这是我的错字。接得好。将示例数据中的数字更新为 240。 向量ab的典型长度是多少? @WarrenWeckesser 实际数据示例如下:a = [34, 200, 96, 158, 75, 208, 158, 230, 151, 85, 192, 131, 40, 142, 54, 64 , 75, 251, 147, 195, 78, 11, 62, 245, 49, 32, 154, 59, 21, 28, 52, 222] b = [128, 129, 2, 129, 196, 2, 168, 101、60、35、83、18、12、10、104、73、122、13、2、176、114、188、1、198、12、0、154、68、5、8、177、128] 【参考方案1】:

也许不是最有效的方法,但最简单的 imo 是将您的输出数组转换为二进制形式的字符串,然后将所有字符的总和转换回整数...

import numpy as np

output = np.random.randint(0,63,10)
hamming = [':b'.format(x).count('1') for x in output]

【讨论】:

【参考方案2】:

方法#1:我们可以将它们广播成二进制位并计算不同位的数量,就像这样 -

def hamming_distance(a, b):
    r = (1 << np.arange(8))[:,None]
    return np.count_nonzero( (a & r) != (b & r) )

示例运行 -

In [144]: a = [127,255]
     ...: b = [127,240]
     ...: 

In [145]: hamming_distance(a, b)
Out[145]: 4

方法#2:使用bitwise-xor运算,我们可以找出ab之间不同二进制位的数量-

def hamming_distance_v2(a, b):
    r = (1 << np.arange(8))[:,None]
    return np.count_nonzero((np.bitwise_xor(a,b) & r) != 0)

【讨论】:

方法 2 抛出异常:TypeError: unsupported operand type(s) for -: 'list' and 'list' @DebasishMitra 添加了一个更好的xor hamming_distance_v2() 的速度是hamming_distance() 的两倍。 对于这两个版本,不匹配的输入大小和/或超出 [0,255] 的值可能会产生不希望的结果。最好使用这些健全性检查:assert np.all(a &gt;= 0)assert np.all(a &lt;= 255)assert np.all(b &gt;= 0)assert np.all(b &lt;= 255)assert a.shape == b.shape【参考方案3】:
sum(bin(x).count("1") for x in np.bitwise_xor(a,b))

【讨论】:

【参考方案4】:

如果您要在程序的一次执行期间多次调用距离函数,则可以通过使用预先计算的位计数表来加快速度。这是汉明距离函数的(又一个)版本:

# _nbits[k] is the number of 1s in the binary representation of k for 0 <= k < 256.
_nbits = np.array(
      [0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, 1, 2, 2, 3, 2, 3, 3,
       4, 2, 3, 3, 4, 3, 4, 4, 5, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4,
       4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 1, 2, 2, 3, 2,
       3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5,
       4, 5, 5, 6, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4,
       5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3,
       3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 2,
       3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6,
       4, 5, 5, 6, 5, 6, 6, 7, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5,
       6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 3, 4, 4, 5, 4, 5,
       5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6,
       7, 7, 8], dtype=np.uint8)


def hamming_distance1(a, b):
    c = np.bitwise_xor(a, b)
    n = _nbits[c].sum()
    return n

在下文中,ab 是对问题的评论中给出的长度为 32 的 Python 列表。 divakar_hamming_distance()divakar_hamming_distance_v2() 来自@Divakar 的回答。

以下是@Divakar 功能的时间安排:

In [116]: %timeit divakar_hamming_distance(a, b)
The slowest run took 5.57 times longer than the fastest. This could mean that an intermediate result is being cached.
100000 loops, best of 3: 11.3 µs per loop

In [117]: %timeit divakar_hamming_distance_v2(a, b)
The slowest run took 5.35 times longer than the fastest. This could mean that an intermediate result is being cached.
100000 loops, best of 3: 10.3 µs per loop

hamming_distance1(a, b) 快一点:

In [118]: %timeit hamming_distance1(a, b)
The slowest run took 6.04 times longer than the fastest. This could mean that an intermediate result is being cached.
100000 loops, best of 3: 7.42 µs per loop

在我的计算机上,初始化 _nbits 大约需要 11 µs,因此如果您只调用一次函数,使用 hamming_distance1 没有任何优势。如果你调用它 3 次或更多次,性能就会有净收益。

如果输入已经是 numpy 数组,则所有函数都明显更快:

In [119]: aa = np.array(a)

In [120]: bb = np.array(b)

In [121]: %timeit divakar_hamming_distance_v2(aa, bb)
The slowest run took 8.22 times longer than the fastest. This could mean that an intermediate result is being cached.
100000 loops, best of 3: 5.72 µs per loop

In [122]: %timeit hamming_distance1(aa, bb)
The slowest run took 12.67 times longer than the fastest. This could mean that an intermediate result is being cached.
100000 loops, best of 3: 2.77 µs per loop

当然,如果您总是在计算汉明距离之前立即执行此操作,则进行转换的时间必须包含在总时间中。但是,如果您编写生成 ab 的代码以更早地利用 numpy,那么在您计算汉明距离时,您可能已经将它们作为 numpy 数组。


(我还对 8 位值之间的预先计算的汉明距离的二维数组进行了一些实验——形状为 (256, 256) 的数组——但初始化成本更高,性能提升很小。)

【讨论】:

不使用 Numba: divakar_hamming_distance(aa, bb) 每循环 5.8 µs hamming_distance1(aa, bb) 每循环 15.4 µs 使用Numba: divakar_hamming_distance(aa, bb) 每循环 1.2 µs hamming_distance1(aa, bb) 每循环 896 ns

以上是关于获取整数数组汉明距离的最快方法的主要内容,如果未能解决你的问题,请参考以下文章

461. 汉明距离

《LeetCode之每日一题》:50.汉明距离总和

Leetcode 477.汉明距离总和

2021/5/28 刷题笔记汉明距离总和与逐位比较法

LeetCode #461 汉明距离

LeetCode #461 汉明距离