Sıfır olmayan bitleri pozitif tamsayıda saymanın hızlı yolu


117

Python'da bir tamsayıdaki bit sayısını saymak için hızlı bir yola ihtiyacım var. Mevcut çözümüm

bin(n).count("1")

ama bunu yapmanın daha hızlı bir yolu olup olmadığını merak ediyorum?

Not: (Büyük bir 2D ikili diziyi tek bir sayı listesi olarak temsil ediyorum ve bitsel işlemler yapıyorum ve bu da zamanı saatlerden dakikalara indiriyor ve şimdi bu fazladan dakikalardan kurtulmak istiyorum.

Düzenleme: 1. python 2.7 veya 2.6'da olmalı

ve küçük sayılar için optimizasyon yapmak o kadar da önemli değil çünkü bu açık bir darboğaz olmayacaktır, ancak bazı yerlerde 10.000'den fazla bit olan sayılarım var

örneğin bu 2000 bitlik bir durumdur:

12448057941136394342297748548545082997815840357634948550739612798732309975923280685245876950055614362283769710705811182976142803324242407017104841062064840113262840137625582646683068904149296501029754654149991842951570880471230098259905004533869130509989042199261339990315125973721454059973605358766253998615919997174542922163484086066438120268185904663422979603026066685824578356173882166747093246377302371176167843247359636030248569148734824287739046916641832890744168385253915508446422276378715722482359321205673933317512861336054835392844676749610712462818600179225635467147870208L


1
"Tam sayılarınız" standart bir python'dan daha uzunsa, ne tür bir temsil kullanıyorsunuz int? Bunu hesaplamak için kendi yöntemi yok mu?
Marcin


3
Soruyu stackoverflow.com/a/2654211/1959808'deki sorudan ayırmak için (farklı olması amaçlanıyorsa - en azından öyle görünüyorsa) lütfen başlığı “... olmayanların sayısını sayarak sıfır bit ... ”veya benzeri. Aksi takdirde int.bit_lengthcevap olmalı ve aşağıda kabul edilen cevap olmamalıdır.
Ioannis Filippidis

Yanıtlar:


121

Rasgele uzunluktaki tam sayılar için, bin(n).count("1")saf Python'da bulabildiğim en hızlıdır.

Tamsayıyı sırasıyla 64 bit ve 32 bit yığınlarda işlemek için Óscar'ın ve Adam'ın çözümlerini uyarlamayı denedim. Her ikisi de en az on kat daha yavaştı bin(n).count("1")(32 bit sürüm yine yaklaşık yarısı kadar sürdü).

Öte yandan, gmpy zamanın popcount() yaklaşık 1 / 20'sini aldı bin(n).count("1"). Yani gmpy yükleyebiliyorsanız, bunu kullanın.

Yorumlarda bir soruyu cevaplamak için, baytlar için bir arama tablosu kullanırım. Bunu çalışma zamanında oluşturabilirsiniz:

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

Ya da tam anlamıyla tanımlayın:

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

Daha sonra 0 ≤ x ≤ 255 counts[x]olan 1 bit sayısını elde etmektir x.


7
1! Bunun tersi doğru değil, ancak belirtilmelidir: bin(n).count("0")'0b' ön eki nedeniyle doğru değil. bin(n)[2:].count('0')Geceleri sayanlar için olması gerekirdi ....
kurt

11
Kaç bayt doldurduğunuzu bilmeden gerçekten sıfır biti sayamazsınız, bu bir Python uzun tamsayı için sorunludur çünkü herhangi bir şey olabilir.
kindall

2
Bunlar tek tamsayılar için hızlı seçenekler olsa da, diğer yanıtlarda sunulan algoritmaların potansiyel olarak vektörleştirilebileceğini, dolayısıyla büyük bir numpydizinin birçok öğesinde çalıştırıldığında çok daha hızlı olabileceğini unutmayın .
gerrit


1
Kullandım bin(n).count("1"). Ancak, python gönderiminin yalnızca% 60'ını yener. @ leetcode
northtree

30

Aşağıdaki algoritmayı uyarlayabilirsiniz:

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

Bu 64 bitlik pozitif sayılar için işe yarar, ancak kolayca genişletilebilir ve işlem sayısı argümanın logaritmasıyla artar (yani, argümanın bit boyutuyla doğrusal olarak).

Bunun nasıl çalıştığını anlamak için 64 bitlik dizinin tamamını 64 1 bitlik kovalara böldüğünüzü hayal edin. Her bölümün değeri, bölümde ayarlanan bit sayısına eşittir (bit ayarlanmamışsa 0 ve bir bit ayarlanmışsa 1). İlk dönüşüm, benzer bir durumda, ancak her biri 2 bit uzunluğunda 32 kova ile sonuçlanır. Bu, kepçelerin uygun şekilde kaydırılması ve değerlerinin eklenmesi ile elde edilir (kovalar arasında hiçbir taşıma gerçekleşemeyeceği için bir ekleme tüm kepçelere dikkat eder - n-bit sayı her zaman n sayısını kodlamak için yeterince uzundur). Daha sonraki dönüşümler, 64 bit uzunluğundaki tek bir bölüme ulaşana kadar katlanarak azalan kova sayısı katlanarak artan durumlara yol açar. Bu, orijinal bağımsız değişkende ayarlanan bit sayısını verir.


Bunun 10.000 bitlik sayılarla nasıl çalışacağına dair hiçbir fikrim yok, ama çözümü beğendim. bunu daha büyük sayılara nasıl uygulayabileceğim konusunda bana bir ipucu verebilir misin?
zidarsk8

Burada uğraştığınız bit sayısını görmedim. Veri işleme kodunuzu C gibi düşük seviyeli bir dilde yazmayı düşündünüz mü? Belki de python kodunuzun bir uzantısı olarak? Python'daki büyük sayılara kıyasla C'de büyük diziler kullanarak performansı kesinlikle artırabilirsiniz. Bununla birlikte, CountBits()yalnızca 8 satır kod ekleyerek 10k bitlik sayıları işlemek için yeniden yazabilirsiniz . Ancak büyük sabitler nedeniyle hantal hale gelecektir.
Adam Zalcman

2
Sabitlerin sırasını oluşturmak için kod yazabilir ve işleme için bir döngü oluşturabilirsiniz.
Karl Knechtel

Bu cevap, büyük dizilerle ilgili durumlar için vektörleştirilebilmesi gibi büyük bir avantaja sahiptir numpy.
gerrit

17

İşte bu yazıda açıklandığı gibi, nüfus sayımı algoritmasının bir Python uygulaması :

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

Bunun için çalışacak 0 <= i < 0x100000000.


Zekice. Kalçadan bir cevap çekmek yerine bunu aramak tamamen uygun!
MrGomez

1
Bunu kıyasladınız mı? Python 2.7 kullanan makinemde, bunun aslında biraz daha yavaş olduğunu buldum bin(n).count("1").
David Weldon

@DavidWeldon Hayır yapmadım, lütfen kriterlerinizi gönderebilir misiniz?
Ó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
Ancak, numberOfSetBits864 × numpy.ndarray64'ümü 841 µs'de işler . İle bitCountStrI açıkça döngü var ve bu 40,7 ms veya daha uzun yaklaşık 50 dakika alıyor.
gerrit

8

Bu gönderiye göre , bu Hamming ağırlığının en hızlı uygulanmasından biri gibi görünüyor (yaklaşık 64 KB bellek kullanmakta sakınca görmüyorsanız).

#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 sitesinde değiştirmelisiniz 2.x rangeile xrange.

Düzenle

Daha iyi performansa ihtiyacınız varsa (ve sayılarınız büyük tam sayılarsa), GMPkitaplığa bir göz atın . Birçok farklı mimari için elle yazılmış montaj uygulamaları içerir.

gmpy GMP kitaplığını saran C kodlu bir Python genişletme modülüdür.

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

Büyük sayılar (10k bit ve daha fazlası) için buna ihtiyacım olduğunu açıkça belirtmek için sorumu düzenledim. 32 bitlik tamsayılar için bir şeyi optimize etmek muhtemelen bu kadar büyük bir fark yaratmayacaktır, çünkü sayımların sayısı gerçekten büyük olmak zorundadır, bu durumda bu yavaş yürütme süresine neden olur.
zidarsk8

Ancak GMP, bahsettiğiniz boyutlarda ve çok ötesinde sayılar da dahil olmak üzere tam olarak çok büyük sayılar içindir.
James Youngman

1
Bellek kullanımı, dinamik olarak boyutlandırılmış Python nesneleri listesi yerine bir tamsayı dizisi olarak saklanacağı array.arrayiçin için kullanırsanız daha iyi olacaktır . POPCOUNT_TABLE16int
gsnedders

6

Bu yöntemi gerçekten seviyorum. Basit ve oldukça hızlıdır, ancak python sonsuz tam sayıya sahip olduğundan bit uzunluğunda da sınırlı değildir.

Aslında göründüğünden daha kurnaz çünkü sıfırları tarayarak zaman kaybetmekten kaçınıyor. Örneğin, 100000000000000000000000010100000001'de ayarlanan bitleri saymak 1111'deki ile aynı zamanı alacaktır.

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

harika görünüyor, ancak yalnızca çok "seyrek" tam sayılar için iyidir. ortalama olarak oldukça yavaştır. Yine de, belirli kullanım durumlarında gerçekten yararlı görünüyor.
zidarsk8

"Ortalama olarak oldukça yavaş" derken neyi kastettiğinden pek emin değilim. Neye kıyasla oldukça yavaş? Alıntı yapmadığınız başka bir python koduna kıyasla yavaş mı demek istiyorsunuz? Ortalama sayı için azar azar saymaktan iki kat daha hızlıdır. Aslında macbook'umda saniyede 12,6 milyon bit sayıyor ki bu benim onları sayabileceğimden çok daha hızlı. Herhangi bir tam sayı uzunluğu için çalışan ve bundan daha hızlı olan başka bir genel python algoritmanız varsa, bunu duymak isterim.
Robotbugs

1
Yukarıda Manuel'in cevabından daha yavaş olduğunu kabul ediyorum.
Robotbugs

Ortalama olarak oldukça yavaş, 10000 basamaklı 10000 sayı için bit saymak bin(n).count("1")0.15 saniye sürer, ancak işleviniz için 3.8 saniye sürmüştür. Sayılar çok az bit setine sahip olsaydı, hızlı çalışırdı, ancak herhangi bir rasgele sayı alırsan, ortalama olarak yukarıdaki işlev, büyüklük sıraları daha yavaş olacaktır.
zidarsk8

Tamam bunu kabul edeceğim. Sanırım ben sadece pislik yapıyordum çünkü biraz belirsizsin ama tamamen haklısın. Yorumumdan önce yukarıdaki Manuel'in yöntemini kullanarak yöntemi test etmemiştim. Çok hantal görünüyor ama aslında çok hızlı. Şimdi bunun gibi bir sürüm kullanıyorum, ancak sözlükte 16 değer var ve bu onun alıntı yaptığından çok daha hızlı. Ancak kayıt için benimkini sadece 1'e ayarlanmış birkaç biti olan bir uygulamada kullanıyordum. Ancak tamamen rastgele bitler için evet, uzunlukla azalan bir varyansla yaklaşık 50:50 olacak.
Robotbugs

3

Algoritmayı, bir tamsayının ikili dizesini [1] elde etmek için kullanabilirsiniz, ancak dizeyi birleştirmek yerine, birlerin sayısını sayarak:

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


Bu hızlı çalışır. Bir hata var, en azından p3'te, [1:] [2:] olmalıdır çünkü oct () dizeden önce '0o' döndürür. Eğer oct () yerine hex () kullanırsanız ve 16 girişli bir sözlük
yaparsanız

2

Numpy'nin çok yavaş olduğunu söyledin. Tek tek bitleri depolamak için mi kullanıyordunuz? İnt'leri bit dizileri olarak kullanma fikrini neden genişletmiyorsunuz ama bunları depolamak için Numpy kullanıyorsunuz?

N biti ceil(n/32.)32 bitlik bir dizi olarak saklayın. Daha sonra, başka bir diziyi indekslemek için kullanmak da dahil olmak üzere, numpy dizisiyle aynı (iyi, yeterince benzer) şekilde çalışabilirsiniz.

Algoritma temelde paralel olarak her hücrede ayarlanan bit sayısını hesaplamaktır ve bunlar her hücrenin bit sayısını toplar.

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

Yine de kimsenin bir C modülü yazmanı önermemesine şaşırdım.


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

Başlangıç ​​temsilinizin 1 veya 0 olan tamsayı listelerinin bir listesi olduğu ortaya çıkıyor.


Bir tamsayıdaki bit sayısı python'da sabittir.

Bununla birlikte, set bitlerinin sayısını saymak istiyorsanız, en hızlı yol, aşağıdaki sözde koda uyan bir liste oluşturmaktır: [numberofsetbits(n) for n in range(MAXINT)]

Bu, listeyi oluşturduktan sonra size sabit zamanlı bir arama sağlayacaktır. Bunun iyi bir uygulaması için @ PaoloMoretti'nin cevabına bakın. Tabii ki, bunların hepsini hafızada tutmak zorunda değilsiniz - bir çeşit kalıcı anahtar-değer deposu veya hatta MySql kullanabilirsiniz. (Diğer bir seçenek de kendi basit disk tabanlı depolamanızı uygulamak olabilir).


@StevenRumbalski Nasıl yararsızdır?
Marcin

Cevabınızı okuduğumda sadece ilk cümlenizi içeriyordu: "Bir tamsayıdaki bit sayısı python'da sabittir."
Steven Rumbalski

Depolaması mümkün olan tüm sayılar için zaten bir sayı arama tablosuna sahibim, ancak çok sayıda sayı listesine sahip olmak ve bunlarda [i] ve a [j] ile çalışmak, çözümünüzü 10+ yoksa işe yaramaz hale getiriyor GB RAM. dizisi & ^ | 10000 sayının üçlüleri için 3 * 10000 ^ 3 arama tablosu boyutu olacaktır. Neye ihtiyacım olacağını bilmediğim için, ihtiyacım olduğunda birkaç bini saymak daha mantıklı
zidarsk8

@ zidarsk8 Veya, bir çeşit veritabanı veya kalıcı anahtar-değer deposu kullanabilirsiniz.
Marcin

@ zidarsk8 10 + GB ram, şaşırtıcı derecede büyük değil. Hızlı sayısal hesaplama yapmak istiyorsanız, orta-büyük demir kullanmak mantıksız değildir.
Marcin
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.