web-dev-qa-db-fra.com

Faire pivoter un tableau à deux dimensions en Python

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

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

Je l'utilise dans mon programme maintenant et cela fonctionne comme supposé. 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 permettent d'obtenir le résultat souhaité.

86
paldepind

Considérez la liste à deux dimensions 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 à Zip() en utilisant argument unpacking , donc l’appel Zip 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 indiquent clairement ce que Zip fait, il va regrouper les éléments de chaque entrée itérable en fonction de l'index, ou en d'autres termes, il regroupe les colonnes.

67
Andrew Clark

C'est un peu intelligent. Voici la ventilation:

  • [::-1] - crée une copie superficielle de la liste d'origine dans l'ordre inverse. Peut aussi utiliser reversed() qui produirait un itérateur inverse sur la liste plutôt que de copier la liste (plus efficace en termes de mémoire).
  • * - transforme chaque sous-liste de la liste d'origine en un argument distinct de Zip() (c'est-à-dire qu'il décompresse la liste)
  • Zip() - prend un élément de chaque argument et en fait une liste (un tuple), et le répète jusqu'à ce que toutes les sous-listes soient épuisées. C’est là que la transposition se produit réellement.

En supposant que vous ayez ceci:

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

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

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

Ensuite chacune des sous-listes est passée en argument à Zip:

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

Zip() consomme plusieurs fois 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:

[(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 les séquences qui entrent dans Zip et le résultat. Le premier peut être réalisé en supprimant le [::-1] et le second en créant une reversed() autour du tout. Comme reversed() renvoie un itérateur sur la liste, nous devrons mettre list() autour de that pour le convertir. Alors:

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

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

70
kindall

Il y a trois parties à ceci:

  1. original [:: - 1] inverse le tableau d'origine. Cette notation est le découpage en tranches de liste Python. Cela vous donne une "sous-liste" de la liste originale décrite par [début: fin: étape], début est le premier élément, fin est le dernier élément à utiliser dans la sous-liste. step dit prendre chaque step'th élément du premier au dernier. Omis début et fin signifie que la tranche sera la liste complète et que 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. Lorsque * précède une liste/un tuple dans la liste d'arguments d'un appel de fonction, cela signifie "développer" la liste/le tuple de sorte que chacun de ses éléments devienne un argument distinct de la fonction, plutôt que la liste/le tuple lui-même. Ainsi, si, par exemple, 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 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.
14
setrofim

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

Cela peut ou peut ne pas ê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 liste comp ou la carte convertira les nuplets intérieurs en listes.

5
MarkS

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

Ensuite, j'ai écrit le code suivant, super verbeux afin de bien comprendre 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 posté.

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 "=== === === === === === === === === "
1
Pitto
def ruota_antiorario(matrix):
   ruota=list(Zip(*reversed(matrix)))
   return[list(elemento) for elemento in ruota]
def ruota_orario(matrix):
   ruota=list(Zip(*reversed(matrix)))
   return[list(elemento)[::-1] for elemento in ruota][::-1]
1
user9402118