在 Python 中旋转二维数组

Posted

技术标签:

【中文标题】在 Python 中旋转二维数组【英文标题】:Rotating a two-dimensional array in Python 【发布时间】:2012-01-15 07:40:27 【问题描述】:

在我正在编写的程序中,需要旋转一个二维数组。在寻找最佳解决方案时,我发现了这个令人印象深刻的单线器:

rotated = zip(*original[::-1])

我现在在我的程序中使用它,它按预期工作。不过,我的问题是我不明白它是如何工作的。

如果有人能解释所涉及的不同功能如何达到预期的结果,我将不胜感激。

【问题讨论】:

确实如此。我在this SO question 中找到了它。 【参考方案1】:

这是一个聪明的点。

首先,如评论中所述,在 Python 3 中 zip() 返回一个迭代器,因此您需要将整个内容包含在 list() 中以获取实际列表,因此截至 2020 年它实际上是:

list(zip(*original[::-1]))

以下是细分:

[::-1] - 以相反的顺序制作原始列表的浅表副本。也可以使用reversed(),它会在列表上生成反向迭代器,而不是实际复制列表(更节省内存)。 * - 使原始列表中的每个子列表成为zip() 的单独参数(即解包列表) zip() - 从每个参数中获取一个项目并从中创建一个列表(嗯,一个元组),然后重复直到所有子列表都用完。这就是转置实际发生的地方。 list()zip() 的输出转换为一个列表。

所以假设你有这个:

[ [1, 2, 3],
  [4, 5, 6],
  [7, 8, 9] ]

你首先得到这个(浅的、颠倒的副本):

[ [7, 8, 9],
  [4, 5, 6],
  [1, 2, 3] ]

接下来将每个子列表作为参数传递给zip

zip([7, 8, 9], [4, 5, 6], [1, 2, 3])

zip() 从每个参数的开头重复使用一个项目并从中创建一个元组,直到没有更多项目,导致(在转换为列表后):

[(7, 4, 1), 
 (8, 5, 2), 
 (9, 6, 3)]

鲍勃是你的叔叔。

要在评论中回答@IkeMiguel 关于在另一个方向旋转它的问题,这非常简单:您只需反转进入zip 的序列和结果。第一个可以通过删除[::-1] 来实现,第二个可以通过在整个事物周围抛出reversed() 来实现。由于reversed() 在列表上返回一个迭代器,我们需要将list() 放在 that 周围来转换它。通过几个额外的list() 调用将迭代器转换为实际列表。所以:

rotated = list(reversed(list(zip(*original))))

我们可以通过使用“火星笑脸”切片而不是reversed()来简化这一点...然后我们不需要外部list()

rotated = list(zip(*original))[::-1]

当然,您也可以简单地将列表顺时针旋转 3 次。 :-)

【讨论】:

可以逆时针旋转吗?? @MiguelIke 是的,做 zip(*matrix)[::-1] ^ 请注意,您必须将 zip 的结果转换为 Python 3.x 中的列表!【参考方案2】:

考虑以下二维列表:

original = [[1, 2],
            [3, 4]]

让我们一步一步分解:

>>> original[::-1]   # elements of original are reversed
[[3, 4], [1, 2]]

此列表使用argument unpacking 传递到zip(),因此zip 调用最终等同于:

zip([3, 4],
    [1, 2])
#    ^  ^----column 2
#    |-------column 1
# returns [(3, 1), (4, 2)], which is a original rotated clockwise

希望 cmets 清楚 zip 的作用,它将根据索引对来自每个可迭代输入的元素进行分组,或者换句话说,它将列分组。

【讨论】:

一个接近的。但我选择了你的,因为它有整洁的 ASCII 艺术;) 和星号?? @johnktejik - 这是答案的“参数解包”部分,点击链接了解详情 为清楚起见,您应该指出这会顺时针旋转矩阵,并且原始列表将转换为元组。 转一圈(返回列表列表而不是元组)我这样做了:rotated = [list(r) for r in zip(*original[::-1])]【参考方案3】:

这分为三个部分:

    original[::-1] 反转原始数组。这种表示法是 Python 列表切片。这为您提供了 [start:end:step] 描述的原始列表的“子列表”,start 是第一个元素,end 是要在子列表中使用的最后一个元素。 step 表示从第一个到最后一个元素。省略 start 和 end 意味着切片将是整个列表,而否定步骤意味着您将获得相反的元素。因此,例如,如果 original 是 [x,y,z],则结果将是 [z,y,x] 在函数调用的参数列表中,在列表/元组之前的 * 表示“扩展”列表/元组,以便其每个元素成为函数的单独参数,而不是列表/元组本身。因此,如果 args = [1,2,3],则 zip(args) 与 zip([1,2,3]) 相同,但 zip(*args) 与 zip(1, 2,3)。 zip 是一个函数,它接受 n 个参数,每个参数的长度为 m,并生成一个长度为 m 的列表,其中的元素长度为 n,并包含每个原始列表的相应元素。例如,zip([1,2],[a,b],[x,y]) 是 [[1,a,x],[2,b,y]]。另见Python documentation.

【讨论】:

+1,因为只有你可能解释了第一步。【参考方案4】:

只是一个观察。输入是一个列表列表,但是非常好的解决方案的输出:rotated = zip(*original[::-1]) 返回一个元组列表。

这可能是也可能不是问题。

然而,它很容易纠正:

original = [[1, 2, 3],
            [4, 5, 6],
            [7, 8, 9]
            ]


def rotated(array_2d):
    list_of_tuples = zip(*array_2d[::-1])
    return [list(elem) for elem in list_of_tuples]
    # return map(list, list_of_tuples)

