Drehen eines zweidimensionalen Arrays in Python

122

In einem Programm, das ich schreibe, kam die Notwendigkeit auf, ein zweidimensionales Array zu drehen. Auf der Suche nach der optimalen Lösung habe ich diesen beeindruckenden Einzeiler gefunden, der die Aufgabe erfüllt:

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

Ich benutze es jetzt in meinem Programm und es funktioniert wie angenommen. Mein Problem ist jedoch, dass ich nicht verstehe, wie es funktioniert.

Ich würde mich freuen, wenn jemand erklären könnte, wie die verschiedenen beteiligten Funktionen das gewünschte Ergebnis erzielen.

Paldepind
quelle
7
Tatsächlich. Ich habe es in dieser SO-Frage gefunden.
Paldepind

Antworten:

95

Betrachten Sie die folgende zweidimensionale Liste:

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

Lassen Sie es uns Schritt für Schritt aufschlüsseln:

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

Diese Liste wird an das Entpackenzip() von Argumenten übergeben , sodass der zipAufruf dem entspricht:

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

Hoffentlich machen die Kommentare deutlich, was zu ziptun ist. Sie gruppieren Elemente aus jeder Eingabe iterabel basierend auf dem Index, oder mit anderen Worten, sie gruppieren die Spalten.

Andrew Clark
quelle
2
Ein enger. Aber ich habe deine wegen der ordentlichen ASCII-Kunst gewählt;)
paldepind
1
und das Sternchen?
John Ktejik
@ Johnktejik - das ist der "Argument entpacken" Teil der Antwort, klicken Sie auf den Link für Details
JR Heard
1
Aus Gründen der Übersichtlichkeit sollten Sie darauf hinweisen, dass dadurch die Matrix im Uhrzeigersinn gedreht wird und die Listen des Originals in Tupel konvertiert werden.
Everett
1
Um den Kreis zu schließen (eine Liste mit Listen und keine Tupel zurückzubekommen), habe ich Folgendes getan:rotated = [list(r) for r in zip(*original[::-1])]
Matt
94

Das ist ein kluges Stück.

Wie in einem Kommentar erwähnt, gibt Python 3 zunächst zip()einen Iterator zurück. Sie müssen also das Ganze einschließen list(), um eine aktuelle Liste wieder herauszubekommen. Ab 2020 ist es also tatsächlich:

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

Hier ist die Aufschlüsselung:

  • [::-1]- erstellt eine flache Kopie der Originalliste in umgekehrter Reihenfolge. Könnte auch verwenden, reversed()was einen umgekehrten Iterator über die Liste erzeugen würde, anstatt die Liste tatsächlich zu kopieren (speichereffizienter).
  • *- macht jede Unterliste in der ursprünglichen Liste zu einem separaten Argument zip()(dh entpackt die Liste)
  • zip()- nimmt aus jedem Argument einen Punkt und erstellt daraus eine Liste (also ein Tupel) und wiederholt diese, bis alle Unterlisten erschöpft sind. Hier findet die Umsetzung tatsächlich statt.
  • list()konvertiert die Ausgabe von zip()in eine Liste.

Angenommen, Sie haben Folgendes:

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

Sie erhalten zuerst diese (flache, umgekehrte Kopie):

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

Als nächstes wird jede der Unterlisten als Argument übergeben an zip:

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

zip() verbraucht wiederholt ein Element vom Anfang jedes seiner Argumente und erstellt daraus ein Tupel, bis keine Elemente mehr vorhanden sind. Dies führt dazu (nachdem es in eine Liste konvertiert wurde):

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

Und Bob ist dein Onkel.

Um die Frage von @ IkeMiguel in einem Kommentar zum Drehen in die andere Richtung zu beantworten, ist es ziemlich einfach: Sie müssen nur die Sequenzen zipund das Ergebnis umkehren . Das erste kann durch Entfernen des [::-1]und das zweite durch Umwerfen reversed()des Ganzen erreicht werden. Da reversed()gibt einen Iterator über die Liste, müssen wir setzen list()um , dass es zu konvertieren. Mit ein paar zusätzlichen list()Aufrufen, um die Iteratoren in eine tatsächliche Liste umzuwandeln. So:

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

