Je voudrais ajouter un peu plus de détails. Dans cette réponse, les concepts clés sont répétés, le rythme est lent et intentionnellement répétitif. La solution proposée ici n'est pas la plus compacte syntaxiquement, elle est cependant destinée à ceux qui souhaitent savoir ce qu'est la rotation matricielle et l'implémentation qui en résulte.
Tout d'abord, qu'est-ce qu'une matrice? Aux fins de cette réponse, une matrice n'est qu'une grille dont la largeur et la hauteur sont identiques. Notez que la largeur et la hauteur d'une matrice peuvent être différentes, mais pour simplifier, ce didacticiel ne considère que les matrices de largeur et de hauteur égales ( matrices carrées ). Et oui, matrices est le pluriel de matrice.
Exemples de matrices: 2 × 2, 3 × 3 ou 5 × 5. Ou, plus généralement, N × N. Une matrice 2 × 2 aura 4 carrés car 2 × 2 = 4. Une matrice 5 × 5 aura 25 carrés car 5 × 5 = 25. Chaque carré est appelé élément ou entrée. Nous représenterons chaque élément avec un point ( .
) dans les diagrammes ci-dessous:
Matrice 2 × 2
. .
. .
Matrice 3 × 3
. . .
. . .
. . .
Matrice 4 × 4
. . . .
. . . .
. . . .
. . . .
Alors, que signifie faire pivoter une matrice? Prenons une matrice 2 × 2 et mettons quelques nombres dans chaque élément afin que la rotation puisse être observée:
0 1
2 3
Une rotation de 90 degrés nous donne:
2 0
3 1
Nous avons littéralement tourné toute la matrice une fois vers la droite, tout comme le volant d'une voiture. Il peut être utile de penser à «basculer» la matrice sur son côté droit. Nous voulons écrire une fonction, en Python, qui prend une matrice et tourne une fois vers la droite. La signature de la fonction sera:
def rotate(matrix):
# Algorithm goes here.
La matrice sera définie à l'aide d'un tableau à deux dimensions:
matrix = [
[0,1],
[2,3]
]
Par conséquent, la première position d'index accède à la ligne. La deuxième position d'index accède à la colonne:
matrix[row][column]
Nous allons définir une fonction utilitaire pour imprimer une matrice.
def print_matrix(matrix):
for row in matrix:
print row
Une méthode de rotation d'une matrice consiste à le faire couche par couche. Mais qu'est-ce qu'une couche? Pensez à un oignon. Tout comme les couches d'un oignon, à mesure que chaque couche est retirée, nous nous déplaçons vers le centre. D'autres analogies sont une poupée Matryoshka ou un jeu de passe-le-colis.
La largeur et la hauteur d'une matrice déterminent le nombre de couches dans cette matrice. Utilisons différents symboles pour chaque couche:
Une matrice 2 × 2 a 1 couche
. .
. .
Une matrice 3 × 3 a 2 couches
. . .
. x .
. . .
Une matrice 4 × 4 a 2 couches
. . . .
. x x .
. x x .
. . . .
Une matrice 5 × 5 a 3 couches
. . . . .
. x x x .
. x O x .
. x x x .
. . . . .
Une matrice 6 × 6 a 3 couches
. . . . . .
. x x x x .
. x O O x .
. x O O x .
. x x x x .
. . . . . .
Une matrice 7 × 7 a 4 couches
. . . . . . .
. x x x x x .
. x O O O x .
. x O - O x .
. x O O O x .
. x x x x x .
. . . . . . .
Vous remarquerez peut-être que l'augmentation de la largeur et de la hauteur d'une matrice n'augmente pas toujours le nombre de couches. En prenant les matrices ci-dessus et en tabulant les couches et les dimensions, nous voyons le nombre de couches augmenter une fois tous les deux incréments de largeur et de hauteur:
+-----+--------+
| N×N | Layers |
+-----+--------+
| 1×1 | 1 |
| 2×2 | 1 |
| 3×3 | 2 |
| 4×4 | 2 |
| 5×5 | 3 |
| 6×6 | 3 |
| 7×7 | 4 |
+-----+--------+
Cependant, toutes les couches n'ont pas besoin de tourner. Une matrice 1 × 1 est la même avant et après rotation. La couche centrale 1 × 1 est toujours la même avant et après la rotation, quelle que soit la taille de la matrice globale:
+-----+--------+------------------+
| N×N | Layers | Rotatable Layers |
+-----+--------+------------------+
| 1×1 | 1 | 0 |
| 2×2 | 1 | 1 |
| 3×3 | 2 | 1 |
| 4×4 | 2 | 2 |
| 5×5 | 3 | 2 |
| 6×6 | 3 | 3 |
| 7×7 | 4 | 3 |
+-----+--------+------------------+
Étant donné la matrice N × N, comment pouvons-nous déterminer par programme le nombre de couches dont nous avons besoin de tourner? Si nous divisons la largeur ou la hauteur par deux et ignorons le reste, nous obtenons les résultats suivants.
+-----+--------+------------------+---------+
| N×N | Layers | Rotatable Layers | N/2 |
+-----+--------+------------------+---------+
| 1×1 | 1 | 0 | 1/2 = 0 |
| 2×2 | 1 | 1 | 2/2 = 1 |
| 3×3 | 2 | 1 | 3/2 = 1 |
| 4×4 | 2 | 2 | 4/2 = 2 |
| 5×5 | 3 | 2 | 5/2 = 2 |
| 6×6 | 3 | 3 | 6/2 = 3 |
| 7×7 | 4 | 3 | 7/2 = 3 |
+-----+--------+------------------+---------+
Remarquez comment N/2
correspond le nombre de couches à faire pivoter? Parfois, le nombre de couches rotatives est inférieur de un au nombre total de couches dans la matrice. Cela se produit lorsque la couche la plus interne est formée d'un seul élément (c'est-à-dire une matrice 1 × 1) et n'a donc pas besoin d'être tournée. Il est simplement ignoré.
Nous aurons sans aucun doute besoin de ces informations dans notre fonction pour faire pivoter une matrice, alors ajoutons-la maintenant:
def rotate(matrix):
size = len(matrix)
# Rotatable layers only.
layer_count = size / 2
Maintenant que nous savons ce que sont les calques et comment déterminer le nombre de calques qui doivent réellement être tournés, comment isoler un seul calque pour pouvoir le faire pivoter? Tout d'abord, nous inspectons une matrice de la couche la plus externe, vers l'intérieur, jusqu'à la couche la plus interne. Une matrice 5 × 5 a trois couches au total et deux couches qui nécessitent une rotation:
. . . . .
. x x x .
. x O x .
. x x x .
. . . . .
Regardons d'abord les colonnes. La position des colonnes définissant la couche la plus externe, en supposant que nous comptons à partir de 0, est 0 et 4:
+--------+-----------+
| Column | 0 1 2 3 4 |
+--------+-----------+
| | . . . . . |
| | . x x x . |
| | . x O x . |
| | . x x x . |
| | . . . . . |
+--------+-----------+
0 et 4 sont également les positions des lignes pour la couche la plus externe.
+-----+-----------+
| Row | |
+-----+-----------+
| 0 | . . . . . |
| 1 | . x x x . |
| 2 | . x O x . |
| 3 | . x x x . |
| 4 | . . . . . |
+-----+-----------+
Ce sera toujours le cas puisque la largeur et la hauteur sont les mêmes. Par conséquent, nous pouvons définir les positions des colonnes et des lignes d'une couche avec seulement deux valeurs (plutôt que quatre).
En se déplaçant vers la deuxième couche, la position des colonnes est 1 et 3. Et, oui, vous l'avez deviné, c'est la même chose pour les lignes. Il est important de comprendre que nous devions à la fois incrémenter et décrémenter les positions des lignes et des colonnes lors du déplacement vers la couche suivante.
+-----------+---------+---------+---------+
| Layer | Rows | Columns | Rotate? |
+-----------+---------+---------+---------+
| Outermost | 0 and 4 | 0 and 4 | Yes |
| Inner | 1 and 3 | 1 and 3 | Yes |
| Innermost | 2 | 2 | No |
+-----------+---------+---------+---------+
Donc, pour inspecter chaque couche, nous voulons une boucle avec des compteurs à la fois croissants et décroissants qui représentent le déplacement vers l'intérieur, à partir de la couche la plus externe. Nous appellerons cela notre «boucle de couches».
def rotate(matrix):
size = len(matrix)
layer_count = size / 2
for layer in range(0, layer_count):
first = layer
last = size - first - 1
print 'Layer %d: first: %d, last: %d' % (layer, first, last)
# 5x5 matrix
matrix = [
[ 0, 1, 2, 3, 4],
[ 5, 6, 6, 8, 9],
[10,11,12,13,14],
[15,16,17,18,19],
[20,21,22,23,24]
]
rotate(matrix)
Le code ci-dessus parcourt les positions (ligne et colonne) de tous les calques nécessitant une rotation.
Layer 0: first: 0, last: 4
Layer 1: first: 1, last: 3
Nous avons maintenant une boucle fournissant les positions des lignes et des colonnes de chaque couche. Les variables first
et last
identifient la position d'index des première et dernière lignes et colonnes. Revenons à nos tableaux de lignes et de colonnes:
+--------+-----------+
| Column | 0 1 2 3 4 |
+--------+-----------+
| | . . . . . |
| | . x x x . |
| | . x O x . |
| | . x x x . |
| | . . . . . |
+--------+-----------+
+-----+-----------+
| Row | |
+-----+-----------+
| 0 | . . . . . |
| 1 | . x x x . |
| 2 | . x O x . |
| 3 | . x x x . |
| 4 | . . . . . |
+-----+-----------+
Nous pouvons donc naviguer à travers les couches d'une matrice. Maintenant, nous avons besoin d'un moyen de naviguer dans une couche afin de pouvoir déplacer des éléments autour de cette couche. Notez que les éléments ne «sautent» jamais d'un calque à un autre, mais ils se déplacent dans leurs calques respectifs.
La rotation de chaque élément d'un calque fait pivoter l'ensemble du calque. La rotation de tous les calques d'une matrice fait pivoter la matrice entière. Cette phrase est très importante, alors faites de votre mieux pour la comprendre avant de continuer.
Maintenant, nous avons besoin d'un moyen de déplacer réellement les éléments, c'est-à-dire de faire pivoter chaque élément, puis la couche, et finalement la matrice. Pour plus de simplicité, nous allons revenir à une matrice 3x3 - qui a une couche rotative.
0 1 2
3 4 5
6 7 8
Notre boucle de couche fournit les index des première et dernière colonnes, ainsi que les première et dernière lignes:
+-----+-------+
| Col | 0 1 2 |
+-----+-------+
| | 0 1 2 |
| | 3 4 5 |
| | 6 7 8 |
+-----+-------+
+-----+-------+
| Row | |
+-----+-------+
| 0 | 0 1 2 |
| 1 | 3 4 5 |
| 2 | 6 7 8 |
+-----+-------+
Parce que nos matrices sont toujours carrées, nous n'avons besoin que de deux variables first
et last
, puisque les positions d'index sont les mêmes pour les lignes et les colonnes.
def rotate(matrix):
size = len(matrix)
layer_count = size / 2
# Our layer loop i=0, i=1, i=2
for layer in range(0, layer_count):
first = layer
last = size - first - 1
# We want to move within a layer here.
Les variables première et dernière peuvent facilement être utilisées pour référencer les quatre coins d'une matrice. En effet, les coins eux-mêmes peuvent être définis en utilisant différentes permutations de first
et last
(sans soustraction, addition ou décalage de ces variables):
+---------------+-------------------+-------------+
| Corner | Position | 3x3 Values |
+---------------+-------------------+-------------+
| top left | (first, first) | (0,0) |
| top right | (first, last) | (0,2) |
| bottom right | (last, last) | (2,2) |
| bottom left | (last, first) | (2,0) |
+---------------+-------------------+-------------+
Pour cette raison, nous commençons notre rotation aux quatre coins extérieurs - nous allons les faire pivoter en premier. Soulignons-les avec *
.
* 1 *
3 4 5
* 7 *
Nous voulons échanger chacun *
avec le *
à droite de celui-ci. Alors allons-y, imprimons nos coins définis en utilisant uniquement différentes permutations de first
et last
:
def rotate(matrix):
size = len(matrix)
layer_count = size / 2
for layer in range(0, layer_count):
first = layer
last = size - first - 1
top_left = (first, first)
top_right = (first, last)
bottom_right = (last, last)
bottom_left = (last, first)
print 'top_left: %s' % (top_left)
print 'top_right: %s' % (top_right)
print 'bottom_right: %s' % (bottom_right)
print 'bottom_left: %s' % (bottom_left)
matrix = [
[0, 1, 2],
[3, 4, 5],
[6, 7, 8]
]
rotate(matrix)
La sortie doit être:
top_left: (0, 0)
top_right: (0, 2)
bottom_right: (2, 2)
bottom_left: (2, 0)
Maintenant, nous pouvons facilement échanger chacun des coins de notre boucle de calque:
def rotate(matrix):
size = len(matrix)
layer_count = size / 2
for layer in range(0, layer_count):
first = layer
last = size - first - 1
top_left = matrix[first][first]
top_right = matrix[first][last]
bottom_right = matrix[last][last]
bottom_left = matrix[last][first]
# bottom_left -> top_left
matrix[first][first] = bottom_left
# top_left -> top_right
matrix[first][last] = top_left
# top_right -> bottom_right
matrix[last][last] = top_right
# bottom_right -> bottom_left
matrix[last][first] = bottom_right
print_matrix(matrix)
print '---------'
rotate(matrix)
print_matrix(matrix)
Matrice avant de tourner les coins:
[0, 1, 2]
[3, 4, 5]
[6, 7, 8]
Matrice après rotation des coins:
[6, 1, 0]
[3, 4, 5]
[8, 7, 2]
Génial! Nous avons réussi à faire pivoter chaque coin de la matrice. Mais, nous n'avons pas fait pivoter les éléments au milieu de chaque couche. De toute évidence, nous avons besoin d'un moyen d'itérer au sein d'une couche.
Le problème est que la seule boucle de notre fonction jusqu'à présent (notre boucle de couche) passe à la couche suivante à chaque itération. Étant donné que notre matrice n'a qu'une seule couche rotative, la boucle de couche se termine après avoir tourné uniquement les coins. Voyons ce qui se passe avec une matrice 5 × 5 plus grande (où deux couches doivent tourner). Le code de fonction a été omis, mais il reste le même que ci-dessus:
matrix = [
[0, 1, 2, 3, 4],
[5, 6, 7, 8, 9],
[10, 11, 12, 13, 14],
[15, 16, 17, 18, 19],
[20, 21, 22, 23, 24]
]
print_matrix(matrix)
print '--------------------'
rotate(matrix)
print_matrix(matrix)
La sortie est:
[20, 1, 2, 3, 0]
[ 5, 16, 7, 6, 9]
[10, 11, 12, 13, 14]
[15, 18, 17, 8, 19]
[24, 21, 22, 23, 4]
Il ne devrait pas être surprenant que les coins de la couche la plus externe aient été tournés, mais vous pouvez également remarquer que les coins de la couche suivante (vers l'intérieur) ont également été tournés. C'est logique. Nous avons écrit du code pour naviguer à travers les couches et également pour faire pivoter les coins de chaque couche. Cela ressemble à un progrès, mais malheureusement, nous devons prendre du recul. Il est tout simplement inutile de passer au calque suivant tant que le calque précédent (extérieur) n'a pas été complètement tourné. Autrement dit, jusqu'à ce que chaque élément du calque ait été tourné. Faire tourner uniquement les coins ne fera pas l'affaire!
Respirez profondément. Nous avons besoin d'une autre boucle. Une boucle imbriquée pas moins. La nouvelle boucle imbriquée utilisera les variables first
et last
, plus un décalage pour naviguer dans une couche. Nous appellerons cette nouvelle boucle notre «boucle d'élément». La boucle d'élément visitera chaque élément le long de la rangée supérieure, chaque élément en bas à droite, chaque élément le long de la rangée inférieure et chaque élément en haut à gauche.
- Pour avancer le long de la ligne supérieure, l'index de colonne doit être incrémenté.
- En descendant vers la droite, l'index de ligne doit être incrémenté.
- Pour reculer le long du bas, l'index de colonne doit être décrémenté.
- Le déplacement vers le haut à gauche nécessite la diminution de l'index de ligne.
Cela semble complexe, mais c'est facile car le nombre de fois que nous incrémentons et décrémentons pour atteindre ce qui précède reste le même sur les quatre côtés de la matrice. Par exemple:
- Déplacez 1 élément sur la rangée supérieure.
- Déplacez 1 élément sur le côté droit.
- Déplacez 1 élément vers l'arrière le long de la rangée inférieure.
- Déplacez 1 élément vers le haut à gauche.
Cela signifie que nous pouvons utiliser une seule variable en combinaison avec les variables first
et last
pour nous déplacer dans une couche. Il peut être utile de noter que le déplacement sur la rangée supérieure et sur le côté droit nécessite une incrémentation. Tout en se déplaçant vers l'arrière le long du bas et vers le haut du côté gauche, les deux nécessitent une décrémentation.
def rotate(matrix):
size = len(matrix)
layer_count = size / 2
# Move through layers (i.e. layer loop).
for layer in range(0, layer_count):
first = layer
last = size - first - 1
# Move within a single layer (i.e. element loop).
for element in range(first, last):
offset = element - first
# 'element' increments column (across right)
top_element = (first, element)
# 'element' increments row (move down)
right_side = (element, last)
# 'last-offset' decrements column (across left)
bottom = (last, last-offset)
# 'last-offset' decrements row (move up)
left_side = (last-offset, first)
print 'top: %s' % (top)
print 'right_side: %s' % (right_side)
print 'bottom: %s' % (bottom)
print 'left_side: %s' % (left_side)
Maintenant, nous devons simplement affecter le haut au côté droit, le côté droit au bas, le bas au côté gauche et le côté gauche au haut. En rassemblant tout cela, nous obtenons:
def rotate(matrix):
size = len(matrix)
layer_count = size / 2
for layer in range(0, layer_count):
first = layer
last = size - first - 1
for element in range(first, last):
offset = element - first
top = matrix[first][element]
right_side = matrix[element][last]
bottom = matrix[last][last-offset]
left_side = matrix[last-offset][first]
matrix[first][element] = left_side
matrix[element][last] = top
matrix[last][last-offset] = right_side
matrix[last-offset][first] = bottom
Compte tenu de la matrice:
0, 1, 2
3, 4, 5
6, 7, 8
Notre rotate
fonction se traduit par:
6, 3, 0
7, 4, 1
8, 5, 2