Python/Numpy: преобразовать список bool в unsigned int

  1. Каков самый быстрый (или самый «питоновский») способ конвертации

    x = [False, False, True, True]
    

    в 12? (Если есть такой способ.)

  2. Что, если бы x было вместо numpy.array логических значений? Есть ли для этого специальная команда?

У меня есть большой массив логических значений размером m на n, где каждая строка из n элементов представляет собой один низкоразмерный хэш многомерного вектора признаков. (В приведенном выше примере n = 4.) Я хотел бы знать ответ, чтобы максимально сжать свои данные. Спасибо.


Изменить: спасибо за ответы! Используя следующий тестовый код,

t = 0
for iter in range(500):
    B = scipy.signbit(scipy.randn(1000,20))
    for b in B:
        t0 = time.clock()
        # test code here
        t1 = time.clock()
        t += (t1-t0)
print t

... вот время выполнения на моем ноутбуке Thinkpad:

Конечно, я приветствую любые независимые тесты, которые могут подтвердить или опровергнуть мои данные!


Изменить: в моем ответе ниже замена int(j) на просто j по-прежнему работает, но работает в шесть раз медленнее! Тогда, возможно, другие ответы станут быстрее, если логическое значение будет приведено с использованием int. Но мне лень все заново тестировать.


Изменить: liori опубликовал результаты независимых тестов здесь.


person Steve Tjoa    schedule 31.10.2010    source источник
comment
Каково правило преобразования [False, False, True, True] в 12?   -  person    schedule 01.11.2010
comment
x[0] — это младший бит, а x[-1] — это старший бит.   -  person Steve Tjoa    schedule 01.11.2010
comment
Пожалуйста, используйте timeit для тестирования, это гораздо менее подвержено ошибкам. Мое время: pastebin.com/x1FEP9gY   -  person liori    schedule 01.11.2010
comment
Спасибо за тесты! Я в них совершенно не сомневаюсь. Я добавил их в пост.   -  person Steve Tjoa    schedule 01.11.2010
comment
Стоит отметить: в тесте liori sven2() с треском проваливается, потому что мы используем 1000-битные числа. Проверьте результаты (например, числа, возвращаемые каждой функцией), и вы увидите, что ее результат неверен для такого большого числа.   -  person Justin Peel    schedule 01.11.2010
comment
@Justing Peel. Хотя в целом вы совершенно правы, если я что-то не упустил, в примере используются 20-битные числа, не 1000-битные! Не должно быть проблем с 20 столбцами в методах на основе numpy....   -  person Joe Kington    schedule 01.11.2010
comment
Я думаю, он имеет в виду тесты, которые опубликовал Лиори. Длина вектора составляет 1000 и проверено более 1000 испытаний.   -  person Steve Tjoa    schedule 01.11.2010
comment
@Steve - А, в этом больше смысла! Спасибо, и извините за шум!   -  person Joe Kington    schedule 01.11.2010


Ответы (10)


Взяв различные идеи из разных других ответов, вот еще один способ сделать это:

sum(1<<i for i, b in enumerate(x) if b)

В моих тестах это довольно быстро - вплоть до метода numpy для большого количества битов, даже если он переполняется как сумасшедший. Я использовал тестовый модуль liori для тестирования. Метод Стива с предложенным мною изменением чуть быстрее. Однако, если нужно выполнять много таких преобразований за раз (и с не слишком большим количеством битов), я уверен, что numpy будет быстрее.

person Justin Peel    schedule 01.11.2010
comment
@КенниТМ. Умно, но я профилировал оригинал примерно на 20% быстрее. Это самый быстрый на сегодняшний день. - person aaronasterling; 01.11.2010

Большинство Pythonic может быть таким:

sum(2**i*b for i, b in enumerate(x))

Трудно сказать, является ли он также самым быстрым.

В numpy я бы использовал

numpy.sum(2**numpy.arange(len(x))*x)

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

person Sven Marnach    schedule 31.10.2010
comment
Спасибо! Для некоторых размеров массивов второе решение работало достаточно хорошо, а для других — нет. - person Steve Tjoa; 01.11.2010
comment
@Steve. Другое преимущество решения numpy заключается в том, что вы можете избежать повторения каждой строки. Используя массив B из приведенного выше тестового кода: numpy.sum(2**numpy.arange(B.shape[1])*B, axis=1). Это должно дать большое ускорение по сравнению с перебором каждой строки в массиве... Полный цикл 500x выполняется менее чем за секунду на моей машине... - person Joe Kington; 01.11.2010
comment
Поскольку numpy не обрабатывает большие целые числа так же, как Python, вы должны быть осторожны с действительно большими числами. Если будут большие числа, вы можете получить немного больше от этого метода, выполнив dtype=numpy.longlong в arange(). Кроме того, существует очень, очень небольшое ускорение за счет использования метода суммы результирующего массива numpy, а не использования numpy.sum. - person Justin Peel; 01.11.2010

reduce(lambda a,b:2*a+b, reversed(x))

Вы можете избавиться от reversed(), если у вас есть младший бит в конце массива. Это также работает с numpy.array и не требует enumerate(). Судя по моим тестам, тоже быстрее: нет необходимости использовать возведение в степень.

person liori    schedule 31.10.2010
comment
Спасибо за изящное решение! Я был потрясен, когда впервые увидел это. К сожалению, кажется, что он работает медленнее всего, с reversed или без него. Может кто знает почему? - person Steve Tjoa; 01.11.2010
comment
@Steve: на моем компьютере это быстрее, чем сумма + возведение в степень. Забавно... как долго вы используете векторы? Вы тестируете производительность с помощью timeit? - person liori; 01.11.2010

