Modo rapido di contare bit diversi da zero in numero intero positivo


117

Ho bisogno di un modo veloce per contare il numero di bit in un numero intero in Python. La mia soluzione attuale è

bin(n).count("1")

ma mi chiedo se esiste un modo più veloce per farlo?

PS: (sto rappresentando un grande array binario 2D come un unico elenco di numeri e sto eseguendo operazioni bit per bit, e questo riduce il tempo da ore a minuti. E ora vorrei sbarazzarmi di quei minuti extra.

Modifica: 1. deve essere in Python 2.7 o 2.6

e l'ottimizzazione per numeri piccoli non ha molta importanza poiché non sarebbe un chiaro collo di bottiglia, ma in alcuni punti ho numeri con più di 10.000 bit

ad esempio questo è un caso a 2000 bit:

12448057941136394342297748548545082997815840357634948550739612798732309975923280685245876950055614362283769710705811182976142803324242407017104841062064840113262840137625582646683068904149296501029754654149991842951570880471230098259905004533869130509989042199261339990315125973721454059973605358766253998615919997174542922163484086066438120268185904663422979603026066685824578356173882166747093246377302371176167843247359636030248569148734824287739046916641832890744168385253915508446422276378715722482359321205673933317512861336054835392844676749610712462818600179225635467147870208L


1
Che tipo di rappresentazione stai usando se i tuoi "numeri interi" sono più lunghi di un pitone standard int? Questo non ha un proprio metodo per calcolarlo?
Marcin

1
possibile duplicato di Count bit di un intero in Python
endolith

3
Per distinguere la domanda da quella in stackoverflow.com/a/2654211/1959808 (se intende essere diversa, almeno così sembra), considera di riformulare il titolo in "... contando il numero di non- zero bit ... "o simile. Altrimenti int.bit_lengthdovrebbe essere la risposta, e non quella accettata di seguito.
Ioannis Filippidis

Risposte:


121

Per interi di lunghezza arbitraria, bin(n).count("1")è il più veloce che ho trovato in Python puro.

Ho provato ad adattare le soluzioni di Óscar e Adam per elaborare il numero intero rispettivamente in blocchi a 64 bit e 32 bit. Entrambi erano almeno dieci volte più lenti dibin(n).count("1") (la versione a 32 bit impiegava di nuovo circa la metà del tempo).

D'altra parte, gmpy ha popcount() impiegato circa 1/20 del tempo di bin(n).count("1"). Quindi, se puoi installare gmpy, usalo.

Per rispondere a una domanda nei commenti, per i byte utilizzerei una tabella di ricerca. Puoi generarlo in fase di esecuzione:

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

O semplicemente definiscilo letteralmente:

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

Quindi è counts[x]per ottenere il numero di 1 bit in xcui 0 ≤ x ≤ 255.


7
+1! Il contrario di questo non è accurato, tuttavia, dovrebbe essere affermato: bin(n).count("0")non è accurato a causa del prefisso "0b". Dovrebbe essere bin(n)[2:].count('0')per quelli che contano gli zombie ...
il lupo

11
Non puoi davvero contare zero bit senza sapere quanti byte stai riempiendo, il che è problematico con un intero lungo Python perché potrebbe essere qualsiasi cosa.
kindall

2
Sebbene queste siano opzioni rapide per singoli interi, si noti che gli algoritmi presentati in altre risposte possono essere potenzialmente vettorizzati, quindi molto più veloci se eseguiti su molti elementi di un ampio numpyarray.
gerrit

Per gli array numpy, esaminerei qualcosa del genere: gist.github.com/aldro61/f604a3fa79b3dec5436a
kindall

1
Ho usato bin(n).count("1"). Tuttavia, batte solo il 60% della presentazione di Python. @ leetcode
northtree

29

È possibile adattare il seguente algoritmo:

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

Funziona per numeri positivi a 64 bit, ma è facilmente estendibile e il numero di operazioni cresce con il logaritmo dell'argomento (cioè linearmente con la dimensione in bit dell'argomento).

