计算 32 位整数列表中的非零位

Counting non-zero bits in a list of 32-bit integers

编辑:问题已解决,不过请注意下面的代码,哈哈,我从一开始就在概念上有一些错误。

我尝试计算各种 32 位整数中的非零位的时间比我愿意承认的要长,但似乎总是有点偏差。

因此,例如,数字的正确数量的非零位:

13676 9190872 136669 -17621 -1631 -183 15570 0 495 468656377 -1340216 -91

将是:

8 12 10 26 25 27 8 0 8 18

然而,无论我尝试什么,我总是至少在几个数字(尤其是负数)上有几位数的偏差。

目前我正在使用:

def bitCount():
    numbers = input().split()
    answer = []
    for num in numbers:
        data = bin(((1 << 32) + int(num)) & -5)
        bitCount = 0
        for char in data[3:]:
            if char == '1':
                bitCount += 1
        answer.append(str(bitCount))
    print(' '.join(answer))
bitCount()

上面的一组数字生成:7 12 9 25 24 26 8 0 7 18 19 26

我做错了什么?我将如何解决这个问题?我无法想象它太复杂了,但经过数小时的搜索、阅读和试验后,我觉得我一定是遗漏了一些明显的东西。任何帮助将不胜感激。

我认为你让这件事变得比你需要的要难得多。我不明白 bin((1 << 32) + int(num) & -5) 应该做什么。正如您所注意到的,bin(number) returns 数字的二进制表示。所以现在你所要做的就是数 1。所以这会起作用:

for num in numbers:
    data = bin(num)
    bitCount = 0
    for char in data:
        if char == '1':
            bitCount += 1

要诱导 python 转换为二进制补码,您可以将 32 位 1(即 0xFFFFFFFF)的位掩码应用于 n。如果 n 为正,则结果为 n。但是,如果 n 为负,则位掩码操作 returns 一个正数,它具有与负数 n 的二进制补码表示相同的所有位。因此,无论哪种情况,位数都是您所期望的。

从那里您可以简单地计算出作为 "bin" 函数结果的位数。

[bin(n & 0xFFFFFFFF).count("1") for n in numbers]

您的示例输入的结果是

[8, 12, 10, 26, 25, 27, 8, 0, 8, 18, 20, 28]

除了给你期望的答案,this other question 还建议 bin(n).count 是找到整数汉明权重的最快方法

试试这个:

#!/usr/bin/env python                                                           


def bit_counter(number):                                                        

    suffix = 2                                                                  
    if number < 0:                                                              
        suffix = 3                                                              

    bit = bin(number)                                                           
    counter = 0                                                                 
    for i in bit[suffix:]:                                                      
        if int(i) == 1:                                                         
            counter += 1                                                        

    return counter                                                              


def main():                                                                     
    a = 13676                                                                   
    print bit_counter(a)                                                        
    a = -13676                                                                  
    print bit_counter(a)                                                        


if __name__ == "__main__":                                                      
    main()          

它适用于负数和非负数。

您的整个方法在 Python 中没有意义。

-17621 中的 1 位数,又名 -100010011010101 二进制,是 7。如果您期望 26,则您不需要询问 1 中的位数number,您要求的是解释为 C 32 位无符号整数的 C 32 位 2 补码有符号整数表示中的 1 位数。如果你想要 Python 中的那个,你必须明确地要求它。例如,您可以使用 num % 0x100000000.

与此同时,无论您从 (1 << 32) + num & -5 获得什么位技巧列表,它都是 假设 C int32 数学而不是实际的整数算术,所以它将又错了另外,我很确定你抄错了。另外,在 Python 中没有理由使用这些技巧——很可能它实际上会更慢,而不是更快,但无论哪种方式,它都会太慢,无法在一个时间里做无数次紧密的循环,而且速度足够快,可以做几次,所以这种挤出最后 5% 的优化在 Python 中比在 C 中毫无意义。(尽管许多这些老把戏实际上,使用现代编译器和 CPU,在 C 中也会减慢速度……)

并通过再次执行 [3:] 来取消初始 1 位假设您有一个 32 位表示,这又是错误的。 (这就是为什么您所有的正数都减去 1 的原因——您要去掉第一个 1 位。)

所以,让我们简单点:

answer = []
for num in numbers:
    data = int(num) % 0x100000000
    bits = format(data, 'b')
    answer.append(str(bits.count('1')))