Моя первая попытка, просто для справки:

def bool2int(x):
    y = 0
    for i,j in enumerate(x):
        if j: y += int(j)<<i
    return y
person Steve Tjoa    schedule 01.11.2010
comment
Подождите, это интересно: замена int(j) на просто j по-прежнему работает, но работает в шесть раз медленнее! - person Steve Tjoa; 01.11.2010
comment
Если вы просто измените int(j) на 1, ваш будет самым быстрым. - person Justin Peel; 01.11.2010

Элегантный, питонический, всегда работающий способ:

def powers(x):
    """yield powers of x, starting from x**0 forever"""
    power = 1
    while True:
        yield power
        power *= x

def bools_to_int(bools):
    # in Python 2, use itertools.izip!
    return sum(int(place) * place_weight for place_weight, place in 
               zip(powers(2), bools))

Обратите внимание, что вы можете избавиться от powers (путем перечисления и возведения в квадрат понимания, как это делают другие ответы), но, возможно, так будет понятнее.

person Community    schedule 31.10.2010
comment
Ваш ответ не дает того же ответа, что и другие. Замена bools на reversed(bools) исправляет это. - person Justin Peel; 01.11.2010
comment
@Justin Peel: Придешь еще? Я уже заметил, что вскоре после ответа добавил reversed... - person ; 01.11.2010
comment
попробуйте код, который у вас есть здесь, с примером, приведенным ОП. Я получаю 3 в качестве ответа, когда должно быть 12. Вам не нужно было вставлять reversed. - person Justin Peel; 01.11.2010
comment
бьется головой о стену @Justin: Да, ты прав, и теперь я понимаю, почему. - person ; 01.11.2010

Что-то вроде этого?

>>> x = [False, False, True, True]
>>> sum([int(y[1])*2**y[0] for y in enumerate(x)])
12

Вы можете преобразовать массив numpy в обычный список, используя приведение list().

>>> a = numpy.array([1,2,3,4])
>>> a
array([1, 2, 3, 4])
>>> list(a)
[1, 2, 3, 4]
person Emil H    schedule 31.10.2010
comment
0**0 равен 1, поэтому вы получаете ошибку «один за другим», если первый элемент равен False. - person liori; 01.11.2010
comment
@liori, я не думаю, что это применимо к моему коду, поскольку я нигде этого не делаю? Хотя все равно интересно. Не знал этого. - person Emil H; 01.11.2010
comment
int(False)*2==0. Первый индекс, заданный enumerate, равен 0. - person liori; 01.11.2010
comment
@liori, да, но я не преувеличиваю это значение. Мой код делает i * 2^j. Для первого бита i * 2^0 = i*1 = i - person Emil H; 01.11.2010
comment
Ладно, позор мне. Я перепутал правила приоритета :-). - person liori; 01.11.2010
comment
@liori, нет проблем. В конце концов, ваше решение все равно было более элегантным. :) - person Emil H; 01.11.2010

Если у вас есть матрица, вы, вероятно, захотите сделать это так:

#precompute powers of two
vals = 2.**np.arange(20)

B = ....
compressed = np.dot(B, vals) # matrix multiplication.

np.dot должен быть быстрее любого цикла в Python. Намного быстрее.

person luispedro    schedule 10.11.2010

Я пробовал ipython %timeit, и кажется, что быстрее сделать следующее:

y = 0
for i,j in enumerate(x):
    if j: y += 1<<i

Кроме того, если ваш логический вектор представляет собой numpy.ndarray, преобразование его в массив python x.tolist() и запуск того же, похоже, в этом случае работает быстрее. Все это маргинально, но последовательно, а на таких скоростях маргиналы хорошо складываются.

person Atreya    schedule 23.08.2013

Для этого в numpy есть функция packbits. Он также поддерживает операции вдоль осей:

In [3]: B = scipy.signbit(scipy.randn(1000,8)).astype("i1")

In [3]: B[0]
Out[3]: array([0, 1, 0, 0, 0, 1, 0, 0], dtype=int8)

In [4]: np.packbits(B[0])
Out[4]: array([68], dtype=uint8)

In [5]: %timeit np.packbits(B, axis=1)
10000 loops, best of 3: 37 µs per loop

он работает для размеров int8 для больших размеров, которые вам нужно сдвинуть и/или

In [8]: x # multiple of 8
Out[8]: array([1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1], dtype=int8)

In [9]: r = np.packbits(x).astype(np.int32); r
Out[9]: array([171, 129], dtype=uint8)

In [10]: r[0] << 8 | r[1] 
Out[10]: 33237

In [11]: sum(1<<i for i, b in enumerate(x[::-1]) if b)
Out[11]: 33237

если x не кратно 8, вы должны заполнить нулями

person jtaylor    schedule 19.07.2014

Если вы хотите добавить еще одно расширение, я добавил pack() и unpack() в ветку разработки gmpy. Мои тесты показывают, что это может быть в 2 или 3 раза быстрее.

>>> import gmpy2
>>> gmpy2.pack([0,0,1,1],1)
mpz(12)
>>> gmpy2.unpack(12,1)
[mpz(0), mpz(0), mpz(1), mpz(1)]

Отказ от ответственности: разрабатываемая версия называется gmpy2 и может сосуществовать со стабильной версией. Он все еще находится в альфа-фазе, но, надеюсь, станет бета-версией через несколько недель. У вас должны быть установлены библиотеки GMP и MPFR. Исходный код доступен по адресу http://code.google.com/p/gmpy/source/checkout

person casevh    schedule 01.11.2010