C#
アルゴリズム
C#小品集シリース

C#: 2次元配列(N*N)の回転/反転の10の操作を2つの基本操作で実現する

実現したいこと

N行N列の2次元配列に対して、以下の10個のメソッドを定義したい。

  1. 中央縦線を軸に反転
  2. 中央横線を軸に反転
  3. 右斜め上対角線を軸に反転
  4. 右斜め下対角線を軸に反転
  5. 左へ90度回転
  6. 左へ180度回転
  7. 左へ270度回転
  8. 右へ90度回転
  9. 右へ180度回転
  10. 右へ270度回転

どうやって解くか

N行N列の2次元配列の回転、反転は、前述の通り10種類が存在します。
しかし、右回転は左回転から単純に導きだせるので、実質的には7種類のメソッドが定義できれば良い、ということになります。

そして、この回転操作は、実は「中央横線を軸に反転」と「右斜め下対角線を軸に反転」の2つを定義さえすれば、その組み合わせで実現できてしまいます。

2つの対角線を言葉で区別したいのですが、正式な呼び方がわかりません。ここでは、左上から右下へ引く対角線を「右斜め下対角線」、左下から右上へ引く対角線を「右斜め上対角線」と呼ぶことにします。

「中央縦線を軸に反転」「右斜め上対角線を軸に反転」といった組み合わせでもOKですが、ここでは、「中央横線を軸に反転」と「右斜め下対角線を軸に反転」の2つのメソッドを定義することで、すべての配列の回転/反転を実現してみます。

中央横線を軸に反転

まずは、中央横線を軸に反転をするメソッドを定義します。

// 2次元配列の回転を行うクラス(要素数には依存しない)
static class ArrayRotation {

    // 中央横軸を中心に回転する
    public static int[,] HorRotate(this int[,] array) {
        var work = array.Clone() as int[,];
        int xLeng = work.GetLength(0);
        for (int x = 0; x < xLeng / 2; x++) {
            for (int y = 0; y < work.GetLength(1); y++) {
                int temp = work[x, y];
                work[x, y] = work[xLeng - x - 1, y];
                work[xLeng - x - 1, y] = temp;
            }
        }
        return work;
    }
}

HorRotateメソッドは、2次元配列の拡張メソッドとして定義しています。
引数の配列そのものは変更させずに、反転後の新たな配列オブ上クトを返すようにしています。

配列の内容を表示するPrintメソッドも定義しておきましょう。

class Program {
    ......

    static void Print(int[,] array) {
        for (int x = 0; x < array.GetLength(0); x++) {
            for (int y = 0; y < array.GetLength(1); y++)
                Console.Write($"{array[x, y],3}");
            Console.WriteLine();
        }
        Console.WriteLine();
    }
}

Printメソッドは、Programkクラスに定義しました。

では動かしてみます。

var array = new int[,] {
        { 1, 2, 3 },
        { 4, 5, 6 },
        { 7, 8, 9 },
};
Print(array.HorRotate());

結果です。

  7  8  9
  4  5  6
  1  2  3

うまく動いているようですね。

右斜め下対角線を軸に反転

今度は、右斜め下対角線を軸に反転するメソッドを定義します。

// 2次元配列の回転を行うクラス(要素数には依存しない)
static class ArrayRotation {
    ...... 

    // 左上から右下への斜め対角線軸を中心に反転する
    public static int[,] RightDownDiagRotate(this int[,] array) {
        var work = array.Clone() as int[,];
        for (int y = 1; y < work.GetLength(1); y++) {
            for (int x = 0; x < y; x++) {
                int temp = work[x, y];
                work[x, y] = work[y, x];
                work[y, x] = temp;
            }
        }
        return work;
    }
}

x,yの値は、対角線の左側だけになるようにループさせています。これで、

work[x, y] <=> work[y, x];

の入れ替えを行えば、右斜め下対角線を軸に反転することができます。

これも確認してみます。

var array = new int[,] {
        { 1, 2, 3 },
        { 4, 5, 6 },
        { 7, 8, 9 },
};
array.FlipHorizontally().Print();
  1  4  7
  2  5  8
  3  6  9

