在Python中旋转二维数组

186 投票
9 回答
166338 浏览
提问于 2025-04-17 07:55

在我写的一个程序中,我需要对一个二维数组进行旋转。为了找到最好的解决办法,我发现了一个很厉害的一行代码,它可以完成这个任务:

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

我现在在我的程序中使用它,效果很好。不过,我的问题是,我不太明白它是怎么工作的。

如果有人能解释一下这个代码里不同的函数是如何实现这个效果的,我会非常感激。

9 个回答

22

这个内容可以分成三个部分:

  1. original[::-1] 是用来反转原始数组的。这种写法是 Python 中的一种列表切片方式。它可以让你得到一个原始列表的“子列表”,格式是 [起始:结束:步长]。起始是第一个元素,结束是要用在子列表中的最后一个元素。步长表示从第一个元素到最后一个元素每隔多少个元素取一个。如果省略了起始和结束,切片就会包含整个列表,而负的步长意味着你会得到反向的元素。所以举个例子,如果 original 是 [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 文档
168

这真是个聪明的点子。

首先,正如评论中提到的,在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]

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

121

考虑一下下面这个二维列表:

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的作用,它会根据索引把每个输入的可迭代对象中的元素分组,换句话说,就是把列分在一起。

撰写回答