print(list(rotated(original)))

# [[7, 4, 1], [8, 5, 2], [9, 6, 3]]

列表组合或映射都会将内部元组转换回列表。

【讨论】:

【参考方案5】:

我自己也遇到过这个问题,我找到了关于这个主题的很棒的***页面(在“常见轮换”段落中:https://en.wikipedia.org/wiki/Rotation_matrix#Ambiguities

然后我写了下面的代码,超级冗长,以便清楚地了解发生了什么。

我希望你会发现在你发布的非常漂亮和聪明的单行中挖掘更多内容是有用的。

要快速测试,您可以在此处复制/粘贴:http://www.codeskulptor.org/

triangle = [[0,0],[5,0],[5,2]]
coordinates_a = triangle[0]
coordinates_b = triangle[1]
coordinates_c = triangle[2]

def rotate90ccw(coordinates):
    print "Start coordinates:"
    print coordinates
    old_x = coordinates[0]
    old_y = coordinates[1]
# Here we apply the matrix coming from Wikipedia
# for 90 ccw it looks like:
# 0,-1
# 1,0
# What does this mean?
#
# Basically this is how the calculation of the new_x and new_y is happening:
# new_x = (0)(old_x)+(-1)(old_y)
# new_y = (1)(old_x)+(0)(old_y)
#
# If you check the lonely numbers between parenthesis the Wikipedia matrix's numbers
# finally start making sense.
# All the rest is standard formula, the same behaviour will apply to other rotations, just
# remember to use the other rotation matrix values available on Wiki for 180ccw and 170ccw
    new_x = -old_y
    new_y = old_x
    print "End coordinates:"
    print [new_x, new_y]

def rotate180ccw(coordinates):
    print "Start coordinates:"
    print coordinates
    old_x = coordinates[0]
    old_y = coordinates[1] 
    new_x = -old_x
    new_y = -old_y
    print "End coordinates:"
    print [new_x, new_y]

def rotate270ccw(coordinates):
    print "Start coordinates:"
    print coordinates
    old_x = coordinates[0]
    old_y = coordinates[1]  
    new_x = -old_x
    new_y = -old_y
    print "End coordinates:"
    print [new_x, new_y]

print "Let's rotate point A 90 degrees ccw:"
rotate90ccw(coordinates_a)
print "Let's rotate point B 90 degrees ccw:"
rotate90ccw(coordinates_b)
print "Let's rotate point C 90 degrees ccw:"
rotate90ccw(coordinates_c)
print "=== === === === === === === === === "
print "Let's rotate point A 180 degrees ccw:"
rotate180ccw(coordinates_a)
print "Let's rotate point B 180 degrees ccw:"
rotate180ccw(coordinates_b)
print "Let's rotate point C 180 degrees ccw:"
rotate180ccw(coordinates_c)
print "=== === === === === === === === === "
print "Let's rotate point A 270 degrees ccw:"
rotate270ccw(coordinates_a)
print "Let's rotate point B 270 degrees ccw:"
rotate270ccw(coordinates_b)
print "Let's rotate point C 270 degrees ccw:"
rotate270ccw(coordinates_c)
print "=== === === === === === === === === "

【讨论】:

【参考方案6】:
def ruota_orario(matrix):
   ruota=list(zip(*reversed(matrix)))
   return[list(elemento) for elemento in ruota]
def ruota_antiorario(matrix):
   ruota=list(zip(*reversed(matrix)))
   return[list(elemento)[::-1] for elemento in ruota][::-1]

【讨论】:

请解释您的解决方案,以便其他人更好地理解。 当然,第一个函数(ruota_antiorario)逆时针旋转,第二个函数(ruota_orario)顺时针旋转【参考方案7】:

逆时针旋转(标准列到行枢轴)作为列表和字典

rows = [
  ['A', 'B', 'C', 'D'],
  [1,2,3,4],
  [1,2,3],
  [1,2],
  [1],
]

pivot = []

for row in rows:
  for column, cell in enumerate(row):
    if len(pivot) == column: pivot.append([])
    pivot[column].append(cell)

print(rows)
print(pivot)
print(dict([(row[0], row[1:]) for row in pivot]))

生产:

[['A', 'B', 'C', 'D'], [1, 2, 3, 4], [1, 2, 3], [1, 2], [1]]
[['A', 1, 1, 1, 1], ['B', 2, 2, 2], ['C', 3, 3], ['D', 4]]
'A': [1, 1, 1, 1], 'B': [2, 2, 2], 'C': [3, 3], 'D': [4]

【讨论】:

这与问题无关,该问题要求解释zip(*original[::-1]) 的工作原理。【参考方案8】:

矩阵顺时针旋转:

mat = [[1,2,3],[4,5,6],[7,8,9]]
clock_rotated_mat = list(zip(*mat[::-1]))

# [(7, 4, 1), (8, 5, 2), (9, 6, 3)]

[::-1] - 反转矩阵

zip(*_) - 根据其索引解包每个列表的嵌套列表值。

list() - 转换回列表对象。

类似地,矩阵逆时针旋转:

mat = [[1,2,3],[4,5,6],[7,8,9]]
anti_clock_rotated_mat = list(zip(*mat))[::-1]

# [(3, 6, 9), (2, 5, 8), (1, 4, 7)]

【讨论】:

以上是关于在 Python 中旋转二维数组的主要内容,如果未能解决你的问题,请参考以下文章

python-二维数组实现90度旋转

Python学习笔记 之 递归二维数组顺时针旋转90°正则表达式

实现二维数组顺时针旋转的功能

算法之二维数组旋转

day4 二维数组旋转90度

矩阵旋转(二维数组旋转)