【Java】ランダムな4×4の魔方陣を出力する

【Java】ランダムな4×4の魔方陣を出力する

Javaを使って4行4列の16マスの魔方陣(1~16の数字を1つずつ使ったもの)をランダムで生成するコードを作成してみました。

魔方陣の概要

魔方陣とは、以下のような縦・横・斜めの全ての列の合計が同じになる数表のことをいいます。今回は1~16の数字を一つずつ使ったものを作っていきます。

下に魔方陣の一例を示します。

$$ \large\begin{array}{|c|c|c|c|} \hline 1 & 14 & 15 & 4 \\ \hline 8 & 11 & 10 & 5 \\ \hline 12 & 7 & 6 & 9 \\ \hline 13 & 2 & 3 & 16 \\ \hline \end{array} $$

画像の魔方陣では、縦・横・斜めのどの列も合計が34になっていることが分かります。この性質を利用して、段階を踏みながらこのような表を出力してみます。

魔方陣の性質を利用したコード

4次魔方陣を求めるプログラム | 大同大学」にあるアルゴリズムを参考にして、Javaで実装してみます。

MakeMagicSquare44.java
 1import java.util.ArrayList;
 2import java.util.Collections;
 3import java.util.List;
 4
 5public class MakeMagicSquare44 {
 6
 7    private static final int MAGIC_SUM = 34;
 8    private static final int SIZE = 16;
 9    private static final int DIM = 4;
10
11    /** 1から16までを格納するArrayList */
12    private List<Integer> list = new ArrayList<>();
13
14    /** 完成した魔方陣の数表を格納する配列 */
15    private int[] array = new int[SIZE];
16
17    public static void main(String[] args) {
18        MakeMagicSquare44 magicSquare = new MakeMagicSquare44();
19        magicSquare.execute();
20        magicSquare.showArray();
21    }
22
23    /** 魔方陣の作成 */
24    private void execute() {
25        while (true) {
26            generateList();
27            initializeCorners();
28            if (attemptFillMagicSquare()) {
29                break;
30            }
31        }
32    }
33
34    /** listに1~16を格納しシャッフルする */
35    private void generateList() {
36        list.clear();
37        for (int i = 1; i <= SIZE; i++) {
38            list.add(i);
39        }
40        Collections.shuffle(list);
41    }
42
43    /** 初期のコーナー要素を設定 */
44    private void initializeCorners() {
45        array[0] = list.remove(0);
46        array[3] = list.remove(0);
47    }
48
49    /** 魔方陣を埋める試みを行う */
50    private boolean attemptFillMagicSquare() {
51        return checkCombinationAndFill(0, 3, 12, 15) &&
52               checkCombinationAndFill(0, 15, 5, 10) &&
53               checkCombinationAndFill(0, 3, 1, 2) &&
54               checkCombinationAndFill(3, 12, 6, 9) &&
55               fillLastOne(1, 5, 9, 13) &&
56               fillLastOne(2, 6, 10, 14) &&
57               checkCombinationAndFill(5, 6, 4, 7) &&
58               fillLastOne(0, 4, 12, 8) &&
59               fillLastOne(3, 7, 15, 11);
60    }
61
62    /** 2つの数字が埋まっている列に対して、残りの2つの数字をlistから決定する */
63    private boolean checkCombinationAndFill(int index1, int index2, int index3, int index4) {
64        for (int i = 0; i < list.size() - 1; i++) {
65            for (int j = i + 1; j < list.size(); j++) {
66                if (array[index1] + array[index2] + list.get(i) + list.get(j) == MAGIC_SUM) {
67                    array[index3] = list.remove(i);
68                    array[index4] = list.remove(j - 1);
69                    return true;
70                }
71            }
72        }
73        return false;
74    }
75
76    /** 3つの数字が埋まっている列に対して、残りの1つの数字をlistから決定する */
77    private boolean fillLastOne(int index1, int index2, int index3, int index4) {
78        for (int i = 0; i < list.size(); i++) {
79            if (array[index1] + array[index2] + array[index3] + list.get(i) == MAGIC_SUM) {
80                array[index4] = list.remove(i);
81                return true;
82            }
83        }
84        return false;
85    }
86
87    /** 配列を数表形式で出力する */
88    private void showArray() {
89        for (int i = 0; i < SIZE; i++) {
90            System.out.printf("%3d", array[i]);
91            if ((i + 1) % DIM == 0) {
92                System.out.println();
93            }
94        }
95    }
96}

実行結果の一例が以下になります。

出力結果(一例)
1  8 13 11  2
2  5 10 16  3
3 12  7  1 14
4  9  4  6 15

ランダム出力なので実行毎に出力結果が変わります。


魔方陣の性質を利用して順に数字を求めていくことで、比較的簡単に生成することができました。以上で記事を終わりにします。