Рассмотрим трехмерный массив формы (4, 4, 4). Каждый двухмерный массив вдоль axis=0 имеет перевернутое по вертикали совпадение где-то в трехмерном массиве.
В показанном небольшом примере arr[0] и arr[3] образуют перевернутую пару, как и arr[1] и arr[2].
import numpy as np
arr = np.array([[[1, 2, 3, 4],
[3, 3, 3, 3],
[4, 6, 0, 9],
[4, 3, 2, 1]],
[[5, 6, 5, 6],
[8, 8, 8, 8],
[0, 9, 0, 9],
[1, 2, 4, 8]],
[[1, 2, 4, 8],
[0, 9, 0, 9],
[8, 8, 8, 8],
[5, 6, 5, 6]],
[[4, 3, 2, 1],
[4, 6, 0, 9],
[3, 3, 3, 3],
[1, 2, 3, 4]]])
ВОПРОС: Как найти индексы перевернутых пар двумерных массивов? Результат в приведенном примере будет:
matched_pairs = [[0, 3],
[1, 2]]
Фактический начальный трехмерный массив обычно имеет форму (20000, 8, 8), поэтому matched_pairs будет иметь форму (10000, 2).
Да, совпадающие пары могут появляться в любом месте трехмерного массива.



Вы можете перебирать каждый элемент arr, проверяя, равен ли он другому элементу arr, перевернутому по оси 1:
flip = np.flip(arr, 1)
matched_pairs = [[i, np.nonzero((arr[i] == flip).all(axis=(1,2)))[0][0]] for i in range(arr.shape[0])]
Выход:
[[0, 3], [1, 2], [2, 1], [3, 0]]
Если вы знаете, что все пары находятся между первой и второй половиной массива, вы можете просто перебрать только первую половину массива, т.е.
i in range(arr.shape[0] // 2)
В противном случае вы можете отфильтровать дубликаты из этого списка, если это необходимо:
matched_pairs = [list(p) for p in {frozenset(p) for p in matched_pairs}]
Выход:
[[1, 2], [0, 3]]
спасибо за вклад. Это хорошо работало с меньшими выборками, но вызывало затруднения, когда трехмерные массивы углублялись вдоль оси = 0.
@user109387 user109387 np, я ожидал, что кто-то придумает что-то более эффективное, и не разочаровался. :) Хороший вопрос.
Вы можете использовать apply_along_axis вместе с lexsort:
a = np.apply_along_axis(lambda x: x if x[0] < x[-1] else x[::-1], 1, arr)
np.lexsort(a.reshape(a.shape[0], -1).T).reshape(-1, 2)
array([[0, 3],
[1, 2]], dtype=int64)
Мы могли бы расширить это и превратить в общую функцию:
import numpy as np
def get_pairs(arr):
a = np.arange(np.prod(arr.shape[1:])).reshape(arr.shape[1:])[::-1].ravel()
def fn(x):
y = x[a]
for i,j in zip(x,y):
if i != j: break
return x if i<j else y
b = np.lexsort(np.array([fn(i) for i in arr.reshape(arr.shape[0], -1)]).T)
return b.reshape(-1, 2)
np.random.seed(0)
a = np.random.randint(0, 10, size=(5, 4, 5))
b = np.vstack([a, a[:,::-1]])[np.random.choice(10, 10, False)]
d = get_pairs(b)
array([[5, 9],
[0, 4],
[1, 2],
[6, 7],
[3, 8]], dtype=int64)
b[d[0]]
array([[[6, 7, 7, 8, 1],
[7, 6, 8, 8, 1],
[9, 3, 5, 2, 4],
[5, 0, 3, 3, 7]],
[[5, 0, 3, 3, 7],
[9, 3, 5, 2, 4],
[7, 6, 8, 8, 1],
[6, 7, 7, 8, 1]]])
b[d[1]]
array([[[4, 4, 8, 4, 3],
[7, 5, 5, 0, 1],
[5, 9, 3, 0, 5],
[0, 1, 2, 4, 2]],
[[0, 1, 2, 4, 2],
[5, 9, 3, 0, 5],
[7, 5, 5, 0, 1],
[4, 4, 8, 4, 3]]])
Сравнение времени:
def pairs_chrslg(arr):
ii,jj = np.where((arr[None,:,:,:] == arr[:,None,::-1,:]).all(axis=(2,3)))
m=ii<jj
return np.array([ii[m], jj[m]]).T
def pairs_nick(arr):
flip = np.flip(arr, 1)
matched_pairs = [[i, np.nonzero((arr[i] == flip).all(axis=(1,2)))[0][0]] for i in range(arr.shape[0])]
return [list(p) for p in {frozenset(p) for p in matched_pairs}]
np.random.seed(0)
def create_arr(n):
a = np.random.randint(0, 10, size=(n, 10, 10))
return np.vstack([a, a[:,::-1]])[np.random.choice(n*2, n*2, False)]
def equal(x, y):
x = np.array(x)
x.sort(1)
y = np.array(y)
y.sort(1)
return np.all(x[x[:,0].argsort()] == y[y[:,0].argsort()])
import perfplot
out = perfplot.bench(
setup= create_arr,
kernels=[
get_pairs,
pairs_chrslg,
pairs_nick
],
labels=["onyambu", "chrslg", "nick"],
n_range=[3, 10, 30, 50, 75, 100, 300, 500, 750, 1000, 1500],
xlabel = "N",
equality_check=equal
)
out
┌──────┬───────────────────────┬───────────────────────┬──────────────────────┐
│ n │ onyambu │ chrslg │ nick │
├──────┼───────────────────────┼───────────────────────┼──────────────────────┤
│ 3 │ 4.9500000000000004e-… │ 1.25e-05 │ 3.3900000000000004e… │
│ 10 │ 8.2e-05 │ 6.75e-05 │ 0.0001206 │
│ 30 │ 0.0001921 │ 0.0004427 │ 0.00081420000000000… │
│ 50 │ 0.000407700000000000… │ 0.0014860000000000001 │ 0.0019667 │
│ 75 │ 0.0006580000000000001 │ 0.0037497000000000003 │ 0.0040774 │
│ 100 │ 0.0008856000000000001 │ 0.006582800000000001 │ 0.00693480000000000… │
│ 300 │ 0.0030358000000000004 │ 0.06236920000000001 │ 0.060254300000000004 │
│ 500 │ 0.0065998 │ 0.1809316 │ 0.1633378 │
│ 750 │ 0.0083125 │ 0.4043601 │ 0.3742809 │
│ 1000 │ 0.011511700000000001 │ 0.7393738000000001 │ 0.6743115000000001 │
│ 1500 │ 0.018324800000000002 │ 1.7131234000000002 │ 1.7008605 │
└──────┴───────────────────────┴───────────────────────┴──────────────────────┘
Обратите внимание, что двое других (особенно Ника) страдают от неэффективности памяти. т.е. невозможно получить пары для больших массивов. например (10000, 20, 20)
Ваша функция def_pairs сработала прекрасно! Я даже применил его к трехмерному массиву фигур (70000, 10, 6), и он нашел все двумерные совпадения за несколько секунд. Большое спасибо!
@user109387 user109387 только что обновил функцию. Рассмотрите возможность использования новой функции
Обновление работает отлично, но я думаю, что предпочитаю более раннюю версию, в которой первый столбец вывода отображался в порядке возрастания. Это важно по практическим соображениям при применении к более крупным массивам, таким как (70000, 10, 6), который я тестировал.
@user109387 user109387 Вторая версия в 2 раза больше первой версии. Заказ — это всего лишь последний шаг, т. е. b[b[:,0].argsort()], который вы можете напрямую включить в качестве последнего шага во второй версии. т.е. сохраните возвращаемое значение второй версии в переменной, например c, а затем выполните return c[c[:,0].argsort()].
Это действительно умное использование lexsort.
@Onyambu - да, я хотел упомянуть, что включил изменение порядка. Ваше пересмотренное решение работает прекрасно и очень быстро.
Одним из чистых способов использования вещания может быть
ii,jj = np.where((arr[None,:,:,:] == arr[:,None,::-1,:]).all(axis=(2,3)))
Объяснение:arr[None,:,:,:] — это просто массив с дополнительной осью len 1 в начале и основной осью в виде axis=1 и второй матричной осью в позициях 2 и 3.arr[:,None,::-1,:] — это массив с дополнительной осью в позиции 1, основной осью — в позиции 0 и второй осью матрицы в позициях 2 и 3. Кроме того, первая ось матрицы перевернута.
Итак, arr[None,:,:,:]==arr[:,None,::-1,:] сравнивает все пары позиций на главной оси (поскольку в этом сравнении главная ось представляет собой две разные оси) матрицы, одна из которых перевернута. Вы получаете 4D-массив логических значений, например (arr[None,:,:,:]==arr[:,None,::-1,:])[i,j,k,l] истинно, если элемент [i,k,l] из arr совпадает с его элементом [j,-k,l]..all(axis=(2,3)) проверяет, верно ли это для всей матрицы. Итак, (arr[None,:,:,:] == arr[:,None,::-1,:]).all(axis=(2,3))[i,j] истинно тогда и только тогда, когда матрица i является перевернутой матрицей j. В каждой строке должно быть одно значение Truenp.where() рассказывает, где находятся «Правда».
Итак ii,jj пары. Вам просто нужно объединить их в один массив. Что можно сделать просто так
np.array(np.where((arr[None,:,:,:] == arr[:,None,::-1,:]).all(axis=(2,3)))).T
Ради острот. Но поскольку, чтобы получить строго желаемый результат, нам нужно отфильтровать избыточные строки, давайте сначала отфильтруем ii и jj, а затем построим массив.
ii,jj = np.where((arr[None,:,:,:] == arr[:,None,::-1,:]).all(axis=(2,3)))
m=ii<jj
np.array([ii[m], jj[m]]).T
Который возвращает точный желаемый результат.
Время показывает, что это быстрее, чем другие решения. (25 мкс против 65 мкс против 143 мкс для исходного размера).
Я думаю (если я правильно понял), что решение lexsort ломается, как только оно находит несовпадающее значение. Итак, для большого массива это становится явным преимуществом, вероятно, во многом компенсирующим медлительность apply_along_axis. Таким образом, для большего размера решение Оньямбу, вероятно, выиграет (Редактировать: я тестировал массив 100×8×8, и мы все еще равны, для 1000×8×8 решение Оньямбу в 10 раз быстрее. Независимо от размера, мой Решение всегда быстрее, чем у Ника, но с пропорционально уменьшающимся преимуществом. Всего на 10% быстрее для 1000×8×8. В любом случае, когда я смотрю на него, это то же самое решение, только с явным for вместо трансляции. неявный ::-1 — это flip, а использование np.where с одним аргументом является синонимом nonzero. Но явное for позволяет избежать недостатка, упомянутого в моем решении в следующем предложении.
Кроме того, у моего решения есть недостаток: оно создает логический массив N²×W×H для проверки массива N×W×H. Для 20000,8,8 потребуется около 25 Гб (поскольку 1 логическое значение = 1 байт в numpy).
@ chrsig Это дает мне AttributeError: объект 'bool' не имеет атрибута 'all'. Я надеюсь, что это легко исправить. Ваше объяснение было очень подробным и ясно изложено - я многому научился. Спасибо.
В какой из трех строк отображается эта ошибка? Я не могу воспроизвести это с вашего arr. Я полагаю, что это, скорее всего, первый (тот, что с all). Именно это и произошло бы, если бы (arr[None,:,:,:] == arr[:,None,::-1,:]) было логическим значением, а не массивом логических значений. Итак, если бы == воспринимался как примитивное сравнение Python, а не как __eq__ метод. Но этого не должно произойти, если Arr такой, как показано в вашем вопросе. Вы уверены, что arr стоял в правильной форме, когда вы это пробовали? Опять же, я просто копирую и вставляю вам arr=np.array..., затем свои 3 строчки, и все работает как задумано.
Это хороший вариант использования трансляции, чтобы избежать flip в моем ответе.
Могут ли совпадения быть где угодно в массиве, например. для вашего примера результат может быть
[[0, 1], [2, 3]]или они всегда будут во второй половине?