Me gustaría agregar un poco más de detalle. En esta respuesta, los conceptos clave se repiten, el ritmo es lento e intencionalmente repetitivo. La solución proporcionada aquí no es la más sintácticamente compacta, sin embargo, está destinada a aquellos que desean aprender qué es la rotación de matrices y la implementación resultante.
En primer lugar, ¿qué es una matriz? Para los fines de esta respuesta, una matriz es solo una cuadrícula donde el ancho y la altura son iguales. Tenga en cuenta que el ancho y el alto de una matriz pueden ser diferentes, pero para simplificar, este tutorial considera solo matrices con igual ancho y alto ( matrices cuadradas ). Y sí, matrices es el plural de matrix.
Los ejemplos de matrices son: 2 × 2, 3 × 3 o 5 × 5. O, más generalmente, N × N. Una matriz 2 × 2 tendrá 4 cuadrados porque 2 × 2 = 4. Una matriz de 5 × 5 tendrá 25 cuadrados porque 5 × 5 = 25. Cada cuadrado se llama elemento o entrada. Representaremos cada elemento con un punto ( .
) en los siguientes diagramas:
Matriz 2 × 2
. .
. .
Matriz 3 × 3
. . .
. . .
. . .
Matriz 4 × 4
. . . .
. . . .
. . . .
. . . .
Entonces, ¿qué significa rotar una matriz? Tomemos una matriz de 2 × 2 y pongamos algunos números en cada elemento para que se pueda observar la rotación:
0 1
2 3
Girar esto 90 grados nos da:
2 0
3 1
Literalmente giramos toda la matriz una vez hacia la derecha, como si girara el volante de un automóvil. Puede ser útil pensar en "inclinar" la matriz sobre su lado derecho. Queremos escribir una función, en Python, que tome una matriz y gire una vez hacia la derecha. La firma de la función será:
def rotate(matrix):
# Algorithm goes here.
La matriz se definirá utilizando una matriz bidimensional:
matrix = [
[0,1],
[2,3]
]
Por lo tanto, la primera posición de índice accede a la fila. La segunda posición del índice accede a la columna:
matrix[row][column]
Definiremos una función de utilidad para imprimir una matriz.
def print_matrix(matrix):
for row in matrix:
print row
Un método para rotar una matriz es hacerlo capa por capa. ¿Pero qué es una capa? Piensa en una cebolla. Al igual que las capas de una cebolla, a medida que se elimina cada capa, nos movemos hacia el centro. Otras analogías es una muñeca Matryoshka o un juego de pasar el paquete.
El ancho y la altura de una matriz dictan el número de capas en esa matriz. Usemos diferentes símbolos para cada capa:
Una matriz 2 × 2 tiene 1 capa
. .
. .
Una matriz 3 × 3 tiene 2 capas.
. . .
. x .
. . .
Una matriz 4 × 4 tiene 2 capas.
. . . .
. x x .
. x x .
. . . .
Una matriz de 5 × 5 tiene 3 capas.
. . . . .
. x x x .
. x O x .
. x x x .
. . . . .
Una matriz de 6 × 6 tiene 3 capas.
. . . . . .
. x x x x .
. x O O x .
. x O O x .
. x x x x .
. . . . . .
Una matriz de 7 × 7 tiene 4 capas.
. . . . . . .
. x x x x x .
. x O O O x .
. x O - O x .
. x O O O x .
. x x x x x .
. . . . . . .
Puede notar que al incrementar el ancho y la altura de una matriz en uno, no siempre aumenta el número de capas. Tomando las matrices anteriores y tabulando las capas y dimensiones, vemos que el número de capas aumenta una vez por cada dos incrementos de ancho y alto:
+-----+--------+
| N×N | Layers |
+-----+--------+
| 1×1 | 1 |
| 2×2 | 1 |
| 3×3 | 2 |
| 4×4 | 2 |
| 5×5 | 3 |
| 6×6 | 3 |
| 7×7 | 4 |
+-----+--------+
Sin embargo, no todas las capas necesitan rotación. Una matriz 1 × 1 es la misma antes y después de la rotación. La capa central 1 × 1 siempre es la misma antes y después de la rotación, sin importar qué tan grande sea la matriz general:
+-----+--------+------------------+
| 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 |
+-----+--------+------------------+
Dada la matriz N × N, ¿cómo podemos determinar mediante programación el número de capas que necesitamos rotar? Si dividimos el ancho o la altura entre dos e ignoramos el resto, obtenemos los siguientes resultados.
+-----+--------+------------------+---------+
| 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 |
+-----+--------+------------------+---------+
¿Observa cómo N/2
coincide el número de capas que deben rotarse? A veces, el número de capas giratorias es uno menos el número total de capas en la matriz. Esto ocurre cuando la capa más interna está formada por un solo elemento (es decir, una matriz 1 × 1) y, por lo tanto, no necesita ser rotada. Simplemente se ignora.
Sin duda, necesitaremos esta información en nuestra función para rotar una matriz, así que añádala ahora:
def rotate(matrix):
size = len(matrix)
# Rotatable layers only.
layer_count = size / 2
Ahora que sabemos qué capas son y cómo determinar el número de capas que realmente necesitan rotación, ¿cómo aislamos una sola capa para poder rotarla? En primer lugar, inspeccionamos una matriz desde la capa más externa, hacia adentro, hasta la capa más interna. Una matriz de 5 × 5 tiene tres capas en total y dos capas que necesitan rotación:
. . . . .
. x x x .
. x O x .
. x x x .
. . . . .
Veamos primero las columnas. La posición de las columnas que definen la capa más externa, suponiendo que contamos desde 0, son 0 y 4:
+--------+-----------+
| Column | 0 1 2 3 4 |
+--------+-----------+
| | . . . . . |
| | . x x x . |
| | . x O x . |
| | . x x x . |
| | . . . . . |
+--------+-----------+
0 y 4 también son las posiciones de las filas para la capa más externa.
+-----+-----------+
| Row | |
+-----+-----------+
| 0 | . . . . . |
| 1 | . x x x . |
| 2 | . x O x . |
| 3 | . x x x . |
| 4 | . . . . . |
+-----+-----------+
Este siempre será el caso, ya que el ancho y la altura son los mismos. Por lo tanto, podemos definir las posiciones de columna y fila de una capa con solo dos valores (en lugar de cuatro).
Moviéndose hacia adentro a la segunda capa, la posición de las columnas es 1 y 3. Y, sí, lo adivinó, es lo mismo para las filas. Es importante comprender que tuvimos que aumentar y disminuir las posiciones de las filas y columnas al movernos hacia la siguiente capa.
+-----------+---------+---------+---------+
| Layer | Rows | Columns | Rotate? |
+-----------+---------+---------+---------+
| Outermost | 0 and 4 | 0 and 4 | Yes |
| Inner | 1 and 3 | 1 and 3 | Yes |
| Innermost | 2 | 2 | No |
+-----------+---------+---------+---------+
Entonces, para inspeccionar cada capa, queremos un bucle con contadores crecientes y decrecientes que representen moverse hacia adentro, comenzando desde la capa más externa. Llamaremos a esto nuestro 'bucle de capa'.
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)
El código anterior recorre las posiciones (fila y columna) de cualquier capa que necesite rotación.
Layer 0: first: 0, last: 4
Layer 1: first: 1, last: 3
Ahora tenemos un bucle que proporciona las posiciones de las filas y columnas de cada capa. Las variables first
e last
identifican la posición del índice de las primeras y últimas filas y columnas. Volviendo a nuestras tablas de filas y columnas:
+--------+-----------+
| 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 | . . . . . |
+-----+-----------+
Entonces podemos navegar a través de las capas de una matriz. Ahora necesitamos una forma de navegar dentro de una capa para poder mover elementos alrededor de esa capa. Tenga en cuenta que los elementos nunca 'saltan' de una capa a otra, pero se mueven dentro de sus respectivas capas.
Al girar cada elemento en una capa, se rota toda la capa. Al girar todas las capas en una matriz, se rota toda la matriz. Esta oración es muy importante, así que por favor, intente comprenderla antes de continuar.
Ahora, necesitamos una forma de mover elementos, es decir, rotar cada elemento y, posteriormente, la capa y, finalmente, la matriz. Para simplificar, volveremos a una matriz de 3x3, que tiene una capa giratoria.
0 1 2
3 4 5
6 7 8
Nuestro bucle de capa proporciona los índices de la primera y la última columna, así como la primera y la última fila:
+-----+-------+
| 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 |
+-----+-------+
Debido a que nuestras matrices son siempre cuadradas, solo necesitamos dos variables first
y last
, dado que las posiciones de índice son las mismas para filas y columnas.
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.
Las variables primero y último se pueden usar fácilmente para hacer referencia a las cuatro esquinas de una matriz. Esto se debe a que las esquinas en sí pueden definirse usando varias permutaciones de first
y last
(sin sustracción, suma ni compensación de esas 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) |
+---------------+-------------------+-------------+
Por esta razón, comenzamos nuestra rotación en las cuatro esquinas exteriores; las rotaremos primero. Destaquémoslos con *
.
* 1 *
3 4 5
* 7 *
Queremos intercambiar cada una *
con el *
de la derecha de la misma. Así que sigamos imprimiendo nuestras esquinas definidas usando solo varias permutaciones de first
y 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 salida debe ser:
top_left: (0, 0)
top_right: (0, 2)
bottom_right: (2, 2)
bottom_left: (2, 0)
Ahora podríamos intercambiar fácilmente cada una de las esquinas desde nuestro bucle de capa:
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)
Matriz antes de girar esquinas:
[0, 1, 2]
[3, 4, 5]
[6, 7, 8]
Matriz después de rotar esquinas:
[6, 1, 0]
[3, 4, 5]
[8, 7, 2]
¡Excelente! Hemos rotado con éxito cada esquina de la matriz. Pero no hemos rotado los elementos en el medio de cada capa. Claramente, necesitamos una forma de iterar dentro de una capa.
El problema es que el único bucle en nuestra función hasta ahora (nuestro bucle de capa) se mueve a la siguiente capa en cada iteración. Como nuestra matriz tiene solo una capa giratoria, el bucle de capa sale después de rotar solo las esquinas. Veamos qué sucede con una matriz más grande de 5 × 5 (donde dos capas necesitan rotación). El código de función se ha omitido, pero sigue siendo el mismo que el anterior:
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)
El resultado es:
[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]
No debería sorprender que las esquinas de la capa más externa se hayan girado, pero también puede notar que las esquinas de la siguiente capa (hacia adentro) también se han girado. Esto tiene sentido. Hemos escrito código para navegar por las capas y también para rotar las esquinas de cada capa. Esto se siente como un progreso, pero desafortunadamente debemos dar un paso atrás. Simplemente no es bueno pasar a la siguiente capa hasta que la capa anterior (exterior) se haya rotado por completo. Es decir, hasta que cada elemento en la capa haya sido girado. ¡Girar solo las esquinas no funcionará!
Tomar una respiración profunda. Necesitamos otro bucle. Un bucle anidado no menos. El nuevo bucle anidado utilizará las variables first
y last
, más un desplazamiento para navegar dentro de una capa. Llamaremos a este nuevo bucle nuestro 'bucle de elementos'. El bucle de elementos visitará cada elemento a lo largo de la fila superior, cada elemento en el lado derecho, cada elemento en la fila inferior y cada elemento en el lado izquierdo.
- Avanzar a lo largo de la fila superior requiere que se incremente el índice de la columna.
- Moverse hacia el lado derecho requiere que se incremente el índice de fila.
- Moverse hacia atrás a lo largo de la parte inferior requiere que se disminuya el índice de la columna.
- Mover hacia arriba por el lado izquierdo requiere que se disminuya el índice de la fila.
Esto suena complejo, pero se hace fácil porque el número de veces que incrementamos y disminuimos para lograr lo anterior sigue siendo el mismo en los cuatro lados de la matriz. Por ejemplo:
- Mueve 1 elemento a través de la fila superior.
- Mueve 1 elemento hacia abajo por el lado derecho.
- Mueva 1 elemento hacia atrás a lo largo de la fila inferior.
- Mueve 1 elemento hacia arriba del lado izquierdo.
Esto significa que podemos usar una sola variable en combinación con las variables first
y last
para movernos dentro de una capa. Puede ser útil notar que moverse por la fila superior y hacia abajo por el lado derecho requiere un incremento. Mientras se mueve hacia atrás a lo largo de la parte inferior y hacia arriba del lado izquierdo, ambos requieren disminución.
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)
Ahora simplemente tenemos que asignar la parte superior al lado derecho, el lado derecho al fondo, el fondo al lado izquierdo y el lado izquierdo a la parte superior. Al poner todo esto juntos obtenemos:
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
Dada la matriz:
0, 1, 2
3, 4, 5
6, 7, 8
Nuestra rotate
función da como resultado:
6, 3, 0
7, 4, 1
8, 5, 2