Швидкий спосіб підрахунку ненульових бітів у натуральному цілому


117

Мені потрібен швидкий спосіб підрахунку кількості бітів у цілому цілому в python. Моє поточне рішення

bin(n).count("1")

але мені цікаво, чи існує якийсь швидший спосіб зробити це?

PS: (Я представляю великий двовимірний двійковий масив у вигляді єдиного списку чисел і роблю побітові операції, і це зводить час з годин на хвилини. Тепер я хотів би позбутися цих додаткових хвилин.

Редагувати: 1. він повинен бути в python 2.7 або 2.6

і оптимізація для невеликих чисел не має великого значення, оскільки це не було б чітким вузьким місцем, але у мене є цифри з 10 000 + бітами в деяких місцях

наприклад, це 2000-бітний випадок:

12448057941136394342297748548545082997815840357634948550739612798732309975923280685245876950055614362283769710705811182976142803324242407017104841062064840113262840137625582646683068904149296501029754654149991842951570880471230098259905004533869130509989042199261339990315125973721454059973605358766253998615919997174542922163484086066438120268185904663422979603026066685824578356173882166747093246377302371176167843247359636030248569148734824287739046916641832890744168385253915508446422276378715722482359321205673933317512861336054835392844676749610712462818600179225635467147870208L


1
Яке представлення ви використовуєте, якщо ваші "цілі числа" довші, ніж у стандартного пітона int? Хіба це не має свого методу для обчислення цього?
Марцін


3
Щоб відрізнити це питання від питання в stackoverflow.com/a/2654211/1959808 (якщо він призначений бути різним --- принаймні, так це виглядає), будь ласка, спробуйте перефразувати заголовок у "... підрахунок кількості не- нульові біти ... ”або подібне. Інакше int.bit_lengthмає бути відповідь, а не та, яку прийнято нижче.
Іоанніс Філіппідіс

Відповіді:


121

Для цілих цілей довільної довжини bin(n).count("1")- це найшвидший, який я міг знайти в чистому Python.

Я спробував адаптувати рішення Оскара та Адама для обробки цілого числа в 64-бітних та 32-бітових фрагментах відповідно. Обидва були принаймні в десять разів повільнішими bin(n).count("1")(32-бітна версія забирала приблизно вдвічі більше часу).

З іншого боку, gmpy popcount() зайняла близько 1/20-го часу 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')

Тоді counts[x]потрібно отримати кількість 1 біт, xде 0 ≤ x ≤ 255.


7
+1! Обернення цього не є точним, однак слід зазначити: bin(n).count("0")не точне через префікса "0b". Потрібно було б bin(n)[2:].count('0')для тих, хто рахує нуди ....
вовк

11
Ви дійсно не можете порахувати нульові біти, не знаючи, скільки байтів ви заповнюєте, однак це проблематично з довгим цілим числом Python, оскільки це може бути все, що завгодно.
kindall

2
Хоча це швидкі варіанти для одиничних цілих чисел, зауважте, що алгоритми, представлені в інших відповідях, можуть бути потенційно векторизовані, таким чином, набагато швидше, якщо працювати на багатьох елементах великого numpyмасиву.
Герріт

Для nummy масивів я розглядаю щось на зразок цього: 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, якщо встановлено один біт). Перша трансформація призводить до аналогічного стану, але з 32 відрами довжиною в 2 біти. Це досягається відповідним зміщенням відра та додаванням їх значень (одне доповнення піклується про всі відра, оскільки жодне перенесення не може відбуватися через відра - n-бітове число завжди досить довге, щоб кодувати число n). Подальші перетворення призводять до станів із експоненціально зменшенням кількості відер експоненціально зростаючого розміру, поки ми не отримаємо одне 64-бітове довге відро. Це дає кількість бітів, встановлених у вихідному аргументі.


Я серйозно не маю уявлення, як це буде працювати з 10 000 бітовими номерами, але мені подобається рішення. Ви можете дати мені підказку, якщо і як я можу це застосувати до більшої кількості?
zidarsk8

Я не бачив кількості бітів, з якими ви тут маєте справу. Чи думали ви писати свій код обробки даних мовою низького рівня, як C? Можливо, як розширення вашого коду python? Ви, звичайно, можете покращити продуктивність, використовуючи великі масиви на C порівняно з великими цифрами в python. Однак, ви можете переписати CountBits()на обробку 10-бітових чисел, додавши лише 8 рядків коду. Але це стане непростим через величезні константи.
Адам Зальцман

2
Ви можете написати код для генерації послідовності констант та встановити цикл для обробки.
Карл Кнечтел

Ця відповідь має велику перевагу в тому, що її можна векторизувати для випадків, що стосуються великих numpyмасивів.
Герріт

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").
Девід Уелдон

@DavidWeldon Ні, я цього не зробив, чи не могли б ви розмістити свої орієнтири?
Оскар Лопес

%timeit numberOfSetBits(23544235423): 1000000 loops, best of 3: 818 ns per loop; %timeit bitCountStr(23544235423): 1000000 loops, best of 3: 577 ns per loop.
Герріт

7
Однак numberOfSetBitsобробляє мої 864 × 64 numpy.ndarrayза 841 мкс. З цим bitCountStrя маю чітко циклічно, і це займає 40,7 мс, або майже в 50 разів більше.
Герріт