うまく動いているようです。

この2つのメソッドを組み合わせる

上の2つのメソッドを組み合わせるといろんな回転、反転処理を定義することができます。

例えば、

array.RightDownDiagRotate().HorRotate();

と組み合わせると、左に90度の回転処理になります。つまり、これで以下の8つのメソッドが定義できます。

    public static int[,] TurnLeft90(this int[,] array) {
        return array.RightDownDiagRotate().HorRotate();
    }
    public static int[,] TurnLeft180(this int[,] array) {
        return array.TurnLeft90().TurnLeft90();
    }
    public static int[,] TurnLeft270(this int[,] array) {
        return array.TurnLeft180().TurnLeft90();
    }

    public static int[,] TurnRight90(this int[,] array) {
        return array.TurnLeft270();
    }
    public static int[,] TurnRight180(this int[,] array) {
        return array.TurnLeft180();
    }
    public static int[,] TurnRight270(this int[,] array) {
        return array.TurnLeft90();
    }

また、

array.TurnLeft180().RightDownDiagRotate();

で、右上から左下への軸にした反転処理になります。

残り1つは、縦中央線を軸にした反転です。

array.RightDownDiagRotate().TurnRight90();

上のように2つのメソッドの組み合わせで実現できます、

  • 中央横線を軸に反転
  • 右斜め下対角線を軸に反転

という2つの関数さえ作成すれば、(効率は悪いですが)この2つの関数の組み合わせで、すべての回転を実現できるって面白いですね。

C#のコード

ArrayRotationクラス全体のコードを示します。ここでは、360度回転のメソッドも定義しています。

using System;
using System.Collections.Generic;
using System.Text;

namespace Rotate2DArrayApp {
    // 2次元配列の回転を行うクラス(要素数には依存しない)
    static class ArrayRotation {
        // これが基準
        public static int[,] TurnLeft90(this int[,] array) {
            return array.RightDownDiagRotate().HorRotate();
        }
        public static int[,] TurnLeft180(this int[,] array) {
            return array.TurnLeft90().TurnLeft90();
        }
        public static int[,] TurnLeft270(this int[,] array) {
            return array.TurnLeft180().TurnLeft90();
        }
        public static int[,] TurnLeft360(this int[,] array) {
            return array.Clone() as int[,];
        }

        public static int[,] TurnRight90(this int[,] array) {
            return array.TurnLeft270();
        }
        public static int[,] TurnRight180(this int[,] array) {
            return array.TurnLeft180();
        }
        public static int[,] TurnRight270(this int[,] array) {
            return array.TurnLeft90();
        }
        public static int[,] TurnRight360(this int[,] array) {
            return array.Clone() as int[,];
        }
        public static int[,] VertRotate(this int[,] array) {
            return array.RightDownDiagRotate().TurnRight90();
        }

        // 中央横軸を中心に回転する
        public static int[,] HorRotate(this int[,] array) {
            var work = array.Clone() as int[,];
            int xLeng = work.GetLength(0);
            for (int x = 0; x < xLeng / 2; x++) {
                for (int y = 0; y < work.GetLength(1); y++) {
                    int temp = work[x, y];
                    work[x, y] = work[xLeng - x - 1, y];
                    work[xLeng - x - 1, y] = temp;
                }
            }
            return work;
        }

        public static int[,] RightUpDiagRotate(this int[,] array) {
            return array.TurnLeft180().RightDownDiagRotate();
        }
            // 左上から右下への斜め軸を中心に回転する
        public static int[,] RightDownDiagRotate(this int[,] array) {
            var work = array.Clone() as int[,];
            for (int y = 1; y < work.GetLength(1); y++) {
                for (int x = 0; x < y; x++) {
                    int temp = work[x, y];
                    work[x, y] = work[y, x];
                    work[y, x] = temp;
                }
            }
            return work;
        }
    }

}

動作を確認してみる。

9*9の配列での動作を確認するコードを書いてみました。

using System;

