Найти всех "соседей" для элемента в многомерном массиве, обвивая границы
Допустим, я использую двумерный / трехмерный массив для моделирования коробки, состоящей из ячеек. Ячейки помечены увеличивающимися числами, начиная с 0. До этого это могло быть сделано
box = np.arange(np.prod(n_cells))
box = box.reshape(n_cells)
где n_cells
является массивом np.ar, в котором хранится количество ячеек, которое должно иметь поле в каждом измерении, в основном это размеры ячейки, подсчитанные в ячейках.
Теперь для меня самое сложное - найти разумный способ найти соседей для каждой ячейки. Моя цель состоит в том, чтобы создать массив или список, который содержит соседей для каждой ячейки - в идеале давайте рассмотрим небольшой 2D-пример.
0 1 2 3
4 5 6 7
8 9 10 11
12 13 14 15
Здесь без периодических граничных условий соседи
0: 1, 4, 5
1: 0, 2, 4, 5, 6
2: 1, 5, 6, 7, 3
...
Но я хотел бы иметь его с периодическими граничными условиями, такими, что
0: 1, 4, 5, 3, 7, 12, 13, 15
1: 0, 2, 4, 5, 6, 12, 13, 14
Таким образом, каждый элемент имеет 8 соседей в 2D. В идеале я хотел бы иметь возможность создавать такой список / массив для любых измерений, но меня особенно интересует 2D/3D, если обобщенное решение недоступно.
Еще одна проблема, на которую я должен обратить внимание, заключается в том, что таким образом я подсчитываю все пары дважды, т. Е. 0 является соседом 1, а 1 - соседом 0. Это то, от чего мне нужно избавиться, но это не главная проблема.
1 ответ
Это можно сделать с помощью numpy.roll, который "катит" массив по заданным осям, с тем типом перехода, который вам нужен. Например, прокрутка по (-1, -1) сдвигает все влево и вверх, поэтому массив становится
[[ 5, 6, 7, 4],
[ 9, 10, 11, 8],
[13, 14, 15, 12],
[ 1, 2, 3, 0]]
Таким образом, мы просто нашли юго-восточного соседа для каждой точки. Осталось сгладить этот список (ravel
), повторите процесс для каждого из 9 смещений (включая (0, 0), что означает само число) и суммируйте результаты. Решение работает для произвольной размерности массива b
:
dim = len(b.shape) # number of dimensions
offsets = [0, -1, 1] # offsets, 0 first so the original entry is first
columns = []
for shift in itertools.product(offsets, repeat=dim): # equivalent to dim nested loops over offsets
columns.append(np.roll(b, shift, np.arange(dim)).ravel())
neighbors = np.stack(columns, axis=-1)
Выход (значение neighbors
):
[[ 0, 1, 3, 4, 5, 7, 12, 13, 15],
[ 1, 2, 0, 5, 6, 4, 13, 14, 12],
[ 2, 3, 1, 6, 7, 5, 14, 15, 13],
[ 3, 0, 2, 7, 4, 6, 15, 12, 14],
[ 4, 5, 7, 8, 9, 11, 0, 1, 3],
[ 5, 6, 4, 9, 10, 8, 1, 2, 0],
[ 6, 7, 5, 10, 11, 9, 2, 3, 1],
[ 7, 4, 6, 11, 8, 10, 3, 0, 2],
[ 8, 9, 11, 12, 13, 15, 4, 5, 7],
[ 9, 10, 8, 13, 14, 12, 5, 6, 4],
[10, 11, 9, 14, 15, 13, 6, 7, 5],
[11, 8, 10, 15, 12, 14, 7, 4, 6],
[12, 13, 15, 0, 1, 3, 8, 9, 11],
[13, 14, 12, 1, 2, 0, 9, 10, 8],
[14, 15, 13, 2, 3, 1, 10, 11, 9],
[15, 12, 14, 3, 0, 2, 11, 8, 10]]
В каждой строке первая запись является исходным номером, остальные - его соседями.
Чтобы каждая пара вход-сосед перечислялась только один раз, вы можете замаскировать избыточные записи, например, с помощью NaN:
np.where(neighbors >= neighbors[:, [0]], neighbors, np.nan)
[[ 0., 1., 3., 4., 5., 7., 12., 13., 15.],
[ 1., 2., nan, 5., 6., 4., 13., 14., 12.],
[ 2., 3., nan, 6., 7., 5., 14., 15., 13.],
[ 3., nan, nan, 7., 4., 6., 15., 12., 14.],
[ 4., 5., 7., 8., 9., 11., nan, nan, nan],
[ 5., 6., nan, 9., 10., 8., nan, nan, nan],
[ 6., 7., nan, 10., 11., 9., nan, nan, nan],
[ 7., nan, nan, 11., 8., 10., nan, nan, nan],
[ 8., 9., 11., 12., 13., 15., nan, nan, nan],
[ 9., 10., nan, 13., 14., 12., nan, nan, nan],
[ 10., 11., nan, 14., 15., 13., nan, nan, nan],
[ 11., nan, nan, 15., 12., 14., nan, nan, nan],
[ 12., 13., 15., nan, nan, nan, nan, nan, nan],
[ 13., 14., nan, nan, nan, nan, nan, nan, nan],
[ 14., 15., nan, nan, nan, nan, nan, nan, nan],
[ 15., nan, nan, nan, nan, nan, nan, nan, nan]])
Идея в том, что neighbors >= neighbors[:, [0]]
перечисляет только тех, кто имеет большее число, чем сама клетка.