양의 정수에서 0이 아닌 비트를 계산하는 빠른 방법


117

파이썬에서 정수의 비트 수를 계산하는 빠른 방법이 필요합니다. 내 현재 솔루션은

bin(n).count("1")

하지만 더 빠른 방법이 있는지 궁금합니다.

추신 : (나는 큰 2D 이진 배열을 단일 숫자 목록으로 나타내고 비트 연산을 수행하고 있으며, 그로 인해 시간이 몇 시간에서 몇 분으로 단축됩니다. 이제 그 여분의 분을 제거하고 싶습니다.

편집 : 1. 파이썬 2.7 또는 2.6에 있어야합니다.

그리고 작은 숫자에 대한 최적화는 명확한 병목 현상이 아니기 때문에 그다지 중요하지 않지만 일부 장소에는 10,000 + 비트의 숫자가 있습니다.

예를 들어 이것은 2000 비트 케이스입니다.

12448057941136394342297748548545082997815840357634948550739612798732309975923280685245876950055614362283769710705811182976142803324242407017104841062064840113262840137625582646683068904149296501029754654149991842951570880471230098259905004533869130509989042199261339990315125973721454059973605358766253998615919997174542922163484086066438120268185904663422979603026066685824578356173882166747093246377302371176167843247359636030248569148734824287739046916641832890744168385253915508446422276378715722482359321205673933317512861336054835392844676749610712462818600179225635467147870208L


1
"정수"가 표준 파이썬보다 길면 어떤 표현을 사용하고 int있습니까? 이를 계산하는 자체 방법이 없습니까?
Marcin 2012 년


3
질문을 stackoverflow.com/a/2654211/1959808 의 질문과 구별하기 위해 (다른 의도가있는 경우 --- 적어도 그렇게 보이는 경우) 제목을“... 0 비트 ...”또는 유사합니다. 그렇지 않으면 int.bit_length답이되어야하며 아래에서 허용되는 답이 아닙니다.
Ioannis Filippidis 2011

답변:


121

임의 길이 정수의 경우 bin(n).count("1")순수한 Python에서 찾을 수있는 가장 빠른 것입니다.

저는 Óscar와 Adam의 솔루션을 적용하여 정수를 각각 64 비트와 32 비트 청크로 처리했습니다. 둘 다 적어도 10 배 더 느 렸습니다 bin(n).count("1")(32 비트 버전은 시간이 절반 정도 걸렸습니다).

반면에 gmpy popcount()bin(n).count("1"). 따라서 gmpy를 설치할 수 있다면 그것을 사용하십시오.

주석의 질문에 답하기 위해 바이트에 대해 조회 테이블을 사용합니다. 런타임에 생성 할 수 있습니다.

counts = bytes(bin(x).count("1") for x in range(256))  # py2: use bytearray

또는 문자 그대로 정의하십시오.

counts = (b'\x00\x01\x01\x02\x01\x02\x02\x03\x01\x02\x02\x03\x02\x03\x03\x04'
          b'\x01\x02\x02\x03\x02\x03\x03\x04\x02\x03\x03\x04\x03\x04\x04\x05'
          b'\x01\x02\x02\x03\x02\x03\x03\x04\x02\x03\x03\x04\x03\x04\x04\x05'
          b'\x02\x03\x03\x04\x03\x04\x04\x05\x03\x04\x04\x05\x04\x05\x05\x06'
          b'\x01\x02\x02\x03\x02\x03\x03\x04\x02\x03\x03\x04\x03\x04\x04\x05'
          b'\x02\x03\x03\x04\x03\x04\x04\x05\x03\x04\x04\x05\x04\x05\x05\x06'
          b'\x02\x03\x03\x04\x03\x04\x04\x05\x03\x04\x04\x05\x04\x05\x05\x06'
          b'\x03\x04\x04\x05\x04\x05\x05\x06\x04\x05\x05\x06\x05\x06\x06\x07'
          b'\x01\x02\x02\x03\x02\x03\x03\x04\x02\x03\x03\x04\x03\x04\x04\x05'
          b'\x02\x03\x03\x04\x03\x04\x04\x05\x03\x04\x04\x05\x04\x05\x05\x06'
          b'\x02\x03\x03\x04\x03\x04\x04\x05\x03\x04\x04\x05\x04\x05\x05\x06'
          b'\x03\x04\x04\x05\x04\x05\x05\x06\x04\x05\x05\x06\x05\x06\x06\x07'
          b'\x02\x03\x03\x04\x03\x04\x04\x05\x03\x04\x04\x05\x04\x05\x05\x06'
          b'\x03\x04\x04\x05\x04\x05\x05\x06\x04\x05\x05\x06\x05\x06\x06\x07'
          b'\x03\x04\x04\x05\x04\x05\x05\x06\x04\x05\x05\x06\x05\x06\x06\x07'
          b'\x04\x05\x05\x06\x05\x06\x06\x07\x05\x06\x06\x07\x06\x07\x07\x08')

그런 다음 0 ≤ x ≤ 255 counts[x]인 1 비트 수를 가져옵니다 x.


7
+1! 그 반대는 정확 bin(n).count("0")하지 않지만 '0b'접두사 때문에 정확하지 않다고 명시해야합니다 . 될 필요가있을 것이다 bin(n)[2:].count('0').... 그 계수 naughts에 대한
늑대

11
채우고있는 바이트 수를 알지 못하면 실제로 0 비트를 계산할 수 없습니다. Python long integer에서는 문제가 될 수 있습니다.
kindall

2
그것들은 단일 정수에 대한 빠른 옵션이지만 다른 답변에 제시된 알고리즘은 잠재적으로 벡터화 될 수 있으므로 큰 numpy배열 의 많은 요소에서 실행되는 경우 훨씬 빠릅니다 .
gerrit 2015

numpy 배열의 경우 다음과 같이 살펴볼 것입니다. gist.github.com/aldro61/f604a3fa79b3dec5436a
kindall

1
나는 bin(n).count("1"). 그러나 파이썬 제출의 60 % 만 능가합니다. @ leetcode
northtree

29

다음 알고리즘을 적용 할 수 있습니다.

def CountBits(n):
  n = (n & 0x5555555555555555) + ((n & 0xAAAAAAAAAAAAAAAA) >> 1)
  n = (n & 0x3333333333333333) + ((n & 0xCCCCCCCCCCCCCCCC) >> 2)
  n = (n & 0x0F0F0F0F0F0F0F0F) + ((n & 0xF0F0F0F0F0F0F0F0) >> 4)
  n = (n & 0x00FF00FF00FF00FF) + ((n & 0xFF00FF00FF00FF00) >> 8)
  n = (n & 0x0000FFFF0000FFFF) + ((n & 0xFFFF0000FFFF0000) >> 16)
  n = (n & 0x00000000FFFFFFFF) + ((n & 0xFFFFFFFF00000000) >> 32) # This last & isn't strictly necessary.
  return n

이는 64 비트 양수에 대해 작동하지만 쉽게 확장 할 수 있으며 인수의 로그에 따라 연산 수가 증가합니다 (즉, 인수의 비트 크기에 따라 선형 적으로).

이것이 어떻게 작동하는지 이해하기 위해 전체 64 비트 문자열을 64 개의 1 비트 버킷으로 나눈다 고 상상해보십시오. 각 버킷의 값은 버킷에 설정된 비트 수와 같습니다 (비트가 설정되지 않은 경우 0, 1 비트가 설정된 경우 1). 첫 번째 변환은 유사한 상태가되지만 각 2 비트 길이의 버킷이 32 개 있습니다. 이는 버킷을 적절하게 이동하고 값을 추가하여 수행됩니다 (버킷간에 캐리가 발생하지 않기 때문에 한 번만 추가하면 모든 버킷이 처리됩니다. n 비트 숫자는 항상 숫자 n을 인코딩하기에 충분합니다). 추가 변환은 하나의 64 비트 길이 버킷에 도달 할 때까지 기하 급수적으로 증가하는 크기의 버킷 수를 기하 급수적으로 감소시키는 상태로 이어집니다. 이것은 원래 인수에 설정된 비트 수를 제공합니다.


나는 이것이 10,000 비트 숫자로 어떻게 작동하는지 진지하게 알지 못하지만 해결책을 좋아합니다. 더 큰 숫자에 어떻게 적용 할 수 있는지 힌트를 줄 수 있습니까?
zidarsk8 2012 년

여기서 다루고있는 비트 수를 보지 못했습니다. C와 같은 저수준 언어로 데이터 처리 코드를 작성하는 것을 고려해 보셨습니까? 아마도 파이썬 코드의 확장으로? 파이썬의 큰 숫자에 비해 C의 큰 배열을 사용하여 성능을 확실히 향상시킬 수 있습니다. 즉, CountBits()8 줄의 코드를 추가하여 10k 비트 숫자를 처리하도록를 다시 작성할 수 있습니다 . 그러나 엄청난 상수로 인해 다루기 어려워 질 것입니다.
Adam Zalcman

2
상수 시퀀스를 생성하는 코드를 작성하고 처리를위한 루프를 설정할 수 있습니다.
Karl Knechtel 2012 년

이 답변은 큰 배열을 다루는 경우 벡터화 할 수 있다는 큰 장점이 numpy있습니다.
gerrit

17

다음은이 게시물 에서 설명한대로 인구 수 알고리즘의 Python 구현입니다 .

def numberOfSetBits(i):
    i = i - ((i >> 1) & 0x55555555)
    i = (i & 0x33333333) + ((i >> 2) & 0x33333333)
    return (((i + (i >> 4) & 0xF0F0F0F) * 0x1010101) & 0xffffffff) >> 24

에서 작동합니다 0 <= i < 0x100000000.


영리하네요. 엉덩이에서 답을 쏘는 대신 이것을 찾는 것이 완전히 적절합니다!
MrGomez

1
이것을 벤치마킹 했습니까? python 2.7을 사용하는 내 컴퓨터에서 실제로 bin(n).count("1").
David Weldon 2012 년

@DavidWeldon 아니요. 벤치 마크를 게시 해 주시겠습니까?
Óscar López

%timeit numberOfSetBits(23544235423): 1000000 loops, best of 3: 818 ns per loop; %timeit bitCountStr(23544235423): 1000000 loops, best of 3: 577 ns per loop.
gerrit 2015

7
그러나 numberOfSetBits내 864x64 numpy.ndarray를 841 µs로 처리합니다. 로 bitCountStrI 명시 적으로 루프를 가지고 있고, 그것은 40.7 밀리 이상 거의 50 시간이 걸립니다.
gerrit

8

게시물 에 따르면 이것은 Hamming 가중치를 가장 빠르게 구현 한 것 같습니다 (약 64KB의 메모리를 사용하는 것이 괜찮다면).

#http://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetTable
POPCOUNT_TABLE16 = [0] * 2**16
for index in range(len(POPCOUNT_TABLE16)):
    POPCOUNT_TABLE16[index] = (index & 1) + POPCOUNT_TABLE16[index >> 1]

def popcount32_table16(v):
    return (POPCOUNT_TABLE16[ v        & 0xffff] +
            POPCOUNT_TABLE16[(v >> 16) & 0xffff])

Python 2.x range에서는 xrange.

편집하다

더 나은 성능이 필요하고 숫자가 큰 정수인 경우 GMP라이브러리를 살펴보십시오 . 여기에는 다양한 아키텍처에 대해 손으로 작성한 어셈블리 구현이 포함되어 있습니다.

gmpy GMP 라이브러리를 래핑하는 C 코딩 된 Python 확장 모듈입니다.

>>> import gmpy
>>> gmpy.popcount(2**1024-1)
1024

나는 큰 숫자 (10k 비트 이상)에 이것이 필요하다는 것을 분명히하기 위해 질문을 편집했습니다. 32 비트 정수에 대해 무언가를 최적화하면 카운트 수가 정말 커야하므로 실행 시간이 느려질 것이므로 그다지 큰 차이를 만들지 않을 것입니다.
zidarsk8 2012 년

그러나 GMP는 당신이 언급 한 크기와 그 이상의 숫자를 포함하여 매우 큰 숫자를위한 것입니다.
James Youngman 2012 년

1
array.arrayfor 를 사용하면 메모리 사용량이 더 좋을 POPCOUNT_TABLE16것입니다. 그러면 동적 크기의 Python int객체 목록 대신 정수 배열로 저장됩니다 .
gsnedders 2014

6

이 방법이 정말 마음에 듭니다. 간단하고 매우 빠르지 만 파이썬에는 무한한 정수가 있기 때문에 비트 길이에 제한이 없습니다.

0을 스캔하는 데 시간을 낭비하지 않기 때문에 실제로 보이는 것보다 더 교활합니다. 예를 들어 1111에서와 같이 1000000000000000000000010100000001에서 설정된 비트를 계산하는 데 같은 시간이 걸립니다.

def get_bit_count(value):
   n = 0
   while value:
      n += 1
      value &= value-1
   return n

멋지게 보이지만 매우 "희소 한"정수에만 좋습니다. 평균적으로 꽤 느립니다. 그래도 특정 사용 사례에서는 정말 유용 해 보입니다.
zidarsk8 2008

"평균적으로 꽤 느립니다"라는 말이 무슨 뜻인지 잘 모르겠습니다. 무엇에 비해 상당히 느린가요? 인용하지 않는 다른 파이썬 코드에 비해 느리다는 의미입니까? 평균 수에 대해 비트 단위로 계산하는 것보다 두 배 빠릅니다. 사실 내 맥북에서는 초당 1,260 만 비트를 계산하는데, 이는 내가 셀 수있는 것보다 훨씬 빠릅니다. 정수 길이에 관계없이 작동하고 이것보다 빠른 또 다른 일반 파이썬 알고리즘이 있다면 그것에 대해 듣고 싶습니다.
Robotbugs

1
나는 실제로 위의 Manuel의 대답보다 느리다는 것을 받아들입니다.
Robotbugs

평균적으로 매우 느리다는 것은 10000 자리의 10000 숫자에 대한 비트를 계산 bin(n).count("1")하는 데 0.15 초가 걸리지 만 함수에는 3.8 초가 걸립니다 . 숫자에 설정된 비트 수가 매우 적 으면 빠르게 작동하지만 임의의 숫자를 취하면 평균적으로 위의 함수가 훨씬 느려집니다.
zidarsk8

네, 동의합니다. 넌 좀 부정확 하니까 내가 그냥 좆 같았 나봐.하지만 네 말이 맞아. 나는 내 의견 전에 위의 Manuel의 방법을 사용하여 방법을 테스트하지 않았습니다. 매우 투박해 보이지만 실제로는 매우 빠릅니다. 나는 지금 그런 버전을 사용하고 있지만 사전에 16 개의 값이 있으며 그가 인용 한 것보다 훨씬 빠릅니다. 그러나 기록을 위해 1로 설정된 몇 비트 만있는 응용 프로그램에서 내 것을 사용했습니다. 그러나 완전히 임의의 비트의 경우 길이에 따라 약간의 차이가 감소하는 약 50:50이됩니다.
Robotbugs

3

알고리즘을 사용하여 정수의 이진 문자열 [1]을 가져올 수 있지만 문자열을 연결하는 대신 1의 수를 계산합니다.

def count_ones(a):
    s = 0
    t = {'0':0, '1':1, '2':1, '3':2, '4':1, '5':2, '6':2, '7':3}
    for c in oct(a)[1:]:
        s += t[c]
    return s

[1] https://wiki.python.org/moin/BitManipulation


이것은 빠르게 작동합니다. 오류가 있습니다. 최소한 p3에서는 [1 :]이 [2 :] 여야합니다. oct ()는 문자열 앞에 '0o'를 반환하기 때문입니다. 코드는 oct () 대신 hex ()를 사용하고 16 개의 항목 사전을 만드는 경우 훨씬 더 빠르게 실행됩니다.
Robotbugs

2

Numpy가 너무 느리다고 했잖아요. 개별 비트를 저장하는 데 사용 했습니까? int를 비트 배열로 사용하는 아이디어를 확장하지 않고 Numpy를 사용하여 저장하는 이유는 무엇입니까?

n 비트를 ceil(n/32.)32 비트 정수 배열로 저장 합니다. 그런 다음 다른 배열을 인덱싱하는 데 사용하는 것을 포함하여 int를 사용하는 것과 동일한 방식으로 (잘, 비슷하게) numpy 배열을 사용할 수 있습니다.

알고리즘은 기본적으로 각 셀에 설정된 비트 수를 병렬로 계산하고 각 셀의 비트 수를 합산하는 것입니다.

setup = """
import numpy as np
#Using Paolo Moretti's answer http://stackoverflow.com/a/9829855/2963903
POPCOUNT_TABLE16 = np.zeros(2**16, dtype=int) #has to be an array

for index in range(len(POPCOUNT_TABLE16)):
    POPCOUNT_TABLE16[index] = (index & 1) + POPCOUNT_TABLE16[index >> 1]

def popcount32_table16(v):
    return (POPCOUNT_TABLE16[ v        & 0xffff] +
            POPCOUNT_TABLE16[(v >> 16) & 0xffff])

def count1s(v):
    return popcount32_table16(v).sum()

v1 = np.arange(1000)*1234567                       #numpy array
v2 = sum(int(x)<<(32*i) for i, x in enumerate(v1)) #single int
"""
from timeit import timeit

timeit("count1s(v1)", setup=setup)        #49.55184188873349
timeit("bin(v2).count('1')", setup=setup) #225.1857464598633

놀랍지 만 아무도 C 모듈 작성을 제안하지 않았습니다.


0
#Python prg to count set bits
#Function to count set bits
def bin(n):
    count=0
    while(n>=1):
        if(n%2==0):
            n=n//2
        else:
            count+=1
            n=n//2
    print("Count of set bits:",count)
#Fetch the input from user
num=int(input("Enter number: "))
#Output
bin(num)

-2

시작 표현은 1 또는 0 인 정수 목록의 목록입니다. 해당 표현에서 간단히 계산하십시오.


정수의 비트 수는 파이썬에서 일정합니다.

그러나 설정 비트 수를 계산하려는 경우 가장 빠른 방법은 다음 의사 코드를 준수하는 목록을 만드는 것입니다. [numberofsetbits(n) for n in range(MAXINT)]

이렇게하면 목록을 생성 한 후 일정한 시간 조회가 제공됩니다. 이것을 잘 구현하려면 @PaoloMoretti의 답변을 참조하십시오. 물론이 모든 것을 메모리에 보관할 필요는 없습니다. 일종의 영구 키-값 저장소 또는 MySql을 사용할 수 있습니다. (또 다른 옵션은 간단한 디스크 기반 저장소를 구현하는 것입니다.)


@StevenRumbalski 어떻게 도움이되지 않습니까?
Marcin

내가 당신의 대답을 읽었을 때 그것은 당신의 첫 번째 문장만을 포함하고 있습니다 : "정수의 비트 수는 파이썬에서 일정합니다."
Steven Rumbalski

나는 이미 저장할 수있는 모든 카운트에 대한 비트 카운트 룩업 테이블을 가지고 있지만, 많은 숫자 목록을 가지고 있고 a [i] & a [j]를 사용하여 연산하면 10+가 없으면 솔루션을 쓸모 없게 만듭니다. 램의 GB. & ^에 대한 배열 | 10000 개의 숫자로 구성된 트리플의 경우 3 * 10000 ^ 3 조회 테이블 크기가됩니다. 내가 필요한 모든 것을 알고하지 않기 때문에, 내가 그들을 필요로 할 때 단지 몇 천 계산하는 것이 더 의미가 있습니다
zidarsk8

@ zidarsk8 또는 일종의 데이터베이스 또는 영구 키-값 저장소를 사용할 수 있습니다.
Marcin 2012 년

@ zidarsk8 10GB 이상의 램은 놀랍도록 크지 않습니다. 빠른 수치 계산을 원한다면 중대형 아이언을 사용하는 것이 합리적이지 않습니다.
Marcin
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.