Как повернуть двоичный вектор к минимуму в 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 не ответил До сих пор.

Если нет уже встроенного способа сделать это, я бы пошел об этом так:

  1. преобразовать массив в int
  2. затем выполните вращение над чистым int и проверьте минимум
  3. преобразовать обратно в массив

таким образом, ваши вращения массива сводятся к бит-сдвигам, которые должны быть довольно быстрый.

Если ваши 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) повернуть назад от последнего вращения