原位旋转矩阵

时间:2016-12-22 19:24:38

标签: python algorithm numpy matrix

我有一个列表列表(我在这里称之为矩阵),我希望在原地顺时针旋转90°(即没有应对另一个矩阵)。我想出了以下代码。

我对算法本身感兴趣,在纯python中(没有使用numpy左右转置)。

def rotate(M):
    n = len(M)
    m = len(M[0])

    for i in range(n//2):
        for j in range(i, m-1-i):
            ii, jj = i, j
            v0 = M[ii][jj]
            for _ in range(3):
                M[ii][jj] = M[n-1-jj][ii]
                ii, jj = n-1-jj, ii
            M[ii][jj] = v0

    return M

虽然行数等于列数,但效果很好。有关修改我的函数以处理行数和列数不相等的情况的想法吗?

以下是一个例子:

input:      [[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]]

output      [[21, 16, 11, 6, 1],
            [22, 17, 12, 7, 2],
            [23, 18, 13, 8, 3],
            [24, 19, 14, 9, 4],
            [25, 20, 15, 10, 5]]

4 个答案:

答案 0 :(得分:2)

始终使用numpy进行矩阵运算。我假设m*n numpy数组arr。我首先使用np.transpose函数进行转置,然后使用np.fliplr函数将其翻转。

output = np.fliplr(np.transpose(arr))

如评论中所述,没有矩形矩阵的临时变量就无法进行就地替换。使用像这样的函数

来模拟行为(如果存储是你的问题)会更好
def clockwise(matrix, row, column):
    return matrix[-column][row]

答案 1 :(得分:2)

其他答案正确地建议rot90transposefliplr。如果您深入研究他们的代码,您会发现该操作可以缩减为transpose和反向索引:

In [467]: arr=np.arange(1,26).reshape(5,5)
In [468]: arr.transpose()
Out[468]: 
array([[ 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]])
In [469]: arr.transpose()[:,::-1]
Out[469]: 
array([[21, 16, 11,  6,  1],
       [22, 17, 12,  7,  2],
       [23, 18, 13,  8,  3],
       [24, 19, 14,  9,  4],
       [25, 20, 15, 10,  5]])

单独transpose[:,::-1]生成观看次数。也就是说,数组是新的,但它与原始数据共享一个数据缓冲区。但是,numpy必须一起复制。换句话说,您无法从[21, 16, 11,...]获取数字[1,2,3,...]而无需重新排序。

transpose[::-1]索引都是在编译代码中实现的。 transpose实际上是一个肤浅的'操作,更改数组shapestrides(以及order),但不重新排列任何值。单独[:,::-1]进行strides更改,但order更改时,还必须执行数组副本。

血腥细节

In [470]: arr.__array_interface__
Out[470]: 
{'data': (151576248, False),
 'descr': [('', '<i4')],
 'shape': (5, 5),
 'strides': None,
 'typestr': '<i4',
 'version': 3}
In [471]: arr1 = arr.transpose()
In [472]: arr1.__array_interface__
Out[472]: 
{'data': (151576248, False),
 'descr': [('', '<i4')],
 'shape': (5, 5),
 'strides': (4, 20),
 'typestr': '<i4',
 'version': 3}
In [473]: arr2 = arr1.copy()
In [474]: arr2.__array_interface__
Out[474]: 
{'data': (154237272, False),
 'descr': [('', '<i4')],
 'shape': (5, 5),
 'strides': None,
 'typestr': '<i4',
 'version': 3}
In [475]: arr3 = arr2[:,::-1]
In [476]: arr3.__array_interface__
Out[476]: 
{'data': (154237288, False),
 'descr': [('', '<i4')],
 'shape': (5, 5),
 'strides': (20, -4),   # or (4,-20) without the explicit copy()
 'typestr': '<i4',
 'version': 3}

列表版本

这是一个pure Python列表实现。 zip(*)是转置的列表版本。 [::-1]就像数组那样反转列表。

In [479]: alist1=arr.tolist()
In [480]: alist1
Out[480]: 
[[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]]
In [481]: alist2=list(zip(*alist1))
In [482]: alist2
Out[482]: 
[(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)]
In [483]: alist3=[l[::-1] for l in alist2]
In [484]: alist3
Out[484]: 
[(21, 16, 11, 6, 1),
 (22, 17, 12, 7, 2),
 (23, 18, 13, 8, 3),
 (24, 19, 14, 9, 4),
 (25, 20, 15, 10, 5)]

或一行:

[list(l[::-1]) for l in zip(*alist1)]

需要内部list来制作列表而不是元组列表。

list代码可用于&#39;矩阵&#39;不是正方形。但它正在制作一些列表的副本。但这是典型的Python列表方式。从旧的(通过列表推导)创建新列表几乎总是比改变原始列表更容易。您的rotate函数证明了这一点。我无法一眼就看出正在做什么。您有n//2m-1-i这样的模糊范围。并且您无法处理nm不同的情况(因此生成的外部列表的长度与原始列表的长度不同)。

请记住,列表包含指针,而不是值。 A&#39;矩阵&#39; list只是一个列表,其中包含指向其他列表的指针,这些列表本身指向存储在内存中的值。

=======================

一些时间

In [493]: %%timeit alist=arr.tolist()
     ...: rotate(alist)
10000 loops, best of 3: 21 µs per loop

In [494]: %%timeit alist=arr.tolist()
     ...: [list(row[::-1]) for row in zip(*alist)]
100000 loops, best of 3: 6.83 µs per loop

纯数组操作要快得多:

In [495]: timeit arr.transpose()[:,::-1]
The slowest run took 11.51 times longer....
1000000 loops, best of 3: 1.46 µs per loop

但是将嵌套列表转换为数组确实需要时间

In [496]: %%timeit alist=arr.tolist()
     ...: np.array(alist).transpose()[:,::-1]
     ...: 
100000 loops, best of 3: 11.7 µs per loop

为了比较,direct numpy函数 - 这个数组足够小,以至于几层函数调用会占用大量时间。

In [523]: timeit np.rot90(arr,-1)
The slowest run took 5.06 times longer ...
100000 loops, best of 3: 6.18 µs per loop

我认为,对于较大的数组,in-place rotate会相对更差 - 直到其他人生成MemoryErrors

答案 2 :(得分:1)

martianwars' answer很棒。然而,与NumPy有一条更直接的路线:

np.rot90(arr)

唯一美中不足的是rot90逆时针旋转数组 而OP的例子似乎需要顺时针。不过没问题。有一个参数k表示旋转到(逻辑上)的次数。对于i顺时针旋转:

np.rot90(arr, 4-i)

答案 3 :(得分:0)

要将二维矩阵顺时针旋转90°(不分配新矩阵),它将包括以下两个步骤。

  1. 矩阵中的反行
  2. 转置整个矩阵
"""Example:
[                 [                [
[1,2,3],          [7,8,9]          [7,4,1]
[4,5,6],    ->    [4,5,6]    ->    [8,5,2]
[7,8,9]           [1,2,3]          [9,6,3]
]                 ]                ]
"""
def rotate(matrix):
    rows = len(matrix)
    columns = len(matrix[0])
        
    # inverse row
    for i in range(rows//2):
        matrix[i], matrix[columns-i-1] =\
        matrix[columns-i-1], matrix[i]
        
    # transpose
    for i in range(rows):
        for j in range(i):
            matrix[i][j], matrix[j][i] =\
            matrix[j][i], matrix[i][j]

if __name__ == "__main__":
    matrix = [[1,2,3],[4,5,6],[7,8,9]]
    rotate(matrix)
    print(matrix)