8

Згідно з цим повідомленням , здається, це одна з найшвидших реалізацій ваги Хеммінга (якщо ви не заперечуєте, використовуючи близько 64 КБ пам'яті).

#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 являє собою модуль розширення Python, кодований С, який охоплює бібліотеку GMP.

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

Я відредагував своє запитання, щоб зрозуміти, що мені потрібно це для великої кількості (10 К біт і більше). оптимізація чогось для 32-бітових цілих чисел, ймовірно, не призведе до великої різниці, оскільки кількість підрахунків повинна бути справді великою, і в цьому випадку це призведе до повільного часу виконання.
zidarsk8

Але GMP призначений саме для дуже великої кількості, включаючи цифри на рівні та значно вищі розміри, про які ви згадуєте.
Джеймс Янгмен

1
Використання пам'яті буде краще, якщо ви використовуєте array.arrayдля POPCOUNT_TABLE16, оскільки тоді він буде зберігатися як масив цілих чисел, а не як динамічно розміщений список intоб'єктів Python .
gsnedders

6

Мені дуже подобається цей метод. Його простий і досить швидкий, але також не обмежений у довжині біт, оскільки python має нескінченні цілі числа.

Насправді це хитріше, ніж це виглядає, оскільки дозволяє уникнути витрачання часу на сканування нулів. Наприклад, знадобиться той самий час, щоб підрахувати встановлені біти в 1000000000000000000000010100000001, як і в 1111.

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

виглядає чудово, але це добре лише для дуже "розріджених" цілих чисел. в середньому це досить повільно. Тим не менш, це виглядає дійсно корисно в певних випадках використання.
zidarsk8

Я не зовсім впевнений, що ви маєте на увазі під "в середньому це досить повільно". Досить повільний порівняно з чим? Ви маєте на увазі повільність порівняно з іншим кодом python, який ви не цитуєте? Це вдвічі швидше, ніж підрахунок побіжно за середньою кількістю. Насправді в моїй книзі macbook вона нараховує 12,6 мільйона біт в секунду, що набагато швидше, ніж я можу їх порахувати. Якщо у вас є інший загальний алгоритм python, який працює для будь-якої цілої довжини і швидший, ніж це, я хотів би почути про це.
Роботи

1
Я погоджуюсь, що це насправді повільніше, ніж відповідь Мануеля вище.
Роботи

Досить повільно в середньому означає, що підрахунок бітів на 10000 чисел з 10000 цифрами займає 0,15 с, bin(n).count("1")але для вашої функції знадобилося 3,8 секунди. Якщо в числах було встановлено дуже мало бітів, воно працювало б швидко, але якщо взяти будь-яке випадкове число, то в середньому функція вище буде на порядок повільнішою.
zidarsk8

Гаразд, я прийму це. Я думаю, що я був просто хуй, тому що ти трохи неточний, але ти абсолютно правий. Я просто не випробовував метод, використовуючи метод Мануеля вище, перш ніж мій коментар. Це виглядає дуже незграбно, але насправді дуже швидко. Зараз я використовую таку версію, але з 16 значеннями у словнику, і це навіть набагато швидше, ніж та, яку він цитував. Але для запису я використовував моє в додатку із лише кількома бітами, які були встановлені на 1. Але для абсолютно випадкових бітів так, це приблизно до 50:50 з невеликою дисперсією, що зменшується з довжиною.
Роботи

3

Ви можете використовувати алгоритм, щоб отримати двійковий рядок [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" перед рядком. Код запускається набагато швидше, хоча якщо ви використовуєте hex () замість oct () та
складете

2

Ви сказали, що Numpy був надто повільним. Ви використовували його для зберігання окремих біт? Чому б не розширити ідею використання ints як бітових масивів, а використовувати Numpy для їх зберігання?

Збережіть n біт як масив ceil(n/32.)32-бітових ints. Потім ви можете працювати з numpy масивом тим же (ну, досить схожим), як ви використовуєте ints, включаючи їх використання для індексації іншого масиву.

В основному алгоритм обчислює паралельно кількість бітів, встановлених у кожній комірці, і їх підсумовують бітові рахунки кожної комірки.

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

Хоча я здивований, ніхто не запропонував вам написати модуль С.


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 Як це корисно?
Марцін

Коли я прочитав вашу відповідь, він містив лише ваше перше речення: "Кількість бітів у цілому чисті є постійним у python."
Стівен Румбальський

У мене вже є таблиця пошуку трохи підрахунків для всіх підрахунків, які можна зберігати, але, маючи великий список номерів і оперуючи ними за допомогою [i] & a [j], робить ваш soltuion марним, якщо у мене немає 10+ ГБ оперативної пам’яті. масив для & ^ | для триплетів з 10000 номерів буде розмір таблиці 3 пошуку в розмірі 3 * 10000 ^ 3. оскільки я не знаю, що мені знадобиться, має сенс просто порахувати кілька тисяч, коли мені це потрібно
zidarsk8

@ zidarsk8 Або ви можете використовувати якусь базу даних або постійну сховище ключа-значення.
Марцін

@ zidarsk8 10 + ГБ оперативної пам'яті не є надзвичайно великим. Якщо ви хочете виконати швидкі чисельні обчислення, нерозумно використовувати залізо середнього розміру.
Марцін
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.