Быстрый способ подсчета ненулевых бит в положительном целом числе


117

Мне нужен быстрый способ подсчета количества бит в целом числе в Python. Мое текущее решение

bin(n).count("1")

но мне интересно, есть ли более быстрый способ сделать это?

PS: (я представляю большой двумерный двоичный массив как единый список чисел и выполняю побитовые операции, и это сокращает время с часов до минут. И теперь я хотел бы избавиться от этих лишних минут.

Изменить: 1. он должен быть в Python 2.7 или 2.6

и оптимизация для небольших чисел не имеет большого значения, поскольку это не будет явным узким местом, но у меня есть числа с более чем 10000 битами в некоторых местах

например, это 2000-битный случай:

12448057941136394342297748548545082997815840357634948550739612798732309975923280685245876950055614362283769710705811182976142803324242407017104841062064840113262840137625582646683068904149296501029754654149991842951570880471230098259905004533869130509989042199261339990315125973721454059973605358766253998615919997174542922163484086066438120268185904663422979603026066685824578356173882166747093246377302371176167843247359636030248569148734824287739046916641832890744168385253915508446422276378715722482359321205673933317512861336054835392844676749610712462818600179225635467147870208L


1
Какое представление вы используете, если ваши «целые числа» длиннее стандартного питона int? Разве у этого нет собственного метода расчета этого?
Marcin

1
возможное дублирование битов Count целого числа в Python
endolith

3
Чтобы отличить вопрос от вопроса в stackoverflow.com/a/2654211/1959808 (если предполагается, что он будет другим - по крайней мере, так выглядит), пожалуйста, подумайте о перефразировании заголовка на «... подсчет количества не- нулевые биты ... »или подобное. В противном случае int.bit_lengthдолжен быть ответ, а не тот, который принят ниже.
Иоаннис Филиппидис

Ответы:


121

Для целых чисел произвольной длины bin(n).count("1")это самое быстрое, что я мог найти в чистом Python.

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

С другой стороны, gmpy popcount() занял около 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массива.
gerrit

Для массивов numpy я бы посмотрел примерно так: gist.github.com/aldro61/f604a3fa79b3dec5436a
kindall

1
Я использовал bin(n).count("1"). Однако это превосходит только 60% представления Python. @ leetcode
northtree

30

Вы можете адаптировать следующий алгоритм:

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массивами.
gerrit

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.
gerrit

7
Однако numberOfSetBitsобрабатывает мой 864 × 64 numpy.ndarrayза 841 мкс. С bitCountStrя должен явно зацикливаться, и это занимает 40,7 мс, или почти в 50 раз больше.
gerrit

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 с кодом C, который является оболочкой для библиотеки GMP.

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

Я отредактировал свой вопрос, чтобы пояснить, что мне нужно это для больших чисел (10k бит и более). оптимизация чего-либо для 32-битных целых чисел, вероятно, не будет иметь большого значения, поскольку количество отсчетов должно быть очень большим, и в этом случае это приведет к медленному времени выполнения.
zidarsk8

Но GMP предназначен именно для очень больших чисел, в том числе для чисел, не превышающих указанных вами.
Джеймс Янгман

1
Использование памяти будет лучше, если вы используете array.arrayfor 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, который работает для любой длины целого числа и быстрее этого, я бы хотел услышать об этом.
Robotbugs

1
Я согласен с тем, что на самом деле это медленнее, чем ответ Мануэля выше.
Robotbugs

В среднем довольно медленно означает, что подсчет бит для 10000 чисел с 10000 цифрами занимает 0,15 с, bin(n).count("1")но для вашей функции потребовалось 3,8 с. Если бы в числах было установлено очень мало битов, он работал бы быстро, но если вы возьмете любое случайное число, в среднем вышеуказанная функция будет работать на порядки медленнее.
zidarsk8

Хорошо, я приму это. Думаю, я просто был придурком, потому что ты немного неточный, но ты совершенно прав. Я просто не тестировал метод с использованием метода Мануэля выше до своего комментария. Это выглядит очень неуклюже, но на самом деле очень быстро. Сейчас я использую такую ​​версию, но с 16 значениями в словаре, и это даже намного быстрее, чем та, которую он процитировал. Но для записи я использовал свой в приложении только с несколькими битами, которые были установлены в 1. Но для полностью случайных битов да, это будет примерно 50:50 с небольшой дисперсией, уменьшающейся с длиной.
Robotbugs

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 () и делаете словарь из 16
статей

2

Вы сказали, что Нампи был слишком медленным. Вы использовали его для хранения отдельных битов? Почему бы не расширить идею использования целых чисел как битовых массивов, но использовать Numpy для их хранения?

Храните n битов как массив ceil(n/32.)32-битных целых чисел. Затем вы можете работать с массивом numpy таким же (ну, достаточно похожим) способом, которым вы используете int, в том числе используя их для индексации другого массива.

Алгоритм заключается в параллельном вычислении количества битов, установленных в каждой ячейке, и их суммировании количества битов каждой ячейки.

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

Хотя я удивлен, что никто не предложил вам написать модуль 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

Оказывается, ваше начальное представление - это список списков целых чисел, равных 1 или 0. Просто посчитайте их в этом представлении.


Количество бит в целом числе в Python постоянно.

Однако, если вы хотите подсчитать количество установленных битов, самый быстрый способ - создать список, соответствующий следующему псевдокоду: [numberofsetbits(n) for n in range(MAXINT)]

Это обеспечит вам постоянный поиск по времени после создания списка. См. Ответ @ PaoloMoretti для хорошей реализации этого. Конечно, вам не обязательно хранить все это в памяти - вы можете использовать какое-то постоянное хранилище значений ключей или даже MySql. (Другой вариант - реализовать собственное простое дисковое хранилище).


@StevenRumbalski Как это бесполезно?
Marcin

Когда я прочитал ваш ответ, он содержал только ваше первое предложение: «Число битов в целом числе в Python постоянно».
Стивен Румбальский,

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

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

@ zidarsk8 10+ ГБ оперативной памяти шокирует не много. Если вы хотите выполнить быстрые численные вычисления, вполне разумно использовать железо среднего и большого размера.
Marcin
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.