web-dev-qa-db-fra.com

Moyen rapide de compter les bits non nuls dans un entier positif

J'ai besoin d'un moyen rapide de compter le nombre de bits dans un entier en python. Mes solutions actuelles sont

bin(n).count("1")

mais je me demande s’il existe un moyen plus rapide de le faire?

PS: (Je représente un grand tableau binaire 2D sous la forme d’une liste unique de nombres et effectue des opérations au niveau des bits, ce qui réduit le temps de quelques heures à quelques minutes. Et maintenant, je voudrais me débarrasser de ces minutes supplémentaires.

Edit: 1. il doit être dans python 2.7 ou 2.6

et l’optimisation pour les petits nombres n’a pas beaucoup d’importance car ce ne serait pas un goulot d’embouteillage clair, mais j’ai des nombres de 10 000 bits ou plus à certains endroits

par exemple, il s’agit d’un cas de 2000 bits:

12448057941136394342297748548545082997815840357634948550739612798732309975923280685245876950055614362283769710705811182976142803324242407017104841062064840113262840137625582646683068904149296501029754654149991842951570880471230098259905004533869130509989042199261339990315125973721454059973605358766253998615919997174542922163484086066438120268185904663422979603026066685824578356173882166747093246377302371176167843247359636030248569148734824287739046916641832890744168385253915508446422276378715722482359321205673933317512861336054835392844676749610712462818600179225635467147870208L
101
zidarsk8

Pour les entiers de longueur arbitraire, bin(n).count("1") est le plus rapide que j'ai pu trouver en Python pur.

J'ai essayé d'adapter les solutions d'Óscar et d'Adam pour traiter l'entier en morceaux de 64 bits et de 32 bits, respectivement. Tous deux étaient au moins dix fois plus lents que bin(n).count("1") (la version 32 bits prenait environ deux fois moins de temps).

D'autre part, gmpypopcount() a pris environ un vingtième du temps de bin(n).count("1"). Donc, si vous pouvez installer gmpy, utilisez-le.

Pour répondre à une question dans les commentaires, pour les octets, j'utiliserais une table de correspondance. Vous pouvez le générer au moment de l'exécution:

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

Ou simplement le définir littéralement:

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

Alors c'est counts[x] pour obtenir le nombre de 1 bits dans x où 0 ≤ x ≤ 255.

108
kindall

Vous pouvez adapter l'algorithme suivant:

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

Cela fonctionne pour les nombres positifs 64 bits, mais il est facilement extensible et le nombre d'opérations augmente avec le logarithme de l'argument (c'est-à-dire linéairement avec la taille en bits de l'argument).

Pour comprendre comment cela fonctionne, imaginez que vous divisiez la chaîne entière de 64 bits en 64 compartiments de 1 bit. La valeur de chaque compartiment correspond au nombre de bits définis dans le compartiment (0 si aucun bit n'est défini et 1 si un bit est défini). La première transformation aboutit à un état analogue, mais avec 32 compartiments de 2 bits chacun. Ceci est réalisé en décalant les compartiments de manière appropriée et en ajoutant leurs valeurs (une addition prend en charge tous les compartiments car aucun report ne peut se produire - les nombres à n bits sont toujours suffisamment longs pour coder le nombre n). D'autres transformations conduisent à des états avec un nombre décroissant décroissant de compartiments de taille en croissance exponentielle jusqu'à arriver à un compartiment de 64 bits de long. Cela donne le nombre de bits défini dans l'argument d'origine.

22
Adam Zalcman

Voici une implémentation Python de l'algorithme de comptage de la population, comme expliqué dans ce post :

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

Cela fonctionnera pour 0 <= i < 0x100000000.

13
Óscar López

Selon ce post , cela semble être l’une des implémentations les plus rapides du poids de Hamming (si vous n’aurez pas besoin d’utiliser environ 64 Ko de mémoire).

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

Sur Python 2.x, vous devez remplacer range par xrange.

Modifier

Si vous avez besoin de meilleures performances (et que vos nombres sont de grands entiers), jetez un œil à la bibliothèque GMP . Il contient des implémentations Assembly écrites à la main pour de nombreuses architectures différentes.

gmpy est un module d'extension C Python) qui encapsule la bibliothèque GMP.

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

J'aime beaucoup cette méthode. C'est simple et assez rapide mais pas limité en longueur de bits depuis python a des entiers infinis.

C'est en fait plus sournois qu'il n'y paraît, car cela évite de perdre du temps à analyser les zéros. Par exemple, il faudra le même temps pour compter les bits définis dans 1000000000000000000000000001001000001 comme dans 1111.

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

Vous avez dit que Numpy était trop lent. L'utilisiez-vous pour stocker des bits individuels? Pourquoi ne pas étendre l’idée d’utiliser ints en tant que matrices de bits mais utiliser Numpy pour les stocker?

Stockez n bits en tant que tableau de ceil(n/32.) ints 32 bits. Vous pouvez ensuite utiliser le tableau numpy de la même manière (de manière similaire) que vous utilisez ints, y compris en les utilisant pour indexer un autre tableau.

L’algorithme consiste essentiellement à calculer, en parallèle, le nombre de bits définis dans chaque cellule et à additionner le nombre de bits de chaque cellule.

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

Bien que je sois surpris, personne ne vous a suggéré d’écrire un module en C.

2
leewz

Vous pouvez utiliser l'algorithme pour obtenir la chaîne binaire [1] d'un entier, mais au lieu de concaténer la chaîne, en comptant le nombre de uns:

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

1
Manuel
#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)

0
Praveen Narala