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.
This question is related to
python
multidimensional-array
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.
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]
There are three parts to this:
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]}
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 "=== === === === === === === === === "
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. :-)
Source: Stackoverflow.com