namespace Rotate2DArrayApp {
    class Program {
        static void Main(string[] args) {
            var array = new int[,] {
                // array[x,y]でアクセス。x軸が縦、y軸が横
                // array[0,2]->13  array[4,0]-> 51
                { 11, 12, 13, 14, 15, 16, 17, 18, 19 },
                { 21, 22, 23, 24, 25, 26, 27, 28, 29 },
                { 31, 32, 33, 34, 35, 36, 37, 38, 39 },
                { 41, 42, 43, 44, 45, 46, 47, 48, 49 },
                { 51, 52, 53, 54, 55, 56, 57, 58, 59 },
                { 61, 62, 63, 64, 65, 66, 67, 68, 69 },
                { 71, 72, 73, 74, 75, 76, 77, 78, 79 },
                { 81, 82, 83, 84, 85, 86, 87, 88, 89 },
                { 91, 92, 93, 94, 95, 96, 97, 98, 99 }
            };

            Console.WriteLine("オリジナル");
            Print(array);
            Console.WriteLine("左90度回転");
            Print(array.TurnLeft90());
            Console.WriteLine("左180度回転");
            Print(array.TurnLeft180());
            Console.WriteLine("左270回転");
            Print(array.TurnLeft270());
            Console.WriteLine("左360度回転");
            Print(array.TurnLeft360());
            Console.WriteLine("右90度回転");
            Print(array.TurnRight90());
            Console.WriteLine("右180度回転");
            Print(array.TurnRight180());
            Console.WriteLine("右270度回転");
            Print(array.TurnRight270());
            Console.WriteLine("右360度回転");
            Print(array.TurnRight360());
            Console.WriteLine("水平軸回転");
            Print(array.HorRotate());
            Console.WriteLine("垂直軸回転");
            Print(array.VertRotate());
            Console.WriteLine("右下がり斜線軸回転");
            Print(array.RightDownDiagRotate());
            Console.WriteLine("右上がり斜線軸回転");
            Print(array.RightUpDiagRotate());
        }

        static void Print(int[,] array) {
            for (int x = 0; x < array.GetLength(0); x++) {
                for (int y = 0; y < array.GetLength(1); y++)
                    Console.Write($"{array[x, y],3}");
                Console.WriteLine();
            }
            Console.WriteLine();

        }
    }
}

結果

上記確認プログラムの実行結果です。

オリジナル
 11 12 13 14 15 16 17 18 19
 21 22 23 24 25 26 27 28 29
 31 32 33 34 35 36 37 38 39
 41 42 43 44 45 46 47 48 49
 51 52 53 54 55 56 57 58 59
 61 62 63 64 65 66 67 68 69
 71 72 73 74 75 76 77 78 79
 81 82 83 84 85 86 87 88 89
 91 92 93 94 95 96 97 98 99

左90度回転
 19 29 39 49 59 69 79 89 99
 18 28 38 48 58 68 78 88 98
 17 27 37 47 57 67 77 87 97
 16 26 36 46 56 66 76 86 96
 15 25 35 45 55 65 75 85 95
 14 24 34 44 54 64 74 84 94
 13 23 33 43 53 63 73 83 93
 12 22 32 42 52 62 72 82 92
 11 21 31 41 51 61 71 81 91

左180度回転
 99 98 97 96 95 94 93 92 91
 89 88 87 86 85 84 83 82 81
 79 78 77 76 75 74 73 72 71
 69 68 67 66 65 64 63 62 61
 59 58 57 56 55 54 53 52 51
 49 48 47 46 45 44 43 42 41
 39 38 37 36 35 34 33 32 31
 29 28 27 26 25 24 23 22 21
 19 18 17 16 15 14 13 12 11

左270回転
 91 81 71 61 51 41 31 21 11
 92 82 72 62 52 42 32 22 12
 93 83 73 63 53 43 33 23 13
 94 84 74 64 54 44 34 24 14
 95 85 75 65 55 45 35 25 15
 96 86 76 66 56 46 36 26 16
 97 87 77 67 57 47 37 27 17
 98 88 78 68 58 48 38 28 18
 99 89 79 69 59 49 39 29 19

