#python #arrays #numpy #matrix
#python #массивы #numpy #матрица
Вопрос:
У меня есть матрица 3×3 нулей и матрица 2×2 единиц:
a = np.zeros((3, 3), dtype=int)
b = np.ones((2, 2), dtype=int)
array([[0, 0, 0],
[0, 0, 0],
[0, 0, 0]])
array([[1, 1],
[1, 1]])
Я хочу сгенерировать все возможные комбинации матриц из a
и b
следующим образом:
array([[1, 1, 0],
[1, 1, 0],
[0, 0, 0]])
array([[0, 1, 1],
[0, 1, 1],
[0, 0, 0]])
array([[0, 0, 0],
[1, 1, 0],
[1, 1, 0]])
array([[0, 0, 0],
[0, 1, 1],
[0, 1, 1]])
Есть ли какой-нибудь способ сделать это быстро? Я попытался использовать for
цикл для изменения значений matrix a
, но это довольно громоздко. Любая помощь будет высоко оценена.
Комментарии:
1. Я предполагаю, что вы хотите обобщить это на a = mxm и b = nxn, где m> n . Это так?
2. @MohammedAadil Да. Я предоставляю только матрицы a и b выше, чтобы сделать их более интуитивно понятными.
3. Всегда ли они будут квадратными матрицами?
4. @MohammedAadil Номера a и b также могут быть прямоугольной формы. К вашему сведению, я на самом деле пытаюсь понять механику сортировки инвентаря в RPG. Матрица a должна быть инвентаризацией, а матрица b — элементом.
5. Это легко исправить. Я добавил новый ответ ниже.
Ответ №1:
Вы можете попробовать следующее :
m1, m2 = 4, 4
n1, n2 = 2, 3
a=np.zeros((m1,m2),dtype=int)
b=np.ones((n1,n2),dtype=int)
d1 = m1 - n1 1
d2 = m2 - n2 1
for i in range(d1):
for j in range(d2):
temp = a.copy()
temp[i:i n1,j:j n2] = b
print(temp)
ВЫВОД :
[[1 1 1 0]
[1 1 1 0]
[0 0 0 0]
[0 0 0 0]]
[[0 1 1 1]
[0 1 1 1]
[0 0 0 0]
[0 0 0 0]]
[[0 0 0 0]
[1 1 1 0]
[1 1 1 0]
[0 0 0 0]]
[[0 0 0 0]
[0 1 1 1]
[0 1 1 1]
[0 0 0 0]]
[[0 0 0 0]
[0 0 0 0]
[1 1 1 0]
[1 1 1 0]]
[[0 0 0 0]
[0 0 0 0]
[0 1 1 1]
[0 1 1 1]]
Работает для всех формованных матриц.
Комментарии:
1. Большое вам спасибо. Это именно то, что я ищу.
2. Есть лучшие способы сделать это, даже с циклами
Ответ №2:
Допустим, у вас есть (A, B)
массив нулей ( a
) и (M, N)
массив единиц ( b
). Разделите проблему по оси. Существуют A - M 1
позиции, которые они могут занимать на первой оси, B - N 1
на второй и так далее для любых дополнительных осей. Таким образом, общее количество возможностей (A - M 1) * (B - N 1)
равно.
Наиболее интуитивно понятный способ визуализации различных позиций — подумать о размещении b
в a
с некоторым смещением. Но есть другой способ. Мы можем рассматривать любую заданную позицию как представление в массив единиц, помещенных в массив нулей, заполненных со всех сторон:
m = np.pad(b, np.subtract(a.shape, b.shape))
Теперь вы можете создать представление m
для каждого из интересующих вас местоположений. Например, если мы используем A = B = 3
and M = N = 2
, первое местоположение будет m[1:4, 1:4]
, второе будет m[1:4, 0:3]
и m
т.д. будет выглядеть так:
array([[0, 0, 0, 0],
[0, 1, 1, 0],
[0, 1, 1, 0],
[0, 0, 0, 0]])
Если вы создаете m
как непрерывный массив C, вы можете использовать тайную магию np.lib.stride_tricks.as_strided
, чтобы получить все возможные представления одновременно. Начиная с m[A - M, B - N]
этого, вы можете добавить два дополнительных измерения, которые просто отступают на один элемент, используя те же шаги, m
что и в первых двух измерениях. Это легко обобщается на более чем два измерения:
def get_positions(a_shape, b_shape):
# Check input shapes
if len(a_shape) != len(b_shape):
raise ValueError('a and b must have the same number of dimensions')
d = np.subtract(a_shape, b_shape)
if (d < 0).any():
raise ValueError('a must be larger than b')
# Make padded buffer
m = np.pad(np.ones(b_shape, np.uint8), d)
# Find initial offset as a tuple of slices
offset = tuple(slice(off, None) for off in d)
# Find new dimensions
shape = tuple(d 1) a_shape
# Find new strides
strides = tuple(-s for s in m.strides) m.strides
# Make a view
view = np.lib.stride_tricks.as_strided(m[offset], shape=shape, strides=strides)
# Return a copy with the leading dims merged
return view.reshape(-1, *a_shape)
Заключительная reshape
операция принудительно копирует массив, потому что в противном случае это совершенно несмежное представление в буфер m
. Если вы согласны с этим, просто верните представление с вдвое большим количеством измерений.
Ваш исходный пример становится:
>>> get_positions((3, 3), (2, 2))
array([[[1, 1, 0],
[1, 1, 0],
[0, 0, 0]],
[[0, 1, 1],
[0, 1, 1],
[0, 0, 0]],
[[0, 0, 0],
[1, 1, 0],
[1, 1, 0]],
[[0, 0, 0],
[0, 1, 1],
[0, 1, 1]]], dtype=uint8)
Это векторизованное решение будет работать для произвольного числа измерений. Вы также можете выполнить упрощенный for
цикл в 2D, который не создает копии данных:
a = np.zeros((A, B), dtype=np.uint8)
b = np.ones((M, N), dtype=np.uint8)
m = np.pad(b, (A - M, B - N))
for i in range(A - M, -1, -1):
for j in range(B - N, -1, -1):
print(m[i:i A, j:j B])
Вы можете легко обобщить на произвольные размеры, используя itertools.product
:
from itertools import product
a_shape = (...)
b_shape = (...)
d = np.subtract(a_shape, b_shape)
m = np.pad(np.ones(b_shape, dtype=np.uint8), d)
for offset in product(*[range(off, -1, -1) for off in d]):
index = tuple(slice(off, off sz) for off, sz in zip(offset, a_shape))
print(m[index])
Преимущества использования цикла над get_positions
функцией, вероятно, в три раза:
- Я ожидаю, что цикл будет быстрее
- Цикл использует меньше памяти, поскольку он предоставляет реальные представления вместо копирования всего массива.
- Цикл позволяет вам работать с полными 32 измерениями, которые поддерживает numpy, в то время как функция использует половину этого числа для вспомогательных осей.