Wir können das ein wenig vereinfachen, indem reversed()wir das "Martian Smiley" -Scheck verwenden, anstatt ... dann brauchen wir das Äußere nicht list():

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

Natürlich können Sie die Liste auch einfach dreimal im Uhrzeigersinn drehen. :-)

irgendwie
quelle
2
Kann man gegen den Uhrzeigersinn drehen?
Miguel Ike
@ MiguelIke ja, zip (* Matrix) [:: - 1]
RYS
3
^ Beachten Sie, dass Sie das Ergebnis zipin eine Liste in Python 3.x umwandeln müssen!
RYS
17

Das besteht aus drei Teilen:

  1. original [:: - 1] kehrt das ursprüngliche Array um. Diese Notation ist Python List Slicing. Dies gibt Ihnen eine "Unterliste" der ursprünglichen Liste, die durch [Start: Ende: Schritt] beschrieben wird. Start ist das erste Element, Ende ist das letzte Element, das in der Unterliste verwendet wird. Schritt sagt, nimm jedes Schritt-Element vom ersten bis zum letzten. Weggelassenes Ende und Ende bedeuten, dass das Slice die gesamte Liste darstellt, und der negative Schritt bedeutet, dass Sie die Elemente in umgekehrter Reihenfolge erhalten. Wenn das Original beispielsweise [x, y, z] wäre, wäre das Ergebnis [z, y, x]
  2. Das *, wenn vor einer Liste / einem Tupel in der Argumentliste eines Funktionsaufrufs steht, bedeutet "Erweitern" der Liste / des Tupels, sodass jedes seiner Elemente zu einem separaten Argument für die Funktion und nicht zu der Liste / dem Tupel selbst wird. Wenn also beispielsweise args = [1,2,3] ist, ist zip (args) dasselbe wie zip ([1,2,3]), aber zip (* args) ist dasselbe wie zip (1, 2,3).
  3. zip ist eine Funktion, die n Argumente verwendet, von denen jedes die Länge m hat und eine Liste der Länge m erzeugt, deren Elemente die Länge n haben und die entsprechenden Elemente jeder der ursprünglichen Listen enthalten. Zip ([1,2], [a, b], [x, y]) ist beispielsweise [[1, a, x], [2, b, y]]. Siehe auch Python-Dokumentation.
Setrofim
quelle
+1 da du der einzige warst, der wahrscheinlich den ersten Schritt erklärt.
Paldepind
8

Nur eine Beobachtung. Die Eingabe ist eine Liste von Listen, aber die Ausgabe der sehr schönen Lösung: gedreht = zip (* original [:: - 1]) gibt eine Liste von Tupeln zurück.

Dies kann ein Problem sein oder auch nicht.

Es ist jedoch leicht zu korrigieren:

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

Die Listenkomposition oder die Karte konvertieren beide die inneren Tupel zurück in Listen.

MarkS
quelle
2
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]
user9402118
quelle
4
Bitte erläutern Sie Ihre Lösung, damit andere sie besser verstehen können.
HelloSpeakman
Natürlich dreht sich die erste Funktion (ruota_antiorario) gegen den Uhrzeigersinn und die zweite Funktion (ruota_orario) im Uhrzeigersinn
user9402118
1

Ich hatte dieses Problem selbst und habe die großartige Wikipedia-Seite zu diesem Thema gefunden (im Abschnitt "Allgemeine Rotationen":
https://en.wikipedia.org/wiki/Rotation_matrix#Ambiguities

Dann habe ich den folgenden Code geschrieben, super ausführlich, um ein klares Verständnis dafür zu haben, was los ist.

Ich hoffe, dass Sie es nützlich finden, mehr in dem sehr schönen und cleveren Einzeiler zu graben, den Sie gepostet haben.

Um es schnell zu testen, können Sie es hier kopieren / einfügen:
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 "=== === === === === === === === === "
Pitto
quelle
-1

Gegen den Uhrzeigersinn drehen (Standard-Drehpunkt zwischen Spalte und Zeile) Als Liste und Diktat

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

Produziert:

[['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]}
Paul Kenjora
quelle
1
Dies hat nichts mit der Frage zu tun, die eine Erklärung der Funktionsweise zip(*original[::-1])erfordert.
kaya3