Per capire come funziona, immagina di dividere l'intera stringa a 64 bit in 64 bucket da 1 bit. Il valore di ogni bucket è uguale al numero di bit impostato nel bucket (0 se non sono impostati bit e 1 se è impostato un bit). La prima trasformazione risulta in uno stato analogo, ma con 32 bucket ciascuno della lunghezza di 2 bit. Ciò si ottiene spostando in modo appropriato i bucket e aggiungendo i loro valori (un'aggiunta si prende cura di tutti i bucket poiché non può verificarsi alcun riporto tra i bucket: il numero di n bit è sempre abbastanza lungo da codificare il numero n). Ulteriori trasformazioni portano a stati con un numero esponenzialmente decrescente di bucket di dimensioni in crescita esponenziale fino ad arrivare a un bucket lungo 64 bit. Questo fornisce il numero di bit impostato nell'argomento originale.


Non ho davvero idea di come funzionerebbe con 10 000 bit, ma la soluzione mi piace. puoi darmi un suggerimento se e come posso applicarlo a numeri più grandi?
zidarsk8

Non ho visto il numero di bit con cui hai a che fare qui. Hai considerato di scrivere il codice di gestione dei dati in un linguaggio di basso livello come il C? Forse come estensione al tuo codice Python? Puoi sicuramente migliorare le prestazioni usando array di grandi dimensioni in C rispetto ai numeri grandi in Python. Detto questo, puoi riscrivere i CountBits()numeri per gestire i 10k bit aggiungendo solo 8 righe di codice. Ma diventerà ingombrante a causa di enormi costanti.
Adam Zalcman

2
È possibile scrivere codice per generare la sequenza di costanti e impostare un ciclo per l'elaborazione.
Karl Knechtel

Questa risposta ha il grande vantaggio di poter essere vettorizzata per casi che trattano numpyarray di grandi dimensioni .
gerrit

17

Ecco un'implementazione Python dell'algoritmo di conteggio della popolazione, come spiegato in questo post :

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

Funzionerà per 0 <= i < 0x100000000.


È intelligente. Cercare questo invece di sparare una risposta dal fianco è del tutto appropriato!
MrGomez

1
Lo hai valutato? Sulla mia macchina che utilizza Python 2.7, ho scoperto che in realtà è un po 'più lento di bin(n).count("1").
David Weldon

@DavidWeldon No, non l'ho fatto, potresti pubblicare i tuoi benchmark?
Ó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
Tuttavia, numberOfSetBitselabora il mio 864 × 64 numpy.ndarrayin 841 µs. Con bitCountStrdevo eseguire il loop in modo esplicito e ci vogliono 40,7 ms, o quasi 50 volte di più.
gerrit

8

Secondo questo post , questa sembra essere una delle implementazioni più veloci del peso di Hamming (se non ti dispiace usare circa 64 KB di memoria).

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

Su Python 2.x dovresti sostituire rangecon xrange.

modificare

Se hai bisogno di prestazioni migliori (ei tuoi numeri sono grandi interi), dai un'occhiata alla GMPlibreria. Contiene implementazioni di assembly scritte a mano per molte architetture diverse.

gmpy è un modulo di estensione Python con codice C che racchiude la libreria GMP.

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

Ho modificato la mia domanda per chiarire che ne ho bisogno per numeri grandi (10k bit e altro). l'ottimizzazione di qualcosa per interi a 32 bit probabilmente non farebbe molta differenza poiché il numero di conteggi dovrebbe essere davvero grande, nel qual caso ciò causerebbe un tempo di esecuzione lento.
zidarsk8

Ma GMP è esattamente per numeri molto grandi, inclusi numeri pari e ben oltre le dimensioni menzionate.
James Youngman

1
L'utilizzo della memoria sarà migliore se lo usi array.arrayper POPCOUNT_TABLE16, poiché verrà memorizzato come un array di numeri interi, invece che come un elenco di intoggetti Python di dimensioni dinamiche .
gsnedders

6

Mi piace molto questo metodo. È semplice e abbastanza veloce ma non è limitato nella lunghezza in bit poiché python ha numeri interi infiniti.

In realtà è più astuto di quanto sembri, perché evita di perdere tempo a scansionare gli zeri. Ad esempio, ci vorrà lo stesso tempo per contare i bit impostati in 1000000000000000000000010100000001 come in 1111.

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

sembra fantastico, ma va bene solo per interi molto "sparsi". in media è piuttosto lento. Tuttavia, sembra davvero utile in alcuni casi d'uso.
zidarsk8

