问题:在Python中旋转二维数组

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

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

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

如果有人可以解释所涉及的不同功能如何实现所需的结果,我将不胜感激。

In a program I’m writing the need to rotate a two-dimensional array came up. Searching for the optimal solution I found this impressive one-liner that does the job:

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

I’m using it in my program now and it works as supposed. My problem though, is that I don’t understand how it works.

I’d appreciate if someone could explain how the different functions involved achieves the desired result.


回答 0

考虑以下二维列表:

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

让我们将其逐步分解:

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

此列表传递给zip()使用参数unpacking,因此zip调用最终等效于此:

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

希望注释能够清楚说明其zip作用,它将基于索引将来自每个可迭代输入的元素进行分组,或者换句话说,将列进行分组。

Consider the following two-dimensional list:

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

Lets break it down step by step:

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

This list is passed into zip() using argument unpacking, so the zip call ends up being the equivalent of this:

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

Hopefully the comments make it clear what zip does, it will group elements from each input iterable based on index, or in other words it groups the columns.


回答 1

太聪明了。

首先,如注释中所述,在Python 3中zip()返回一个迭代器,因此您需要将整个内容封装起来list()以得到实际的列表,因此从2020年开始实际上是:

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

这是细分:

  • [::-1]-以相反的顺序对原始列表进行浅表复制。也可以使用reversed()which来在列表上生成反向迭代器,而不是实际复制列表(更节省内存)。
  • *-使原始列表中的每个子列表成为一个单独的参数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)]

而鲍勃是你的叔叔。

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

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

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

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

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

That’s a clever bit.

First, as noted in a comment, in Python 3 zip() returns an iterator, so you need to enclose the whole thing in list() to get an actual list back out, so as of 2020 it’s actually:

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

Here’s the breakdown:

  • [::-1] – makes a shallow copy of the original list in reverse order. Could also use reversed() which would produce a reverse iterator over the list rather than actually copying the list (more memory efficient).
  • * – makes each sublist in the original list a separate argument to zip() (i.e., unpacks the list)
  • zip() – takes one item from each argument and makes a list (well, a tuple) from those, and repeats until all the sublists are exhausted. This is where the transposition actually happens.
  • list() converts the output of zip() to a list.

So assuming you have this:

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

You first get this (shallow, reversed copy):

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

Next each of the sublists is passed as an argument to zip:

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

zip() repeatedly consumes one item from the beginning of each of its arguments and makes a tuple from it, until there are no more items, resulting in (after it’s converted to a list):

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

And Bob’s your uncle.

To answer @IkeMiguel’s question in a comment about rotating it in the other direction, it’s pretty straightforward: you just need to reverse both the sequences that go into zip and the result. The first can be achieved by removing the [::-1] and the second can be achieved by throwing a reversed() around the whole thing. Since reversed() returns an iterator over the list, we will need to put list() around that to convert it. With a couple extra list() calls to convert the iterators to an actual list. So:

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

We can simplify that a bit by using the “Martian smiley” slice rather than reversed()… then we don’t need the outer list():

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

Of course, you could also simply rotate the list clockwise three times. :-)


回答 2

这包括三个部分:

  1. original [::-1]反转原始数组。该表示法是Python列表切片。这为您提供了[start:end:step]描述的原始列表的“子列表”,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文档。

There are three parts to this:

  1. original[::-1] reverses the original array. This notation is Python list slicing. This gives you a “sublist” of the original list described by [start:end:step], start is the first element, end is the last element to be used in the sublist. step says take every step’th element from first to last. Omitted start and end means the slice will be the entire list, and the negative step means that you’ll get the elements in reverse. So, for example, if original was [x,y,z], the result would be [z,y,x]
  2. The * when preceding a list/tuple in the argument list of a function call means “expand” the list/tuple so that each of its elements becomes a separate argument to the function, rather than the list/tuple itself. So that if, say, args = [1,2,3], then zip(args) is the same as zip([1,2,3]), but zip(*args) is the same as zip(1,2,3).
  3. zip is a function that takes n arguments each of which is of length m and produces a list of length m, the elements of are of length n and contain the corresponding elements of each of the original lists. E.g., zip([1,2],[a,b],[x,y]) is [[1,a,x],[2,b,y]]. See also Python documentation.

回答 3

只是一个观察。输入是一个列表列表,但是非常好的解决方案的输出:旋转= 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]]

list comp或map都将内部元组转换回list。

Just an observation. The input is a list of lists, but the output from the very nice solution: rotated = zip(*original[::-1]) returns a list of tuples.

This may or may not be an issue.

It is, however, easily corrected:

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

The list comp or the map will both convert the interior tuples back to lists.


回答 4

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

回答 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 "=== === === === === === === === === "

I’ve had this problem myself and I’ve found the great wikipedia page on the subject (in “Common rotations” paragraph:
https://en.wikipedia.org/wiki/Rotation_matrix#Ambiguities

Then I wrote the following code, super verbose in order to have a clear understanding of what is going on.

I hope that you’ll find it useful to dig more in the very beautiful and clever one-liner you’ve posted.

To quickly test it you can copy / paste it here:
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

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

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

Rotating Counter Clockwise ( standard column to row pivot ) As List and Dict

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

Produces:

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

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。