左360度回転
 11 12 13 14 15 16 17 18 19
 21 22 23 24 25 26 27 28 29
 31 32 33 34 35 36 37 38 39
 41 42 43 44 45 46 47 48 49
 51 52 53 54 55 56 57 58 59
 61 62 63 64 65 66 67 68 69
 71 72 73 74 75 76 77 78 79
 81 82 83 84 85 86 87 88 89
 91 92 93 94 95 96 97 98 99

右90度回転
 91 81 71 61 51 41 31 21 11
 92 82 72 62 52 42 32 22 12
 93 83 73 63 53 43 33 23 13
 94 84 74 64 54 44 34 24 14
 95 85 75 65 55 45 35 25 15
 96 86 76 66 56 46 36 26 16
 97 87 77 67 57 47 37 27 17
 98 88 78 68 58 48 38 28 18
 99 89 79 69 59 49 39 29 19

右180度回転
 99 98 97 96 95 94 93 92 91
 89 88 87 86 85 84 83 82 81
 79 78 77 76 75 74 73 72 71
 69 68 67 66 65 64 63 62 61
 59 58 57 56 55 54 53 52 51
 49 48 47 46 45 44 43 42 41
 39 38 37 36 35 34 33 32 31
 29 28 27 26 25 24 23 22 21
 19 18 17 16 15 14 13 12 11

右270度回転
 19 29 39 49 59 69 79 89 99
 18 28 38 48 58 68 78 88 98
 17 27 37 47 57 67 77 87 97
 16 26 36 46 56 66 76 86 96
 15 25 35 45 55 65 75 85 95
 14 24 34 44 54 64 74 84 94
 13 23 33 43 53 63 73 83 93
 12 22 32 42 52 62 72 82 92
 11 21 31 41 51 61 71 81 91

右360度回転
 11 12 13 14 15 16 17 18 19
 21 22 23 24 25 26 27 28 29
 31 32 33 34 35 36 37 38 39
 41 42 43 44 45 46 47 48 49
 51 52 53 54 55 56 57 58 59
 61 62 63 64 65 66 67 68 69
 71 72 73 74 75 76 77 78 79
 81 82 83 84 85 86 87 88 89
 91 92 93 94 95 96 97 98 99

水平軸回転
 91 92 93 94 95 96 97 98 99
 81 82 83 84 85 86 87 88 89
 71 72 73 74 75 76 77 78 79
 61 62 63 64 65 66 67 68 69
 51 52 53 54 55 56 57 58 59
 41 42 43 44 45 46 47 48 49
 31 32 33 34 35 36 37 38 39
 21 22 23 24 25 26 27 28 29
 11 12 13 14 15 16 17 18 19

垂直軸回転
 19 18 17 16 15 14 13 12 11
 29 28 27 26 25 24 23 22 21
 39 38 37 36 35 34 33 32 31
 49 48 47 46 45 44 43 42 41
 59 58 57 56 55 54 53 52 51
 69 68 67 66 65 64 63 62 61
 79 78 77 76 75 74 73 72 71
 89 88 87 86 85 84 83 82 81
 99 98 97 96 95 94 93 92 91

右下がり斜線軸回転
 11 21 31 41 51 61 71 81 91
 12 22 32 42 52 62 72 82 92
 13 23 33 43 53 63 73 83 93
 14 24 34 44 54 64 74 84 94
 15 25 35 45 55 65 75 85 95
 16 26 36 46 56 66 76 86 96
 17 27 37 47 57 67 77 87 97
 18 28 38 48 58 68 78 88 98
 19 29 39 49 59 69 79 89 99

右上がり斜線軸回転
 99 89 79 69 59 49 39 29 19
 98 88 78 68 58 48 38 28 18
 97 87 77 67 57 47 37 27 17
 96 86 76 66 56 46 36 26 16
 95 85 75 65 55 45 35 25 15
 94 84 74 64 54 44 34 24 14
 93 83 73 63 53 43 33 23 13
 92 82 72 62 52 42 32 22 12
 91 81 71 61 51 41 31 21 11


この記事は、Gushwell's C# Programming Pageで公開したものを大幅に加筆・修正したものです。