我追求的是Python代码能够反转numpy数组中每个数组反对角线中的值的顺序。
我已经尝试过
np.rot90
、np.fliplr
、np.transpose
、np.flipud
的各种组合,但没有一个能够给我 5x3 数组的原始形状,其中所有对角线都反转了。
知道如何实现这一点吗?
示例:
[[ 1 2 4]
[ 3 5 7]
[ 6 8 10]
[ 9 11 13]
[12 14 15]]
应该变成:
[[ 1 3 6]
[ 2 5 9]
[ 4 8 12]
[ 7 11 14]
[10 13 15]]
我认为这一定很容易,但不知何故,我还没有找到如何在具有数百万个值的数组上有效地完成它。
受到已经提供的答案(状态 2024-05-23 11:37 CET)的启发,并重新思考完成所需转换的最有效方法是什么,似乎给出一个带有两个索引的简单函数:
Column-X
,Row-y
数组中的值并返回访问数组所需的索引,就像在对角线上翻转/反转一样,将提供最快的结果。使用这样的函数,数组的对角线翻转版本将获得正确的值,而无需对数组进行操作,就像下面演示的简单情况一样简单:
>>> srcArr
array([[ 1, 2, 3, 4, 5, 6],
[ 7, 8, 9, 10, 11, 12],
[13, 14, 15, 16, 17, 18],
[19, 20, 21, 22, 23, 24]])
>>> def oneBasedXY(column, row):
... return ( row - 1, column - 1 )
...
>>> srcArr[oneBasedXY(3,4)]
21
>>> srcArr[(3,4)]
23
从这个角度来看,问题归结为提供一个功能
ijIndicesToSourceArray_gettingValueOfSourceArrayWithReversedRightLeftAntiDiagonalsAt(i,j,arrShapeRows,arrShapeColumns)
无需使用任何循环即可解决此问题的简单方法:
当矩阵是方阵(h=w)时,只需转置就足够了。我展示了一个矩阵“高”(h > w)时的示例:
def flip_along_diag(a):
h, w = a.shape
out = np.zeros_like(a)
# step 1: flip upper and lower triangles
u, v = np.triu_indices(w, 0)
p, q = np.tril_indices(w, 0)
out[u, w-v-1] = a[w-v-1, u]
out[h-w+p, w-1-q] = a[h-1-q, p]
# step 2: flip the "parallelogram" of size (r, s) in the middle
r, s = h - w - 1, w
if r >= 1:
i, j = np.mgrid[:r, :s]
i = i + np.arange(s)[::-1] + 1
out[i, j] = np.fliplr(a[i, j])
return out
我相信当矩阵很宽(w > h)而不是很高时,只需稍加修改就可以扩展相同的算法。
更新:为了处理宽(w > h)矩阵的情况,我们可以简单地首先检查w > h,然后根据需要简单地修改索引。
def flip_along_diag(a):
h, w = a.shape
if h == w: # for square matrix, a.T is enough
return a.T
out = np.zeros_like(a)
k, d = min(h, w), abs(h - w)
# step 1: flip two triangles
u, v = np.triu_indices(k, 0)
p, q = np.tril_indices(k, 0)
if h > w: # h > w: upper and lower triangle
out[u, k-v-1] = a[k-v-1, u]
out[d+p, w-q-1] = a[h-q-1, p]
else: # w > h: left and right triangle
out[u, k-v-1] = a[k-v-1, u]
out[p, w-q-1] = a[h-q-1, d+p]
# step 2: flip parallelogram
if h > w: # h > w: flip left-right
r, s = h - w - 1, w
if r >= 1:
i, j = np.mgrid[:r, :s]
i = i + np.arange(s)[::-1] + 1
out[i, j] = np.fliplr(a[i, j])
else: # w > h: flip up-down
r, s = h, w - h - 1
if s >= 1:
i, j = np.mgrid[:r, :s]
j = j + np.arange(r)[::-1, None] + 1
out[i, j] = np.flipud(a[i, j])
return out
我还没有找到 Numpy 方法来执行此操作。
但是,可以使用此函数生成索引:
import numpy as np
def flip_inds(a):
m, n = a.shape
i = np.arange(m).reshape(-1, 1)
j = np.arange(n).reshape(1, -1)
jp = np.where(
i + j < min(m, n),
i,
np.where(
i + j < max(m, n),
(
i + j - (m - 1 - i)
if n > m else
n - 1 - j
),
n - (m - i)
)
)
ip = i + j - jp
return ip, jp
ip, jp = flip_inds(a)
a[ip, jp] # diagonals flipped
但我会推荐Numba,因为它会更有效:
import numba as nb
@nb.njit
def flip(a):
m, n = a.shape
b = np.empty_like(a)
for i in range(m):
for j in range(n):
# Along a diagonal i + j is constant
# denote by (ip, jp) the indices of the value in the input that should be put in the (i, j) entry of the output
# We know that: i + j = ip + jp
jp = (
# Case 1: (i, j) in upper left triangle
# Here we just do the regular transpose
i
if i + j < min(m, n) else (
# Case 2: (i, j) neither in upper left or lower right triangle
# in this case what we do depends on if n > m or not
# If n > m we are bounded by m, locate ip at i steps from the maximum i index:
# ip = m - 1 - i
# from that we can obtain jp as i + j - ip:
i + j - (m - 1 - i)
if n > m else
# If m > n locate the jp index j steps from the max j index
n - 1 - j
)
if i + j < max(m, n) else
# Case 3: Lower right corner, here we can do a transpose again, but with indices going backwards
n - (m - i)
)
ip = i + j - jp
b[i, j] = a[ip, jp]
return b
flip(a) # diagonals flipped
@nb.njit
def flip(a):
m, n = a.shape
b = np.empty_like(a)
for i in range(m):
for j in range(n):
jp = (
i
if i + j < min(m, n) else (
i + j - (m - 1 - i)
if n > m else
n - 1 - j
)
if i + j < max(m, n) else
n - (m - i)
)
ip = i + j - jp
b[i, j] = a[ip, jp]
return b
我会提出类似于之前贡献者提出的方法,但在 numba 中使用快速 for 循环。
一步一步:
首先让我们镜像左上三角形和右下三角形
在这两个三角形之间切换平行四边形中的元素:
import numpy as np
import numba as nb
from timeit import timeit
@nb.njit(cache=True,fastmath=True)
def mirror(a):
n_rows, n_cols = a.shape[:2]
b = a.copy()
t = min(n_rows, n_cols)
# mirror upper left and bottom right triangles
for i in range(1, t-1):
for j in range(min(i, t-1-i)):
# inverting upper left triangle
b[i,j], b[j,i] = a[j,i], a[i,j]
# inverting bottom right triangle
b[n_rows-1-i,n_cols-1-j], b[n_rows-1-j,n_cols-1-i] = a[n_rows-1-j,n_cols-1-i], a[n_rows-1-i,n_cols-1-j]
# mirror middle parallelogram
if n_rows>=n_cols:
for i in range(int(t/2)):
b[t-1-i:n_rows-i,i], b[i:n_rows-t+1+i, n_cols-1-i] = a[i:n_rows-t+1+i, n_cols-1-i], a[t-1-i:n_rows-i,i]
else:
for i in range(int(t/2)):
b[i, t-1-i:n_cols-i ], b[n_rows-1-i, i:n_cols-t+1+i ] = a[n_rows-1-i, i:n_cols-t+1+i ], a[i, t-1-i:n_cols-i ]
return b
def generate_random_matrx(n_rows: int, n_cols: int):
return np.arange(1,1+n_rows*n_cols).reshape(n_rows, n_cols)
def test1():
A = [[ 1, 2, 3, 4, 5],
[ 6, 7, 8, 9, 10],
[11, 12, 13, 14, 15],
[16, 17, 18, 19, 20],
[21, 22, 23, 24, 25]]
A = np.array(A)
B = mirror(A)
A_out = [[ 1, 6, 11, 16, 21],
[ 2, 7, 12, 17, 22],
[ 3, 8, 13, 18, 23],
[ 4, 9, 14, 19, 24],
[ 5, 10, 15, 20, 25]]
A_out = np.array(A_out)
assert np.all(B == A_out)
def test2():
A = [[ 1, 2, 3],
[ 4, 5, 6],
[ 7, 8, 9],
[10, 11, 12],
[13, 14, 15],
[16, 17, 18],
[19, 20, 21],
[22, 23, 24],
[25, 26, 27],
[28, 29, 30]]
A = np.array(A)
B = mirror(A)
A_out = [[ 1, 4, 7],
[ 2, 5, 10],
[ 3, 8, 13],
[ 6, 11, 16],
[ 9, 14, 19],
[12, 17, 22],
[15, 20, 25],
[18, 23, 28],
[21, 26, 29],
[24, 27, 30]]
A_out = np.array(A_out)
assert np.all(B == A_out)
def test3():
A = [[ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
[11, 12, 13, 14, 15, 16, 17, 18, 19, 20],
[21, 22, 23, 24, 25, 26, 27, 28, 29, 30],
[31, 32, 33, 34, 35, 36, 37, 38, 39, 40]]
A = np.array(A)
B = mirror(A)
A_out = [[ 1, 11, 21, 31, 32, 33, 34, 35, 36, 37],
[ 2, 12, 22, 23, 24, 25, 26, 27, 28, 38],
[ 3, 13, 14, 15, 16, 17, 18, 19, 29, 39],
[ 4, 5, 6, 7, 8, 9, 10, 20, 30, 40]]
A_out = np.array(A_out)
assert np.all(B == A_out)
A = generate_random_matrx(3, 10)
print(A)
B = mirror(A)
print(B)
test1()
test2()
test3()
以索引作为输入的函数:
@nb.njit(cache=True,fastmath=True)
def get_indices(i:int,j:int,n_rows:int,n_cols:int):
t = min(n_rows, n_cols)
# upper left triangle
if i<t and i+j<t:
return (j,i)
# bottom right triangle
if (n_rows-1-i)<t and (n_rows-1-i) + (n_cols-1-j)<t:
i, j = (n_rows-1-i), (n_cols-1-j)
return (n_rows-1-j,n_cols-1-i)
# parallelograms
if n_rows>=n_cols:
new_j = n_cols-1-j
return (i- (new_j-j), new_j)
else:
new_i = n_rows-1-i
return (new_i, j - (new_i-i))
让我们将细胞的坐标视为向量平面上的向量(见下图)。在这种情况下,我们可以通过如下方式找到目标点:
def mirror(pi, pj, m, n) -> list:
'''Returns the point on the reverse transposed diagonal.
(pi, pj) - source point, (m, n) - maximum axis coordinates'''
P = np.array([pi, pj])
p_sum = P.sum()
A = np.array([min(p_sum, m), max(p_sum - m, 0)])
B = np.array([max(p_sum - n, 0), min(p_sum, n)])
M = A + B - P
return M.tolist()
在此图中:
以向量化形式,公式可能如下所示:
def transpose_reversed_diag(arr):
m, n = arr.shape
P = np.array(np.meshgrid(np.arange(m), np.arange(n), indexing='ij'))
m -= 1
n -= 1
psum = P.sum(0)
A = np.where(
psum < m,
np.stack([psum, np.zeros_like(psum)]),
np.stack([np.full_like(psum, m), psum - m]),
)
B = np.where(
psum < n,
np.stack([np.zeros_like(psum), psum]),
np.stack([psum - n, np.full_like(psum, n)])
)
return arr[*(A + B - P)]
假设我们知道正方形的左上角和长度。在这种情况下,我们可以这样调换正方形中的对角线:
import numpy as np
from numba import njit
@njit
def _flip_diag(ai, aj, length, arr):
'''Transpose the diagonal of a square
with the top left corner (ai, aj) and the given length
which is placed in the 2d-array arr'''
i, j = 0, length
while i < j:
left = ai+i, aj+j
right = ai+j, aj+i
arr[left], arr[right] = arr[right], arr[left]
i += 1
j -= 1
因此,我们可以通过迭代数组每个对角线上构建的正方形来解决初始任务:
@njit
def transpose(arr, inplace=False):
out = arr if inplace else arr.copy()
m, n = arr.shape
if m == n:
return out.T
if m < n:
return transpose(out.T, inplace=True).T
for d in range(n-1): # upper left triangle
_flip_diag(0, 0, d, out)
for d in range(m-n+1): # main body
_flip_diag(d, 0, n-1, out)
for d in range(1, n): # lower right triangle
_flip_diag(m-n+d, d, n-1-d, out)
return out
我认为没有任何有效的方法可以做到这一点。我的做法与我在这个答案中所说的从对角线创建数组类似,但一路上反转对角线。
import numpy as np
from scipy.sparse import diags
a = np.arange(15).reshape(5,3) + 1
n, m = a.shape
print(a)
offsets = np.arange(n+m-1)[::-1]-(a.shape[0]-1)
aflipped = np.fliplr(a)
diagonals = [np.diagonal(aflipped, offset=i) for i in offsets]
b = np.zeros_like(a)
for diagonal, offset in zip(diagonals, offsets):
b += diags(diagonal[::-1], offset, shape=a.shape).astype(int)
b = np.fliplr(b)
print(b)
只要没有更好的答案演示如何避免使用 Python 循环,下面的方法仅使用 numpy 并支持具有任何类型值(也可以是混合)的数组:
#!/usr/bin/python
import numpy as np
## Decide the shape of the test array :
M =3 ; N=5 # ( M-Rows x N-Columns )
## Create a test array :
srcArr = np.arange( 11, 10+N*M+1,
dtype=np.uint8).reshape(M,N) ; print( srcArr ); print( " ---===--- " )
# ---===---===---===---===---===---===---===---===---===---===---===---===---===---===---===---
## Extend the array to the right with M-1 columns for later "shear" :
tmpArr=np.zeros((M,M-1), dtype=np.uint8) ;# print(tmpArr );print( " ---===--- " )
extArr=np.concatenate( ( srcArr, tmpArr ), axis=1 ) ; print( extArr ); print( " ---===--- " )
## Share the extended array using np.roll() :
for index in range( 1, M):
extArr[index]=np.roll(extArr[index], index)
pass; print( extArr ); print( " ---===--- " )
## And reverse by up/down flipping the order of the former diagonals being now columns :
flpArr = np.flipud(extArr) ; print( flpArr ); print( " ---===--- " )
## Diagonals are reversed and will be "rolled" into their former positions in the array :
for index in range( 0, M +N - 1):
if True : ## The "tricky" part because handling non-square arrays requires handling
flpArr[:,index]=np.roll( flpArr[:,index], ## of three algorithmic different phases
( index + 1 ) if index < min(N,M) ## < Phase A : left, "growing" part of array
else ( -1 * (M + abs(N-M) ) + 2* (index + 1 - min(N,M)) ) ## Phase B :
* ( 1 if ( M - min(N,M) ) and (N+M-1 - 2*N - 1) ## < same "size":
else 0 ) if index < (N+M) - min(N,M) - 1
else -(M+N ) + index + 1 ) ## < Phase C : right, "shinking"
pass; print(flpArr) ; print( " ---===--- " )
for index in range( 1, M): ## Rolling the array back to its initial shape :
flpArr[index]=np.roll( flpArr[index], -index)
pass; print( flpArr) ; print( " ---===--- " )
tgtArr = flpArr[ :M, :N] ## < cut the final array out of the extended one
pass; print(tgtArr) ; print( " ---===--- " )
"""
[[ 1 2 3]
[ 4 5 6]
[ 7 8 9]
[ 10 11 12]
[13 14 15]]
[[ 1 2 3]
[ 4 5 6]
[ 7 8 9]
[10 11 12]
[13 14 15]]
[[ 1 2 3]
[ 4 5 6]
[ 7 8 9]
[10 11 12]
[13 14 15]]
"""
代码输出:
[[11 12 13 14 15]
[16 17 18 19 20]
[21 22 23 24 25]]
---===---
[[11 12 13 14 15 0 0]
[16 17 18 19 20 0 0]
[21 22 23 24 25 0 0]]
---===---
[[11 12 13 14 15 0 0]
[ 0 16 17 18 19 20 0]
[ 0 0 21 22 23 24 25]]
---===---
[[ 0 0 21 22 23 24 25]
[ 0 16 17 18 19 20 0]
[11 12 13 14 15 0 0]]
---===---
[[11 16 21 22 23 0 0]
[ 0 12 17 18 19 24 0]
[ 0 0 13 14 15 20 25]]
---===---
[[11 16 21 22 23 0 0]
[12 17 18 19 24 0 0]
[13 14 15 20 25 0 0]]
---===---
[[11 16 21 22 23]
[12 17 18 19 24]
[13 14 15 20 25]]
---===---
上面的输出演示了使用 numpy
np.roll()
的方法,可以“剪切”数组,以便首先获得可用于翻转的对角线,然后将其展开回原始形状。
反对角线保留上的指数之和。这意味着您可以使用索引和构造数组,如下所示(假设
a
是您的数组):
rows, cols = np.indices(a.shape)
indices_sum = rows + cols
array([[0, 1, 2],
[1, 2, 3],
[2, 3, 4],
[3, 4, 5],
[4, 5, 6]])
并迭代每个唯一值,获取索引并像这样翻转:
import numpy as np
rows, cols = np.indices(a.shape)
indices_sum = rows + cols
for i in np.unique(indices_sum):
r, c = np.where(indices_sum==i)
a[r, c] = a[r[::-1], c[::-1]]
注意:您可以使用
np.arange(1, a.shape[0] + a.shape[1] - 2)
代替 np.unique(indices_sum)
这里我仍然使用python循环,但我认为这是更简单的解决方案。您可以尝试调整它以排除循环。
我相信这与其他答案之一类似,但更容易理解(无论如何对我来说)。基本上,我们调换上下三角形并翻转剩余的对角线。因为 Numpy 有几个函数来处理主对角线,所以首先沿着轴 1 翻转数组。
def flip_anti_diags(x: np.ndarray) -> np.ndarray:
# Handle wide matrices. Square matrices just work
transpose = x.shape[0] < x.shape[1]
if transpose:
x = x.T
# Smallest dimension
s = x.shape[1]
# Numpy has various functions to obtain main diagonals and triangles,
# so flip x so these work
x = np.fliplr(x)
# Get the upper and lower triangles, without any other diagonals,
# but don't transpose them yet as we need them for the diagonals
u = np.triu(x)
l = np.tril(x, x.shape[1] - x.shape[0])
# Get the diagonals and flip them, returning the flipped anti-diagonals
m = x - u - l
m = np.flipud(m)
# Get the transposed upper and lower triangles
u = np.fliplr(u)
u = u[:s].T
l = np.fliplr(l)
l = l[-s:].T
# Combine the diagonals and the transposed triangles
x = m
x[:s] += u
x[-s:] += l
# Flip back, if necessary
if transpose:
x = x.T
return x