反转(翻转)非方形 numpy 数组的左右(反对)对角线

问题描述 投票:0回答:8

我追求的是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)

python numpy diagonal
8个回答
6
投票

无需使用任何循环即可解决此问题的简单方法:

  • 第1步:调换上下三角形(红色和蓝色)
  • 第二步:翻转中间的“平行四边形”(紫色)

grid

当矩阵是方阵(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,然后根据需要简单地修改索引。

wide

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

4
投票

我还没有找到 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

2
投票

我会提出类似于之前贡献者提出的方法,但在 numba 中使用快速 for 循环。

一步一步:

  1. 首先让我们镜像左上三角形和右下三角形

  2. 在这两个三角形之间切换平行四边形中的元素:

  • 这样的平行四边形可以是垂直的
  • 或者它可以是水平的
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))

2
投票

矢量抽象

让我们将细胞的坐标视为向量平面上的向量(见下图)。在这种情况下,我们可以通过如下方式找到目标点:

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()

Solution in vector form

在此图中:

  • P 是源点
  • M是目标点
  • A 和 B 是与 P 的对角线的极值点

以向量化形式,公式可能如下所示:

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

1
投票

我认为没有任何有效的方法可以做到这一点。我的做法与我在这个答案中所说的从对角线创建数组类似,但一路上反转对角线。

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)

1
投票

只要没有更好的答案演示如何避免使用 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()
的方法,可以“剪切”数组,以便首先获得可用于翻转的对角线,然后将其展开回原始形状。


1
投票

反对角线保留上的指数之和。这意味着您可以使用索引和构造数组,如下所示(假设

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循环,但我认为这是更简单的解决方案。您可以尝试调整它以排除循环。


1
投票

我相信这与其他答案之一类似,但更容易理解(无论如何对我来说)。基本上,我们调换上下三角形并翻转剩余的对角线。因为 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
© www.soinside.com 2019 - 2024. All rights reserved.