Rotazione di un array bidimensionale in Python


122

In un programma che sto scrivendo è emersa la necessità di ruotare un array bidimensionale. Alla ricerca della soluzione ottimale ho trovato questo impressionante one-liner che fa il lavoro:

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

Lo sto usando nel mio programma ora e funziona come previsto. Il mio problema, però, è che non capisco come funziona.

Apprezzerei se qualcuno potesse spiegare come le diverse funzioni coinvolte ottengono il risultato desiderato.


7
Infatti. L'ho trovato in questa domanda SO.
paldepind

Risposte:


96

Considera il seguente elenco bidimensionale:

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

Analizziamo passo dopo passo:

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

Questo elenco viene passato zip()usando l' argomento unpacking , quindi la zipchiamata finisce per essere l'equivalente di questo:

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

Si spera che i commenti chiariscano cosa zipfa, raggrupperà gli elementi di ogni input iterabile in base all'indice, o in altre parole raggrupperà le colonne.


2
Uno vicino. Ma ho scelto il tuo a causa della bella arte ASCII;)
paldepind

1
e l'asterisco ??
john ktejik

@johnktejik - questa è la parte "spacchettamento dell'argomento" della risposta, fare clic sul collegamento per i dettagli
JR Heard

1
Per chiarezza, dovresti sottolineare che questo ruota la matrice in senso orario e che gli elenchi dell'originale vengono convertiti in tuple.
Everett,

1
per tornare al punto di partenza (tornare a un elenco di elenchi e non tuple) ho fatto questo:rotated = [list(r) for r in zip(*original[::-1])]
matt

94

È un po 'intelligente.

Innanzitutto, come notato in un commento, in Python 3 zip()restituisce un iteratore, quindi è necessario racchiudere il tutto list()per ottenere un elenco effettivo, quindi a partire dal 2020 è in realtà:

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

Ecco la ripartizione:

  • [::-1]- crea una copia superficiale dell'elenco originale in ordine inverso. Potrebbe anche usare reversed()che produrrebbe un iteratore inverso sulla lista piuttosto che copiarla effettivamente (più efficiente in termini di memoria).
  • *- rende ogni sottolista nell'elenco originale un argomento separato per zip()(cioè, decomprime l'elenco)
  • zip()- prende un elemento da ogni argomento e crea un elenco (beh, una tupla) da quelli, e si ripete finché tutte le sottoliste non sono esaurite. È qui che avviene la trasposizione.
  • list()converte l'output di zip()in un elenco.

Quindi supponendo che tu abbia questo:

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

Per prima cosa ottieni questo (copia superficiale e invertita):

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

Successivamente ciascuna delle sottoliste viene passata come argomento a zip:

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

zip() consuma ripetutamente un elemento dall'inizio di ciascuno dei suoi argomenti e ne crea una tupla, fino a quando non ci sono più elementi, risultando (dopo che è stato convertito in un elenco):

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

E Bob è tuo zio.

Per rispondere alla domanda di @ IkeMiguel in un commento sulla rotazione nell'altra direzione, è abbastanza semplice: devi solo invertire sia le sequenze che entrano in gioco zipche il risultato. Il primo può essere ottenuto rimuovendo il [::-1]e il secondo può essere ottenuto lanciando un reversed()intorno al tutto. Dal momento che reversed()restituisce un iteratore sulla lista, avremo bisogno di mettere list()in giro che per convertirlo. Con un paio di list()chiamate extra per convertire gli iteratori in un elenco effettivo. Così:

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

Possiamo semplificarlo un po 'usando la sezione "smiley marziano" piuttosto che reversed()... quindi non abbiamo bisogno dell'esterno list():

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

Ovviamente, potresti anche ruotare l'elenco in senso orario tre volte. :-)


2
È possibile ruotare in senso antiorario ??
Miguel Ike

@MiguelIke yes, do zip (* matrix) [:: - 1]
RYS

3
^ nota che devi eseguire il cast del risultato di zipin una lista in Python 3.x!
RYS

17

Ci sono tre parti in questo:

  1. original [:: - 1] inverte l'array originale. Questa notazione è l'affettatura di elenchi in Python. Questo ti dà una "sottolista" della lista originale descritta da [inizio: fine: passo], inizio è il primo elemento, fine è l'ultimo elemento da usare nella sottoelenco. step dice di prendere ogni passo dal primo all'ultimo elemento. L'omissione di inizio e fine significa che la sezione sarà l'intero elenco e il passaggio negativo significa che otterrai gli elementi al contrario. Quindi, ad esempio, se l'originale fosse [x, y, z], il risultato sarebbe [z, y, x]
  2. * Quando precede una lista / tupla nell'elenco di argomenti di una chiamata di funzione significa "espandere" la lista / tupla in modo che ciascuno dei suoi elementi diventi un argomento separato per la funzione, piuttosto che la lista / tupla stessa. Quindi, se, ad esempio, args = [1,2,3], zip (args) è uguale a zip ([1,2,3]), ma zip (* args) è uguale a zip (1, 2,3).
  3. zip è una funzione che accetta n argomenti ciascuno dei quali è di lunghezza m e produce una lista di lunghezza m, gli elementi di sono di lunghezza ne contengono gli elementi corrispondenti di ciascuna delle liste originali. Ad esempio, zip ([1,2], [a, b], [x, y]) è [[1, a, x], [2, b, y]]. Vedi anche la documentazione di Python.

+1 poiché tu sei l'unico che probabilmente spiega il primo passaggio.
paldepind

8

Solo un'osservazione. L'input è un elenco di elenchi, ma l'output della soluzione molto carina: rotated = zip (* original [:: - 1]) restituisce un elenco di tuple.

Questo può o non può essere un problema.

Tuttavia, è facilmente corretto:

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

La composizione dell'elenco o la mappa convertiranno entrambe le tuple interne in elenchi.


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]

4
Spiega la tua soluzione in modo che gli altri possano capirla meglio.
HelloSpeakman

ovviamente la prima funzione (ruota_antiorario) ruota in senso antiorario e la seconda funzione (ruota_orario) in senso orario
user9402118

1

Ho avuto questo problema anch'io e ho trovato la fantastica pagina di wikipedia sull'argomento (nel paragrafo "Rotazioni comuni":
https://en.wikipedia.org/wiki/Rotation_matrix#Ambiguities

Poi ho scritto il seguente codice, super dettagliato per avere una chiara comprensione di cosa sta succedendo.

Spero che troverai utile approfondire di più nella bellissima e intelligente battuta che hai pubblicato.

Per testarlo rapidamente puoi copiarlo / incollarlo qui:
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 "=== === === === === === === === === "

-1

Rotazione in senso antiorario (da colonna standard a pivot di riga) As List e 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]))

produce:

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

1
Questo non è correlato alla domanda, che richiede una spiegazione di come zip(*original[::-1])funziona.
kaya3
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.