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

问题描述 投票:0回答:5

设 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 中有任何快速而优雅的解决方案吗?

python numpy binary xor hamming-distance
5个回答
12
投票

方法#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
运算,我们可以找出
a
b
之间不同的二进制位数 -

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

7
投票

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

# _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

在下面,

a
b
是问题评论中给出的长度为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
没有任何优势。 如果您调用三次或更多次,性能会有净收益。

如果输入已经是 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

当然,如果您总是在计算汉明距离之前立即执行此操作,则进行转换的时间必须包含在总体计时中。 但是,如果您编写生成

a
b
的代码来更早地利用 numpy,那么在计算汉明距离时,您可能已经将它们作为 numpy 数组了。


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


1
投票

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

import numpy as np

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

1
投票
sum(bin(x).count("1") for x in np.bitwise_xor(a,b))

0
投票

Python 3.10 中引入的 bit_count()

 上的 
int
 方法就是可以使用的工具。

对于类似于字节大小的整数的列表 a 和 b 之类的东西。

sum([(x ^ y).bit_count() for x, y in zip(a, b)])

如果您知道列表 a 和 b 的长度相同并且其中的整数和所有整数都在范围(:256)内,则应该这样做。

我在 toy_crypto 实用程序中有一个更通用的字节汉明距离函数

© www.soinside.com 2019 - 2024. All rights reserved.