print(' '.join(answer))

注意我用的是format(data, 'b'),这样就不用把开头的0b去掉了。

或者,如果你想让它更紧凑:

print(' '.join(str(format(int(num)%0x100000000, 'b').count('1')) for num in numbers))

无论哪种方式,您都会得到:

8 12 10 26 25 27 8 0 8 18 20 28

…确实比您的预期输出多了两个数字,但是您的输入也比您的预期输出多了两个数字,所以我认为这是可以预料的。

整数中设置为 1 的位数称为其 汉明权重人口计数

有几种快速算法可以执行这样的计算。查看以下来源:

  1. Bit array 维基百科文章
  2. Bit Twiddling Hack
  3. Whosebug question #109023
  4. 的回答
  5. Bit Manipulation page 在 Python 维基上。
  6. The Aggregate Magic Algorithms(针对 32 位整数进行了优化)。

我首选的通用算法是:

def bit_count(n):
    """Return the number of bits set to 1 in the integer number 'n'.
       This is called the Hamming weight or the population count of 'n'.
       The algorithm performs as many iterations as there are set bits.
       References:
         The C Programming Language 2nd Ed., Kernighan & Ritchie, 1988.
         Peter Wegner in CACM 3 (1960), 322.
    """
    assert n >= 0, 'Argument of bit_count() must be non-negative'
    count = 0
    while n:
        n &= n - 1
        count += 1
    return count

对于 32 位非负整数,以下函数速度更快:

def bit_count_parallel_32(n):
    """Return the number of set bits (1) present in the integer number 'n'.
       This algorithm accepts only 32-bit non-negative integer numbers.
    """
    assert 0 <= n < 2**32, ('Argument of bit_count_parallel_32() must be '
        'non-negative and less than %d (2**32)') % 2**32
    n = n - ((n >> 1) & 0x55555555)
    n = (n & 0x33333333) + ((n >> 2) & 0x33333333)
    return (((n + (n >> 4) & 0x0F0F0F0F) * 0x01010101) & 0xffffffff) >> (8 + 16)

如果有人需要,这里是 64 位非负整数的版本:

def bit_count_parallel_64(n):
    """Return the number of set bits (1) present in the integer number 'n'.
       This algorithm accepts only 64-bit non-negative integer numbers.
    """
    assert 0 <= n < 2**64, ('Argument of bit_count_parallel_64() must be '
        'non-negative and less than %d (2**64)') % 2**64
    n = n - ((n >> 1) & 0x5555555555555555)
    n = (n & 0x3333333333333333) + ((n >> 2) & 0x3333333333333333)
    return (((n + (n >> 4) & 0x0F0F0F0F0F0F0F0F) * 0x0101010101010101) \
        & 0xffffffffffffffff) >> (8 + 16 + 32)

但是请注意,所有这些算法都不适用于负整数。如果需要使用负数,可以使用前面的函数统计负数的补码表示中设置的位数,例如:

>>> bit_count(-3 & 0xFF)
7

也可以看看专门介绍这本好书的网站 Hacker's Delight (Wayback Machine)。

最简单的方法是使用gmpy:

import gmpy
[gmpy.popcount(n & 0xFFFFFFFF) for n in numbers]
#>>> [8, 12, 10, 26, 25, 27, 8, 0, 8, 18, 20, 28]

与字符串操作相比,这也可能相当快。

另一种方法是使用 Numpy:

import numpy

def count_bits_u32(u32):
  u32 = (u32 & 0x55555555) + ((u32 & 0xAAAAAAAA) >> 1)
  u32 = (u32 & 0x33333333) + ((u32 & 0xCCCCCCCC) >> 2)
  u32 = (u32 & 0x0F0F0F0F) + ((u32 & 0xF0F0F0F0) >> 4)
  u32 = (u32 & 0x00FF00FF) + ((u32 & 0xFF00FF00) >> 8)
  u32 = (u32 & 0x0000FFFF) + ((u32 & 0xFFFF0000) >> 16)
  return u32

count_bits_u32(numpy.array(numbers, dtype="uint32"))
#>>> array([ 8, 12, 10, 26, 25, 27,  8,  0,  8, 18, 20, 28], dtype=uint32)

这对于大型值列表可能更有效,因为它打包整数并使用矢量化运算。