Как я могу сгенерировать комбинацию матриц на основе предоставленных матриц 0 и 1 в python?

#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 функцией, вероятно, в три раза:

  1. Я ожидаю, что цикл будет быстрее
  2. Цикл использует меньше памяти, поскольку он предоставляет реальные представления вместо копирования всего массива.
  3. Цикл позволяет вам работать с полными 32 измерениями, которые поддерживает numpy, в то время как функция использует половину этого числа для вспомогательных осей.