Как повернуть двоичный вектор к минимуму в Python
Если у меня есть произвольный двоичный вектор (массив numpy) в Python, например
import numpy as np
vector = np.zeros((8,1))
vector[2,1] = 1
vector[3,1] = 1
это даст мне двоичный массив 00001100. У меня также может быть 00000000 или 00010100 и т. д. Как сделать такой скрипт, что когда я даю этот двоичный вектор в качестве входа, скрипт дает минимальный двоичный массив numpy с правым поворотом в качестве выхода? Несколько примеров:
00010000 --> 00000001
10100000 --> 00000101
11000001 --> 00000111
00000000 --> 00000000
11111111 --> 11111111
10101010 --> 01010101
11110000 --> 00001111
00111000 --> 00000111
10001111 --> 00011111
etc. Любые предложения / хорошие оптимизированные реализации Python в виду? =) Спасибо за любую помощь. Мне нужно это для локальной реализации двоичного шаблона=)
5 ответов
самый быстрый способ сделать это-сначала создать таблицу, а затем вы можете использовать индексирование ndarray для получения результата, вот код:
вам нужно создать таблицу самостоятельно, код здесь просто демо
import numpy as np
np.random.seed(0)
#create the table
def rotated(s):
for i in range(len(s)):
s2 = s[i:] + s[:i]
if s2[-1] == "1":
yield int(s2, 2)
bitmap = []
for i in range(256):
s = "{:08b}".format(i)
try:
r = min(rotated(s))
except ValueError:
r = i
bitmap.append(r)
bitmap = np.array(bitmap, np.uint8)
затем мы можем использовать bitmap
и numpy.packbits()
и numpy.unpackbits()
:
a = np.random.randint(0, 2, (10, 8))
a = np.vstack((a, np.array([[1,1,0,0,0,0,0,1]])))
b = np.unpackbits(bitmap[np.packbits(a, axis=1)], axis=1)
print a
print
print b
вот вывод:
[[0 1 1 0 1 1 1 1]
[1 1 1 0 0 1 0 0]
[0 0 0 1 0 1 1 0]
[0 1 1 1 1 0 1 0]
[1 0 1 1 0 1 1 0]
[0 1 0 1 1 1 1 1]
[0 1 0 1 1 1 1 0]
[1 0 0 1 1 0 1 0]
[1 0 0 0 0 0 1 1]
[0 0 0 1 1 0 1 0]
[1 1 0 0 0 0 0 1]]
[[0 1 1 0 1 1 1 1]
[0 0 1 0 0 1 1 1]
[0 0 0 0 1 0 1 1]
[0 0 1 1 1 1 0 1]
[0 1 0 1 1 0 1 1]
[0 1 0 1 1 1 1 1]
[0 0 1 0 1 1 1 1]
[0 0 1 1 0 1 0 1]
[0 0 0 0 0 1 1 1]
[0 0 0 0 1 1 0 1]
[0 0 0 0 0 1 1 1]]
попробуйте это:
v = np.array([0,0,1,1,1,0,0,0]) #testing value
count = 0
def f(x):
global count
if x: count = 0
else: count += 1
return count
uf = np.vectorize(f)
v = np.array([0,0,1,1,1,0,0,0])
v2 = np.concatenate((v,v))
vs = uf(v2)
i = vs.argmax()
m = vs[i]
rot = i-m + 1
print np.roll(v2,-rot)[:v.size] #output: [0 0 0 0 0 1 1 1]
Я не уверен, что numpy предоставляет это, но странно, что никто из парней numpy не ответил До сих пор.
Если нет уже встроенного способа сделать это, я бы пошел об этом так:
- преобразовать массив в int
- затем выполните вращение над чистым int и проверьте минимум
- преобразовать обратно в массив
таким образом, ваши вращения массива сводятся к бит-сдвигам, которые должны быть довольно быстрый.
Если ваши bitarrays имеют размер для образцов, я думаю, этого может быть достаточно (у меня нет numpy на руках, но логика должна быть такой же):
#! /usr/bin/python3
def array2int (a):
i = 0
for e in a: i = (i << 1) + e
return i
def int2array (i, length):
return [ (i >> p) & 1 for p in range (length - 1, -1, -1) ]
def rot (i, length):
return ( (i & ((1 << (length - 1) ) - 1) ) << 1) | (i >> (length - 1) )
def rotMin (a):
length = len (a)
minn = i = array2int (a)
for _ in range (length):
i = rot (i, length)
if i < minn: minn = i
return int2array (minn, length)
#test cases
for case in (16, 160, 193, 0, 255, 170, 240, 56, 143):
case = int2array (case, 8)
result = rotMin (case)
print ("{} -> {}".format (case, result) )
если они намного длиннее, вы, возможно, захотите сначала найти самые длинные прогоны нулей, а затем проверить только те случаи, которые начинаются с такого прогона.
вывод:
[0, 0, 0, 1, 0, 0, 0, 0] -> [0, 0, 0, 0, 0, 0, 0, 1]
[1, 0, 1, 0, 0, 0, 0, 0] -> [0, 0, 0, 0, 0, 1, 0, 1]
[1, 1, 0, 0, 0, 0, 0, 1] -> [0, 0, 0, 0, 0, 1, 1, 1]
[0, 0, 0, 0, 0, 0, 0, 0] -> [0, 0, 0, 0, 0, 0, 0, 0]
[1, 1, 1, 1, 1, 1, 1, 1] -> [1, 1, 1, 1, 1, 1, 1, 1]
[1, 0, 1, 0, 1, 0, 1, 0] -> [0, 1, 0, 1, 0, 1, 0, 1]
[1, 1, 1, 1, 0, 0, 0, 0] -> [0, 0, 0, 0, 1, 1, 1, 1]
[0, 0, 1, 1, 1, 0, 0, 0] -> [0, 0, 0, 0, 0, 1, 1, 1]
[1, 0, 0, 0, 1, 1, 1, 1] -> [0, 0, 0, 1, 1, 1, 1, 1]
используя numpy
Я не могу полностью избежать итерации, но я могу ограничить ее наименьшим размером, количеством возможных вращений (8). Я нашел несколько вариантов. Я подозреваю, что последний самый быстрый, но я не делал тестов времени. Основная идея состоит в том, чтобы собрать все возможные вращения в массив и выбрать минимальное значение из них.
x=[[0, 0, 0, 1, 0, 0, 0, 0],
[1, 0, 1, 0, 0, 0, 0, 0],
[1, 1, 0, 0, 0, 0, 0, 1],
...
[1, 0, 0, 0, 1, 1, 1, 1]]
x = np.array(x)
M,N = x.shape
j = 2**np.arange(N)[::-1] # powers of 2 used convert vector to number
# use np.dot(xx,j) to produce a base 10 integer
A) в первой версии я собираю вращения в 3D массив
xx = np.zeros([M, N, N],dtype=int)
for i in range(N):
xx[:,i,:] = np.roll(x, i, axis=1)
t = np.argmin(np.dot(xx, j), axis=1) # find the mimimum index
print t
print xx[range(M),t,:]
выдает:
[4 5 6 0 0 1 4 3 7]
[[0 0 0 0 0 0 0 1]
[0 0 0 0 0 1 0 1]
[0 0 0 0 0 1 1 1]
...
[0 0 0 1 1 1 1 1]]
B) вариацией было бы хранить np.dot(xx, j)
значения в 2D-массиве и преобразуют минимум каждой строки обратно в массив из 8 столбцов.
xx = x.copy()
for i in range(N):
y = np.roll(x, i, axis=1)
xx[:,i] = np.dot(y, j)
y = np.min(xx, axis=1)
print y
# [4 5 6 0 0 1 4 3 7]
# convert back to binary
z = x.copy()
for i in range(N):
z[:,i] = y%2
y = y//2
z = np.fliplr(z)
print z
я не смог найти numpy
способ преобразования вектора чисел в двоичный массив. Но при N намного меньше M этот итерационный подход не является дорогостоящим. numpy.base_repr
использует это, но работает только на скалярах. [int2array
и np.unpackbits
используется в других ответах быстрее.]
C) еще лучше, я мог бы свернуть j
, а не x
:
xx = x.copy()
for i in range(N):
xx[:,i] = np.dot(x, np.roll(j,i))
y = np.min(xx, axis=1)
print y
D) возможно дальнейшее ускорение путем построения массива повернутых j
, и делать продукт точки как раз раз раз. Возможно, удастся построить jj
без итерации, но создание массива 8x8 только один раз не дорого.
jj = np.zeros([N,N], dtype=int)
for i in range(N):
jj[:,i] = np.roll(j,i)
print jj
xx = np.dot(x, jj)
# or xx = np.einsum('ij,jk',x,jj)
y = np.min(xx, axis=1)
print y
примечания по времени:
для маленьких x
, как строки образца 9, первое решение (а) самый быстрый. Преобразование целых чисел обратно в двоичные занимает 1/3 времени, замедляя другие решения.
на x
, как 10000 строк, последнее самое лучшее. С IPython timeit
A) 10 loops, best of 3: 22.7 ms per loop
B) 100 loops, best of 3: 13.5 ms per loop
C) 100 loops, best of 3: 8.21 ms per loop
D) 100 loops, best of 3: 6.15 ms per loop
# Hyperboreous: rotMin(x1)
# adapted to work with numpy arrays
H) 1 loops, best of 3: 177 ms per loop
в какой-то момент я подумал, что могу получить скорость, выборочно вращая строки только до тех пор, пока они не достигнут своего минимального значения. Но эти добавленные образцы показывают, что я не могу использовать локальный минимум:
[1, 0, 1, 0, 1, 0, 0, 0],
[1, 0, 0, 1, 1, 0, 0, 0],
[1, 0, 0, 1, 0, 0, 0, 1],
[0, 1, 0, 1, 0, 1, 0, 0],
[0, 1, 0, 1, 0, 0, 1, 0],
тегом xx
значения
[168 81 162 69 138 21 42 84]
[152 49 98 196 137 19 38 76]
[145 35 70 140 25 50 100 200]
[ 84 168 81 162 69 138 21 42]
[ 82 164 73 146 37 74 148 41]
но обратите внимание, что минимальная для каждой из этих строк является первым 0
самого длинного пробега 0s
. Таким образом, можно найти минимум без выполнения всех поворотов и преобразования в числовое значение.
Вы можете использовать двусторонней очередью коллекции, функция поворота, если вы преобразовать массив в список, вы можете преобразовать его обратно в массив, откуда вы сделали с вашей ротации.
import numpy as np
import collections #import collections and deque
from collections import deque
vector = np.array([1, 1,0]) # example array
list = vector.tolist() # use tolist() to convert the array "vector" to a list
n = collections.deque(list) # convert the list to a deque
#rotate two closest
print n.rotate(1)
>>> deque([1, 1, 0])
#rotate three closest
print n.rotate(2)
deque([1, 0, 1])
n.rotate(-1)
повернуть назад от последнего вращения