Rotation d'un tableau à deux dimensions en Python

122

Dans un programme que j'écris, la nécessité de faire pivoter un tableau à deux dimensions est apparue. À la recherche de la solution optimale, j'ai trouvé cet impressionnant monocouche qui fait le travail:

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

Je l'utilise maintenant dans mon programme et cela fonctionne comme prévu. Mon problème cependant, c'est que je ne comprends pas comment cela fonctionne.

J'apprécierais que quelqu'un puisse expliquer comment les différentes fonctions impliquées atteignent le résultat souhaité.

paldepind
la source
7
En effet. Je l'ai trouvé dans cette question SO.
paldepind

Réponses:

96

Considérez la liste bidimensionnelle suivante:

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

Permet de le décomposer étape par étape:

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

Cette liste est passée en zip()utilisant le déballage d'arguments , donc l' zipappel finit par être l'équivalent de ceci:

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

Espérons que les commentaires clarifient ce que zipfait, il regroupera les éléments de chaque entrée itérable en fonction de l'index, ou en d'autres termes, il regroupe les colonnes.

Andrew Clark
la source
2
Un proche. Mais j'ai choisi le vôtre en raison de l'art ASCII soigné;)
paldepind
1
et l'astérisque ??
john ktejik
@johnktejik - c'est la partie "déballage des arguments" de la réponse, cliquez sur le lien pour plus de détails
JR Heard
1
Pour plus de clarté, vous devez souligner que cela fait pivoter la matrice dans le sens des aiguilles d'une montre et que les listes de l'original sont converties en tuples.
Everett
1
pour faire un tour complet (récupérer une liste de listes et non de tuples) j'ai fait ceci:rotated = [list(r) for r in zip(*original[::-1])]
matt
94

C'est un peu intelligent.

Premièrement, comme indiqué dans un commentaire, dans Python 3 zip()renvoie un itérateur, vous devez donc inclure le tout list()pour récupérer une liste réelle, donc à partir de 2020, c'est en fait:

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

Voici la répartition:

  • [::-1]- fait une copie superficielle de la liste originale dans l'ordre inverse. Pourrait également utiliser reversed()qui produirait un itérateur inversé sur la liste plutôt que de copier réellement la liste (plus efficace en mémoire).
  • *- fait de chaque sous-liste de la liste d'origine un argument distinct pour zip()(c'est- à -dire décompresse la liste)
  • zip()- prend un élément de chaque argument et fait une liste (enfin, un tuple) de ceux-ci, et se répète jusqu'à ce que toutes les sous-listes soient épuisées. C'est là que la transposition se produit réellement.
  • list()convertit la sortie de zip()en liste.

Donc, en supposant que vous avez ceci:

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

Vous obtenez d'abord ceci (copie inversée superficielle):

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

Ensuite, chacune des sous-listes est passée comme argument à zip:

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

zip() consomme à plusieurs reprises un élément depuis le début de chacun de ses arguments et en fait un tuple, jusqu'à ce qu'il n'y ait plus d'éléments, ce qui entraîne (après sa conversion en liste):

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

Et Bob est ton oncle.

Pour répondre à la question de @ IkeMiguel dans un commentaire sur la rotation dans l'autre sens, c'est assez simple: il vous suffit d'inverser à la fois les séquences qui entrent zipet le résultat. Le premier peut être réalisé en supprimant le [::-1]et le second peut être réalisé en lançant un reversed()autour du tout. Depuis reversed()renvoie un itérateur sur la liste, nous devrons mettre list()autour que pour le convertir. Avec quelques list()appels supplémentaires pour convertir les itérateurs en une liste réelle. Alors:

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

Nous pouvons simplifier un peu cela en utilisant la tranche "Smiley Martien" plutôt que reversed()... alors nous n'avons pas besoin de l'extérieur list():

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

Bien sûr, vous pouvez également simplement faire pivoter la liste trois fois dans le sens des aiguilles d'une montre. :-)

kindall
la source
2
Est-il possible de tourner dans le sens anti-horaire ??
Miguel Ike
@MiguelIke yes, do zip (* matrix) [:: - 1]
RYS
3
^ notez que vous devez convertir le résultat de zipen une liste en Python 3.x!
RYS
17

Il y a trois parties à cela:

  1. original [:: - 1] inverse le tableau d'origine. Cette notation est le découpage de liste Python. Cela vous donne une "sous-liste" de la liste d'origine décrite par [start: end: step], start est le premier élément, end est le dernier élément à utiliser dans la sous-liste. L'étape dit de prendre chaque élément de l'étape du premier au dernier. Le début et la fin omis signifient que la tranche sera la liste entière, et l'étape négative signifie que vous obtiendrez les éléments à l'envers. Ainsi, par exemple, si l'original était [x, y, z], le résultat serait [z, y, x]
  2. Le * lorsqu'il précède une liste / tuple dans la liste d'arguments d'un appel de fonction signifie "développer" la liste / tuple de sorte que chacun de ses éléments devienne un argument distinct de la fonction, plutôt que la liste / tuple elle-même. Donc si, disons, args = [1,2,3], alors zip (args) est identique à zip ([1,2,3]), mais zip (* args) est identique à zip (1, 2,3).
  3. zip est une fonction qui prend n arguments dont chacun est de longueur m et produit une liste de longueur m, les éléments de sont de longueur n et contiennent les éléments correspondants de chacune des listes originales. Par exemple, zip ([1,2], [a, b], [x, y]) est [[1, a, x], [2, b, y]]. Voir aussi la documentation Python.
Setrofim
la source
+1 puisque vous étiez le seul à expliquer probablement la première étape.
paldepind
8

Juste une observation. L'entrée est une liste de listes, mais la sortie de la très belle solution: rotated = zip (* original [:: - 1]) renvoie une liste de tuples.

Cela peut ou non être un problème.

Il est cependant facilement corrigé:

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 composition de liste ou la carte convertira les tuples intérieurs en listes.

Des marques
la source
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
la source
4
Veuillez expliquer votre solution afin que les autres puissent mieux la comprendre.
HelloSpeakman
bien sûr, la première fonction (ruota_antiorario) tourne dans le sens antihoraire et la deuxième fonction (ruota_orario) dans le sens horaire
user9402118
1

J'ai moi-même eu ce problème et j'ai trouvé la super page wikipedia sur le sujet (dans le paragraphe "Rotations communes":
https://en.wikipedia.org/wiki/Rotation_matrix#Ambiguities

Ensuite, j'ai écrit le code suivant, super verbeux afin d'avoir une compréhension claire de ce qui se passe.

J'espère que vous trouverez utile de creuser davantage dans le très beau et intelligent one-liner que vous avez publié.

Pour le tester rapidement, vous pouvez le copier / coller ici:
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
la source
-1

Rotation dans le sens inverse des aiguilles d'une montre (pivot de colonne à ligne standard) comme liste et dictée

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

Produit:

[['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
la source
1
Cela n'a aucun rapport avec la question, qui demande une explication de son zip(*original[::-1])fonctionnement.
kaya3