Non sono abbastanza sicuro di cosa intendi per "in media è piuttosto lento". Abbastanza lento rispetto a cosa? Intendi lento rispetto a qualche altro codice Python che non stai citando? È due volte più veloce del conteggio bit per bit per il numero medio. In effetti sul mio macbook conta 12,6 milioni di bit al secondo, che è molto più veloce di quanto io possa contarli. Se hai un altro algoritmo python generico che funziona per qualsiasi lunghezza di numero intero ed è più veloce di questo, mi piacerebbe sentirlo.
Robotbugs

1
Accetto che in realtà sia più lento della risposta di Manuel sopra.
Robotbugs

Abbastanza lento in media significa che il conteggio di bit per 10000 numeri con 10000 cifre richiede 0,15 secondi bin(n).count("1")ma ci sono voluti 3,8 secondi per la tua funzione. Se i numeri avessero pochissimi bit impostati, funzionerebbe velocemente, ma se prendi un numero casuale, in media la funzione sopra sarà più lenta di ordini di grandezza.
zidarsk8

OK, lo accetterò. Immagino di essere stato solo un coglione perché sei un po 'impreciso ma hai perfettamente ragione. Semplicemente non avevo testato il metodo usando il metodo di Manuel sopra prima del mio commento. Sembra molto goffo ma in realtà è molto veloce. Ora sto usando una versione del genere ma con 16 valori nel dizionario ed è anche molto più veloce di quella citata. Ma per la cronaca stavo usando il mio in un'applicazione con solo pochi bit che erano impostati su 1. Ma per bit totalmente casuali sì, andrà a circa 50:50 con una piccola variazione che diminuisce con la lunghezza.
Robotbugs

3

È possibile utilizzare l'algoritmo per ottenere la stringa binaria [1] di un numero intero ma invece di concatenare la stringa, contando il numero di uno:

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


Funziona velocemente. C'è un errore, almeno su p3, il [1:] dovrebbe essere [2:] perché oct () restituisce '0o' prima della stringa. Il codice viene eseguito molto più velocemente se usi hex () invece di oct () e crei un dizionario a 16 voci,
Robotbugs

2

Hai detto che Numpy era troppo lento. Lo stavi usando per memorizzare singoli bit? Perché non estendere l'idea di utilizzare int come array di bit ma utilizzare Numpy per memorizzarli?

Memorizza n bit come array di ceil(n/32.) int a 32 bit. Puoi quindi lavorare con l'array numpy nello stesso modo (beh, abbastanza simile) in cui usi gli int, incluso il loro utilizzo per indicizzare un altro array.

L'algoritmo è fondamentalmente quello di calcolare, in parallelo, il numero di bit impostati in ogni cella e sommare il numero di bit di ciascuna cella.

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

Anche se sono sorpreso che nessuno ti abbia suggerito di scrivere un modulo 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

Si scopre che la tua rappresentazione iniziale è un elenco di elenchi di int che sono 1 o 0. Contali semplicemente in quella rappresentazione.


Il numero di bit in un intero è costante in Python.

Tuttavia, se vuoi contare il numero di bit impostati, il modo più veloce è creare un elenco conforme al seguente pseudocodice: [numberofsetbits(n) for n in range(MAXINT)]

Ciò fornirà una ricerca temporale costante dopo aver generato l'elenco. Vedi la risposta di @ PaoloMoretti per una buona implementazione di questo. Ovviamente, non devi tenere tutto in memoria: potresti usare una sorta di archivio persistente di valori-chiave, o anche MySql. (Un'altra opzione sarebbe quella di implementare la tua semplice archiviazione basata su disco).


@StevenRumbalski In che modo è inutile?
Marcin

Quando ho letto la tua risposta conteneva solo la tua prima frase: "Il numero di bit in un intero è costante in Python".
Steven Rumbalski

Ho già una tabella di ricerca del conteggio dei bit per tutti i conteggi che è possibile memorizzare, ma avere un lungo elenco di numeri e operare su di essi con un [i] e a [j], rende la tua soluzione inutile a meno che non abbia 10+ GB di ram. matrice per & ^ | per tripli di 10000 numeri sarebbe 3 * 10000 ^ 3 la dimensione della tabella di ricerca. dato che non so di cosa avrò bisogno, ha più senso contare solo le poche migliaia quando ne ho bisogno
zidarsk8

@ zidarsk8 Oppure, potresti usare un qualche tipo di database o archivio persistente di valori-chiave.
Marcin

@ zidarsk8 10 + GB di RAM non sono incredibilmente grandi. Se vuoi eseguire calcoli numerici veloci, non è irragionevole usare ferro medio-grande.
Marcin
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.