What is the right way to rotate an array?
I realise that this doesn't fully answer the question, but for the special case of square matrices, there's already a suitable function: Image`MorphologicalOperationsDump`SquareMatrixRotate
(which, no doubt, is how Sjoerd's suggestion works internally). This is undocumented, of course!
The implementation is the following (modulo some bugs I've fixed--i
and j
were not localized in the original, leading to problems if you want to rotate a matrix containing these symbols, there were no conditions on the arguments, and [admittedly, a minor point] the rotation matrix was numericized only after inversion, which is inefficient):
SquareMatrixRotate[mat_?MatrixQ, angle_?NumericQ] /; Equal @@ Dimensions[mat] :=
Module[{
inv, dim, ct,
i, j,
ii, jj
},
inv = Inverse@RotationMatrix@N[angle];
dim = First@Dimensions[mat];
ct = (dim + 1)/2;
Table[
{ii, jj} = MapThread[
Clip[#1, {1, #2}, {1, 1}] &,
{Round[inv.{i - ct, j - ct} + {ct, ct}], {dim, dim}}
];
mat[[ii, jj]],
{i, dim}, {j, dim}
]
]
Is it in any sense better than your existing methods? Well, you can rotate by an arbitrary angle, although the results may not make much sense for angles for which no "natural" rotation is possible. Other than that, I would say no: personally, I would have used Reverse
as you did.
FindPermutation
may be useful as part of a yet-to-be-found general method as follows (using your examples as templates for the three rotations):
a = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
b = (Reverse[a, {2}] // Transpose );
c = (Reverse[a // Transpose, {2}]);
d = Reverse[a, {1, 2}];
cntrclckws = FindPermutation[Flatten@a, Flatten@b];
clckws = Cycles[Reverse /@ RotateLeft /@ cntrclckws[[1]]];
flp = PermutationProduct[clckws, clckws]
With
m = {{r, s, t}, {u, v, w}, {x, y, z}};
and
{Permute[Flatten@m, cntrclckws] // Partition[#, 3] &,
Permute[Flatten@m, clckws] // Partition[#, 3] &,
Permute[Flatten@m, flp] // Partition[#, 3] &}
one can "rotate" m
to :
{{{t, w, z}, {s, v, y}, {r, u, x}}, {{x, u, r}, {y, v, s}, {z, w, t}}, {{z, y, x}, {w, v, u}, {t, s, r}}}
I don't think of this as rotating in the sense of an angle, rather, the sequence of positions around the matrix. So I came up with a rather different solution.
First let's create the simple case:
test[3, 3] = Array[a, {3, 3}]
{{a[1, 1], a[1, 2], a[1, 3]}, {a[2, 1], a[2, 2], a[2, 3]}, {a[3, 1], a[3, 2], a[3, 3]}}
This is the list of positions that rotates
snake = Join[Table[{1, i}, {i, 3}], {{2, 3}},
Table[{3, i}, {i, 3, 1, -1}], {{2, 1}}]
{{1, 1}, {1, 2}, {1, 3}, {2, 3}, {3, 3}, {3, 2}, {3, 1}, {2, 1}}
RotateLeft[snake, 1]
{{1, 2}, {1, 3}, {2, 3}, {3, 3}, {3, 2}, {3, 1}, {2, 1}, {1, 1}}
output = test[3, 3];
ReplacePart[output,
Thread[RotateLeft[snake,
1] -> (test[3, 3][[Sequence @@ #]] & /@ snake)] ] // MatrixForm
$$\left( \begin{array}{ccc} a[2,1] & a[1,1] & a[1,2] \\ a[3,1] & a[2,2] & a[1,3] \\ a[3,2] & a[3,3] & a[2,3] \\ \end{array} \right)$$
Demonstrating the cases asked for in the question:
aa = Partition[Range[9], 3];
rotateMatrix[aa, -2] // MatrixForm
$$\left( \begin{array}{ccc} 3 & 6 & 9 \\ 2 & 5 & 8 \\ 1 & 4 & 7 \\ \end{array} \right)$$
rotateMatrix[aa, 2] // MatrixForm
$$\left( \begin{array}{ccc} 7 & 4 & 1 \\ 8 & 5 & 2 \\ 9 & 6 & 3 \\ \end{array} \right)$$
rotateMatrix[aa, 4] // MatrixForm
$$\left( \begin{array}{ccc} 9 & 8 & 7 \\ 6 & 5 & 4 \\ 3 & 2 & 1 \\ \end{array} \right)$$
We can generalise this to the case where the matrix has either three rows or three columns pretty easily.
rotateMatrix[a_?MatrixQ, j_Integer] /; Length[a] == 3 || Length[a[[1]]] == 3 :=
Module[{output = a, m = Length[a], n = Length[a[[1]]], snake},
snake = Join[Table[{1, i}, {i, n}], Table[{i, n}, {i, 2, m - 1}],
Table[{m, i}, {i, n, 1, -1}], Table[{i, 1}, {i, m - 1, 2, -1}]];
ReplacePart[output,
Thread[RotateLeft[snake, j] -> (a[[Sequence @@ #]] & /@ snake)] ]
]
rotateMatrix[test[3, 3], 1] // MatrixForm
$$\left( \begin{array}{ccc} a[2,1] & a[1,1] & a[1,2] \\ a[3,1] & a[2,2] & a[1,3] \\ a[3,2] & a[3,3] & a[2,3] \\ \end{array} \right) $$
test[3, 4] = Array[b, {3, 4}]
{{b[1, 1], b[1, 2], b[1, 3], b[1, 4]}, {b[2, 1], b[2, 2], b[2, 3], b[2, 4]}, {b[3, 1], b[3, 2], b[3, 3], b[3, 4]}}
It works for both positive (rotate clockwise) and negative (rotate anti-clockwise) integers as the second argument.
rotateMatrix[test[3, 4], -2] // MatrixForm
$$\left( \begin{array}{cccc} b[1,3] & b[1,4] & b[2,4] & b[3,4] \\ b[1,2] & b[2,2] & b[2,3] & b[3,3] \\ b[1,1] & b[2,1] & b[3,1] & b[3,2] \\ \end{array} \right) $$
It would be possible to generalise this further to cases where neither the number of rows nor the number of columns is 3. One would have to specify what happens to the "inner rings" of positions that rotate. For example, in a 5 by 5 matrix, does the inner ring rotate three-fifths of the number of positions that the outer ring does?