Python/Numpy:将布尔型列表转换为无符号整数

12
  1. What is the fastest (or most "Pythonic") way to convert

    x = [False, False, True, True]
    

    into 12? (If there is such a way.)

  2. What if x were instead a numpy.array of bools? Is there a special command for that?

我有一个大的m×n布尔数组,每个n元素行表示高维特征向量的单个低维哈希。 (在上面的示例中,n = 4。)我想知道答案,以便尽可能压缩我的数据。谢谢。


编辑:感谢回复!使用以下测试代码,

t = 0
for iter in range(500):
    B = scipy.signbit(scipy.randn(1000,20))
    for b in B:
        t0 = time.clock()
        # test code here
        t1 = time.clock()
        t += (t1-t0)
print t

以下是在我的Thinkpad笔记本电脑上的运行时间:

当然,我欢迎任何独立测试来证实或否认我的数据!


编辑:在下面的回答中,将int(j)更改为简单的j仍然有效,但运行速度慢了六倍!那么,如果使用int来转换bool值,也许其他答案会变得更快。但我太懒了,不想再测试一遍。


编辑:liori在这里发布了独立测试结果。


将 [False, False, True, True] 转换为 12 的规则是什么? - user334856
2
请使用 timeit 进行测试,它更不容易出错。我的时间:http://pastebin.com/x1FEP9gY - liori
谢谢测试!我完全不怀疑它们。我已经将它们添加到帖子中。 - Steve Tjoa
只是需要注意的一点 - 在liori的测试中,sven2()表现非常糟糕,因为我们使用了1000位数字。检查结果(即每个函数返回的数字)你会发现对于这么大的数字,它的结果是错误的。 - Justin Peel
我认为他指的是liori发布的测试中。向量长度为1000,经过1000次试验进行测试。 - Steve Tjoa
显示剩余3条评论
10个回答

11

参考其他答案的各种想法,这里提供另一种方法:

sum(1<<i for i, b in enumerate(x) if b)

在我的测试中它非常快 - 即使它会溢出,对于大量位数的数字它也与numpy方法一样快。我使用了liori的测试模块进行测试。斯蒂芬的方法,在我建议的改变下,只是稍微快一点。然而,如果需要同时完成许多这种类型的转换(并且位数不太多),我敢打赌numpy会更快。


1
sum(b<<i for i, b in enumerate(x)) - kennytm
@KennyTM。聪明,但我对其进行了分析,原始版本大约快20%。它是迄今为止最快的。 - aaronasterling

6
最符合Python风格的可能是这样的代码:
sum(2**i*b for i, b in enumerate(x))

很难说它是否也是最快的。

在numpy中,我会使用

numpy.sum(2**numpy.arange(len(x))*x)

但是,对于小数组 x 来说,这种方法并不能提供更快的速度;而且对于大数组 x 来说,由于使用了机器大小的整数而不是 Python 的任意精度整数,该方法也无法奏效。

谢谢!对于某些数组大小,第二种解决方案效果很好,但对于其他一些情况则不行。 - Steve Tjoa
@Steve - numpy解决方案的另一个优点是可以避免迭代每一行。使用上面测试代码中的“B”数组: numpy.sum(2**numpy.arange(B.shape[1])*B, axis=1)。这应该比在数组的每一行上迭代要快得多...完整的500x循环在我的机器上执行不到一秒钟... - Joe Kington
1
由于numpy不能像Python一样处理大整数,因此在处理非常大的数字时必须小心。如果有更大的数字,可以通过在arange()中使用dtype=numpy.longlong来获得更多的优势。此外,使用结果numpy数组的sum方法而不是numpy.sum也可以略微提高速度。 - Justin Peel

3
reduce(lambda a,b:2*a+b, reversed(x))

如果你将最低有效位放在数组末尾,就可以摆脱reversed()。这也适用于numpy.array,并且不需要使用enumerate()。从我的测试结果来看,这种方法似乎更快:不需要使用指数运算。

谢谢您提供的优雅解决方案!我第一次看到它时感到非常惊讶。不幸的是,它似乎是最慢的,无论是否使用“reversed”。有人知道为什么吗? - Steve Tjoa
@Steve:在我的电脑上,它比求和和指数运算更快。有趣的是...你使用多长的向量?你使用 timeit 进行性能测试吗? - liori

2

一种优雅、pythonic的、始终有效的方法是这样的:

def powers(x):
    """yield powers of x, starting from x**0 forever"""
    power = 1
    while True:
        yield power
        power *= x

