在 Python 中旋转二维数组

在一个程序中,我写的需要旋转一个二维数组出现了。在寻找最佳解决方案的过程中,我发现了这个令人印象深刻的笑话:

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

我现在在我的程序中使用它,并且它像预期的那样工作。我的问题是,我不明白它是如何工作的。

我希望有人能解释一下这些不同的功能是如何达到预期效果的。

143840 次浏览

这招很聪明。

首先,正如注释中指出的那样,在 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()放在 那个周围来转换它。通过一些额外的 list()调用将迭代器转换为实际的列表。所以:

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

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

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

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

考虑以下二维列表:

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

让我们一步一步地把它分解:

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

这个列表使用 解开包装的论据传递给 zip(),因此 zip调用的结果相当于:

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

希望这些注释能清楚地表明 zip的作用,它将根据索引对每个输入迭代的元素进行分组,换句话说,它将列进行分组。

这里有三个部分:

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

我自己也遇到过这个问题,我在维基百科上找到了关于这个主题的页面(在“公共旋转”一段:
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 "=== === === === === === === === === "

只是观察。输入是一个列表列表,但是来自非常好的解决方案: rotated = zip (* first [ : :-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]]

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

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]

顺时针旋转计数器(标准列到行轴)

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]}

矩阵顺时针旋转:

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

逆转矩阵

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