Cách nhanh chóng để đếm các bit khác không trong số nguyên dương


117

Tôi cần một cách nhanh chóng để đếm số bit trong một số nguyên trong python. Giải pháp hiện tại của tôi là

bin(n).count("1")

nhưng tôi đang tự hỏi nếu có cách nào nhanh hơn để làm điều này?

Tái bút: (Tôi đang đại diện cho một mảng nhị phân 2D lớn dưới dạng một danh sách các số duy nhất và thực hiện các phép toán bit, và điều đó làm giảm thời gian từ hàng giờ xuống còn phút. Và bây giờ tôi muốn loại bỏ những phút thừa đó.

Chỉnh sửa: 1. nó phải ở trong python 2.7 hoặc 2.6

và tối ưu hóa cho các số nhỏ không quan trọng lắm vì đó sẽ không phải là một nút cổ chai rõ ràng, nhưng tôi có những con số với 10 000 + bit ở một số nơi

ví dụ đây là trường hợp 2000 bit:

12448057941136394342297748548545082997815840357634948550739612798732309975923280685245876950055614362283769710705811182976142803324242407017104841062064840113262840137625582646683068904149296501029754654149991842951570880471230098259905004533869130509989042199261339990315125973721454059973605358766253998615919997174542922163484086066438120268185904663422979603026066685824578356173882166747093246377302371176167843247359636030248569148734824287739046916641832890744168385253915508446422276378715722482359321205673933317512861336054835392844676749610712462818600179225635467147870208L


1
Bạn đang sử dụng loại biểu diễn nào nếu "số nguyên" của bạn dài hơn python tiêu chuẩn int? Điều đó không có phương pháp riêng để tính toán điều này?
Marcin


3
Để phân biệt câu hỏi với câu hỏi đó trong stackoverflow.com/a/2654211/1959808 (nếu nó có ý định khác --- ít nhất là giống như vậy), vui lòng xem xét diễn đạt lại tiêu đề thành “... đếm số không bit ... ”hoặc tương tự. Nếu không int.bit_lengthsẽ là câu trả lời, và không phải là câu được chấp nhận dưới đây.
Ioannis Filippidis

Câu trả lời:


121

Đối với các số nguyên có độ dài tùy ý, bin(n).count("1")là tốc độ nhanh nhất mà tôi có thể tìm thấy trong Python thuần túy.

Tôi đã thử điều chỉnh các giải pháp của Óscar và Adam để xử lý số nguyên tương ứng ở các khối 64 bit và 32 bit. Cả hai đều chậm hơn ít nhất mười lần bin(n).count("1")(phiên bản 32-bit mất khoảng một nửa thời gian).

Mặt khác, gmpy popcount() chiếm khoảng 1/5 thời gian bin(n).count("1"). Vì vậy, nếu bạn có thể cài đặt gmpy, hãy sử dụng nó.

Để trả lời một câu hỏi trong nhận xét, đối với byte, tôi sẽ sử dụng một bảng tra cứu. Bạn có thể tạo nó trong thời gian chạy:

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

Hoặc chỉ định nghĩa nó theo nghĩa đen:

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')

Sau đó, nó counts[x]lấy số bit 1 trong xđó 0 ≤ x ≤ 255.


7
+1! Ngược lại của điều này là không chính xác, tuy nhiên, cần phải nói rõ: bin(n).count("0")không chính xác vì tiền tố '0b'. Sẽ cần bin(n)[2:].count('0')cho những người đếm không có gì ....
the wolf

11
Tuy nhiên, bạn thực sự không thể đếm số bit 0 mà không biết mình đang điền bao nhiêu byte, điều này có vấn đề với một số nguyên dài trong Python vì nó có thể là bất kỳ thứ gì.
kindall

2
Mặc dù đó là những lựa chọn nhanh cho các số nguyên đơn lẻ, nhưng lưu ý rằng các thuật toán được trình bày trong các câu trả lời khác có thể có khả năng được vecto hóa, do đó nhanh hơn nhiều nếu chạy trên nhiều phần tử của một numpymảng lớn .
gerrit

Đối với các mảng numpy, tôi sẽ xem xét một cái gì đó như thế này: gist.github.com/aldro61/f604a3fa79b3dec5436a
kindall

1
Tôi đã sử dụng bin(n).count("1"). Tuy nhiên, chỉ đánh bại 60% số lần nộp python. @ leetcode
northtree

29

Bạn có thể điều chỉnh thuật toán sau:

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

Điều này hoạt động đối với các số dương 64 bit, nhưng nó có thể dễ dàng mở rộng và số lượng phép toán tăng lên theo lôgarit của đối số (tức là tuyến tính với kích thước bit của đối số).

Để hiểu cách hoạt động của điều này, hãy tưởng tượng rằng bạn chia toàn bộ chuỗi 64 bit thành 64 nhóm 1 bit. Giá trị của mỗi nhóm bằng số bit được đặt trong nhóm (0 nếu không có bit nào được đặt và 1 nếu một bit được đặt). Lần chuyển đổi đầu tiên dẫn đến một trạng thái tương tự, nhưng với 32 nhóm, mỗi nhóm dài 2 bit. Điều này đạt được bằng cách dịch chuyển các nhóm một cách thích hợp và thêm các giá trị của chúng (một phép bổ sung sẽ quan tâm đến tất cả các nhóm vì không thể thực hiện được trên các nhóm - số n-bit luôn đủ dài để mã hóa số n). Các phép biến đổi tiếp theo dẫn đến các trạng thái có số lượng nhóm giảm dần theo cấp số nhân có kích thước đang phát triển theo cấp số nhân cho đến khi chúng ta đi đến một nhóm dài 64 bit. Điều này cho biết số bit được đặt trong đối số ban đầu.


Tôi thực sự không biết điều này sẽ hoạt động như thế nào với 10 000 số bit, nhưng tôi thích giải pháp. bạn có thể cho tôi một gợi ý nếu và làm thế nào tôi có thể áp dụng nó cho những con số lớn hơn?
zidarsk 8

Tôi không thấy số bit bạn đang xử lý ở đây. Bạn đã cân nhắc việc viết mã xử lý dữ liệu của mình bằng một ngôn ngữ cấp thấp như C chưa? Có lẽ như một phần mở rộng cho mã python của bạn? Bạn chắc chắn có thể cải thiện hiệu suất bằng cách sử dụng các mảng lớn trong C so với các chữ số lớn trong python. Điều đó nói rằng, bạn có thể viết lại CountBits()để xử lý các số 10k-bit bằng cách chỉ thêm 8 dòng mã. Nhưng nó sẽ trở nên khó sử dụng do các hằng số lớn.
Adam Zalcman

2
Bạn có thể viết mã để tạo chuỗi hằng số và thiết lập một vòng lặp cho quá trình xử lý.
Karl Knechtel

Câu trả lời này có ưu điểm lớn là nó có thể được vecto hóa cho các trường hợp xử lý các numpymảng lớn .
gerrit

17

Đây là một triển khai Python của thuật toán đếm dân số, như được giải thích trong bài đăng này :

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

Nó sẽ hoạt động cho 0 <= i < 0x100000000.


Thật khéo léo. Nhìn lên điều này thay vì bắn một câu trả lời từ hông là hoàn toàn thích hợp!
MrGomez

1
Bạn đã chuẩn cái này chưa? Trên máy tính của tôi sử dụng python 2.7, tôi thấy điều này thực sự chậm hơn một chút bin(n).count("1").
David Weldon

@DavidWeldon Không, tôi không có, bạn có thể vui lòng đăng điểm chuẩn của mình được không?
Ó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

7
Tuy nhiên, numberOfSetBitsxử lý 864 × 64 của tôi numpy.ndarraytrong 841 µs. Với bitCountStrtôi phải lặp một cách rõ ràng, và nó mất 40,7 ms, hoặc lâu hơn gần 50 lần.
gerrit

8

Theo bài đăng này , đây có vẻ là một trong những cách thực hiện trọng lượng Hamming nhanh nhất (nếu bạn không ngại sử dụng khoảng 64KB bộ nhớ).

#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])

Trên Python 2.x, bạn nên thay thế rangebằng xrange.

Biên tập

Nếu bạn cần hiệu suất tốt hơn (và số của bạn là số nguyên lớn), hãy xem GMPthư viện. Nó chứa các triển khai lắp ráp viết tay cho nhiều kiến ​​trúc khác nhau.

gmpy là một mô-đun mở rộng Python được mã hóa C bao bọc thư viện GMP.

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

Tôi đã chỉnh sửa câu hỏi của mình để làm rõ rằng tôi cần điều này cho Số lớn (10k bit trở lên). tối ưu hóa thứ gì đó cho số nguyên 32 bit sẽ không tạo ra nhiều sự khác biệt vì số lượng đếm sẽ phải thực sự lớn, trong trường hợp đó sẽ gây ra thời gian thực thi chậm.
zidarsk 8

Nhưng GMP chính xác là dành cho những con số rất lớn, bao gồm những con số bằng và vượt xa những kích thước bạn đề cập.
James Youngman

1
Việc sử dụng bộ nhớ sẽ tốt hơn nếu bạn sử dụng array.arraycho POPCOUNT_TABLE16, vì sau đó nó sẽ được lưu trữ dưới dạng một mảng số nguyên, thay vì dưới dạng danh sách các intđối tượng Python có kích thước động .
gsnedders

6

Tôi thực sự thích phương pháp này. Nó đơn giản và khá nhanh nhưng cũng không bị giới hạn về độ dài bit vì python có vô hạn số nguyên.

Nó thực sự tinh ranh hơn vẻ ngoài của nó, vì nó tránh lãng phí thời gian quét các số không. Ví dụ, sẽ mất cùng thời gian để đếm các bit đã đặt trong 1000000000000000000000010100000001 như trong 1111.

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

trông tuyệt vời, nhưng nó chỉ tốt cho các số nguyên rất "thưa thớt". trung bình nó khá chậm. Tuy nhiên, nó trông thực sự hữu ích trong một số trường hợp sử dụng nhất định.
zidarsk8

Tôi không chắc ý bạn là "trung bình thì khá chậm". Khá chậm so với cái gì? Ý bạn là chậm so với một số mã python khác mà bạn không trích dẫn? Nó nhanh gấp đôi so với việc đếm từng chút một cho số trung bình. Trên thực tế, trên macbook của tôi, nó đếm 12,6 triệu bit mỗi giây, nhanh hơn rất nhiều so với tôi có thể đếm được. Nếu bạn có một thuật toán python chung khác hoạt động với bất kỳ độ dài số nguyên nào và nhanh hơn thuật toán này, tôi muốn biết về nó.
Robotbugs

1
Tôi chấp nhận rằng nó thực sự chậm hơn câu trả lời của Manuel ở trên.
Robotbugs

Trung bình khá chậm, đếm số bit cho 10000 số với 10000 chữ số mất 0,15 giây bin(n).count("1")nhưng hàm của bạn mất 3,8 giây. Nếu các số có rất ít bit được thiết lập thì nó sẽ hoạt động nhanh, nhưng nếu bạn lấy bất kỳ số ngẫu nhiên nào, về trung bình, hàm trên sẽ có thứ tự độ lớn chậm hơn.
zidarsk 8

OK, tôi sẽ chấp nhận điều đó. Tôi đoán tôi chỉ là một con ranh vì bạn hơi thiếu chính xác nhưng bạn hoàn toàn đúng. Tôi chỉ chưa thử nghiệm phương pháp bằng cách sử dụng phương pháp của Manuel ở trên trước khi nhận xét của tôi. Nó trông rất lắt léo nhưng nó thực sự rất nhanh. Bây giờ tôi đang sử dụng một phiên bản như vậy nhưng với 16 giá trị trong từ điển và nó thậm chí còn nhanh hơn nhiều so với phiên bản mà anh ấy đã trích dẫn. Nhưng đối với bản ghi mà tôi đang sử dụng trong một ứng dụng chỉ có một vài bit được đặt thành 1. Nhưng đối với các bit hoàn toàn ngẫu nhiên, nó sẽ vào khoảng 50:50 với một chút phương sai giảm theo độ dài.
Robotbugs

3

Bạn có thể sử dụng thuật toán để lấy chuỗi nhị phân [1] của một số nguyên nhưng thay vì nối chuỗi, hãy đếm số đơn vị:

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


Điều này hoạt động nhanh chóng. Có một lỗi, ít nhất là trên p3, [1:] phải là [2:] vì oct () trả về '0o' trước chuỗi. Mã này chạy nhanh hơn rất nhiều tuy nhiên nếu bạn sử dụng hex () thay vì tháng mười () và thực hiện một cuốn từ điển 16 nhập cảnh,
Robotbugs

2

Bạn nói Numpy quá chậm. Bạn có sử dụng nó để lưu trữ các bit riêng lẻ không? Tại sao không mở rộng ý tưởng sử dụng int làm mảng bit mà lại sử dụng Numpy để lưu trữ chúng?

Lưu trữ n bit dưới dạng một mảng các ceil(n/32.)int 32 bit. Sau đó, bạn có thể làm việc với mảng numpy giống như cách bạn sử dụng int, bao gồm cả việc sử dụng chúng để lập chỉ mục một mảng khác.

Thuật toán về cơ bản là tính toán song song số lượng bit được đặt trong mỗi ô và chúng tổng hợp số bit của mỗi ô.

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

Mặc dù tôi rất ngạc nhiên là không ai đề nghị bạn viết mô-đun 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

Hóa ra biểu diễn bắt đầu của bạn là một danh sách danh sách các số nguyên là 1 hoặc 0. Chỉ cần đếm chúng trong biểu diễn đó.


Số bit trong một số nguyên là không đổi trong python.

Tuy nhiên, nếu bạn muốn đếm số bit đã đặt, cách nhanh nhất là tạo một danh sách tuân theo mã giả sau: [numberofsetbits(n) for n in range(MAXINT)]

Điều này sẽ cung cấp cho bạn một thời gian tra cứu liên tục sau khi bạn đã tạo danh sách. Xem câu trả lời của @ PaoloMoretti để biết cách triển khai tốt điều này. Tất nhiên, bạn không cần phải lưu tất cả những thứ này trong bộ nhớ - bạn có thể sử dụng một số loại lưu trữ khóa-giá trị liên tục, hoặc thậm chí MySql. (Một tùy chọn khác sẽ là triển khai bộ lưu trữ dựa trên đĩa đơn giản của riêng bạn).


@StevenRumbalski Nó không hữu ích như thế nào?
Marcin

Khi tôi đọc câu trả lời của bạn, nó chỉ chứa câu đầu tiên của bạn: "Số bit trong một số nguyên là không đổi trong python."
Steven Rumbalski

Tôi đã có một bảng tra cứu số lượng bit cho tất cả các số lượng có thể lưu trữ, nhưng việc có một danh sách lớn các số và thao tác trên chúng với [i] & a [j], khiến việc giải quyết của bạn trở nên vô dụng trừ khi tôi có hơn 10 GB ram. mảng cho & ^ | đối với bộ ba của 10000 số sẽ là kích thước bảng tra cứu 3 * 10000 ^ 3. vì tôi không biết mình sẽ cần gì, sẽ hợp lý hơn nếu chỉ đếm vài nghìn khi tôi cần chúng
zidarsk

@ zidarsk8 Hoặc, bạn có thể sử dụng một số loại cơ sở dữ liệu hoặc kho lưu trữ khóa-giá trị liên tục.
Marcin

@ zidarsk8 10 + GB ram không lớn gây sốc. Nếu bạn muốn thực hiện tính toán số nhanh, không phải là không hợp lý khi sử dụng bàn ủi vừa và lớn.
Marcin
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.