def bools_to_int(bools):
    # in Python 2, use itertools.izip!
    return sum(int(place) * place_weight for place_weight, place in 
               zip(powers(2), bools))

请注意,您可以通过枚举和平方来消除powers(就像其他答案所做的那样)-但也许以这种方式更清晰。

你的答案与其他人的答案不一致。将 bools 替换为 reversed(bools) 可以解决这个问题。 - Justin Peel
@Justin Peel:再说一遍?我回答后很快就注意到了,并添加了“reversed”... - user395760
尝试使用OP提供的示例来运行此处的代码。当答案应该是12时,我得到了3。您不需要加上“reversed”。 - Justin Peel
猛撞头 @Justin:是的,你说得对,现在我明白为什么了。 - user395760

2

我的初步尝试,仅供参考:

def bool2int(x):
    y = 0
    for i,j in enumerate(x):
        if j: y += int(j)<<i
    return y

等等,这很有趣:将 int(j) 更改为简单的 j 仍然有效,但运行速度慢了六倍! - Steve Tjoa
3
如果你只把 int(j) 改成 1,那么你的代码就是最快的。 - Justin Peel

1

像这样吗?

>>> x = [False, False, True, True]
>>> sum([int(y[1])*2**y[0] for y in enumerate(x)])
12

您可以使用list()方法将numpy数组转换为常规列表。
>>> a = numpy.array([1,2,3,4])
>>> a
array([1, 2, 3, 4])
>>> list(a)
[1, 2, 3, 4]

1
0**0 等于 1,因此如果第一个元素为 False,则会出现偏差错误。 - liori
@liori,我不认为这适用于我的代码,因为我实际上没有在任何地方这样做?不过还是很有趣的。我不知道这个。 - Emil H
int(False)*2==0。由 enumerate 给出的第一个索引是 0 - liori
1
@liori,是的,但我没有将其值提高到任何幂。我的代码执行i * 2^j。对于第一个位,即i * 2^0 = i*1 = i。 - Emil H
好的,我错了。我搞混了优先级规则 :-). - liori
@liori,没问题。最终,你的解决方案更优雅。 :) - Emil H

1
如果您有一个矩阵,您可能想这样做:
#precompute powers of two
vals = 2.**np.arange(20)

B = ....
compressed = np.dot(B, vals) # matrix multiplication.

np.dot 应该比 Python 中的任何循环都要快。快得多。


1

numpy有packbits函数可用于此操作。它还支持沿轴进行操作:

In [3]: B = scipy.signbit(scipy.randn(1000,8)).astype("i1")

In [3]: B[0]
Out[3]: array([0, 1, 0, 0, 0, 1, 0, 0], dtype=int8)

In [4]: np.packbits(B[0])
Out[4]: array([68], dtype=uint8)

In [5]: %timeit np.packbits(B, axis=1)
10000 loops, best of 3: 37 µs per loop

它适用于int8大小,对于较大的大小,您需要进行移位和或操作。
In [8]: x # multiple of 8
Out[8]: array([1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1], dtype=int8)

In [9]: r = np.packbits(x).astype(np.int32); r
Out[9]: array([171, 129], dtype=uint8)

In [10]: r[0] << 8 | r[1] 
Out[10]: 33237

In [11]: sum(1<<i for i, b in enumerate(x[::-1]) if b)
Out[11]: 33237

如果x不是8的倍数,则必须用零进行填充。

1

我正在尝试使用ipython %timeit,似乎以下操作更快:

y = 0
for i,j in enumerate(x):
    if j: y += 1<<i

此外,如果您的布尔向量是numpy.ndarray类型,将其转换为Python数组 x.tolist() 并运行相同的操作似乎在这种情况下更快。虽然差别微小,但稳定性很好,而且在这种速度下,边际效应也很好。

0

如果你愿意将另一个扩展添加到混合中,我已经在gmpy的开发分支中添加了pack()和unpack()。我的测试显示它可能会快2倍或3倍。

>>> import gmpy2
>>> gmpy2.pack([0,0,1,1],1)
mpz(12)
>>> gmpy2.unpack(12,1)
[mpz(0), mpz(0), mpz(1), mpz(1)]

注意事项:开发版本称为gmpy2,可以与稳定版本共存。它仍处于alpha阶段,但希望在几周内成为beta版。您需要安装GMP和MPFR库。源代码可在http://code.google.com/p/gmpy/source/checkout获取。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接