魔法陣 magic square

プログラミングを学ぶには,実践あるのみ
というのが自論です
これまで何冊かのプログラミング言語に関する本を読んできましたが,本を読むことによってプログラミングを習得できたという経験は一度もありません
必要に迫られて,無理でも何でもアプリを作らなければならない,となったときに初めてプログラミングの力が身についたように思います
その経験を基に「簡単なものからとにかく作ってみる」を実践します

仮にも数学の教員をやってきました身,それっぽいものから始めよう・・・と思いを巡らせたときに脳裏に浮かんだのが「魔法陣」でした
3以上の任意の整数を入力すると,その次数の魔法陣を表示するアプリ
うん,いいじゃないか・・・ということで,3,5,7,・・・という奇数次数の魔法陣を表示します

えっ? なぜ奇数次数か? ですって?
それは・・・簡単だからですよ vv 偶数次数の魔法陣は難しいのです

次数3の魔法陣の例を示します

\(4\) \(9\) \(2\)
\(3\) \(5\) \(7\)
\(8\) \(1\) \(6\)

数学では,数や式が縦横に並ぶとき,横の並びを行といい縦の並びを列といいます
どの行の数を合計しても,どの列の数を合計しても \(15\) です
加えて,どちらの対角線上の数字の合計もまた \(15\) になっています

このように,\(1\) から \(n \times n\) までの整数を,\(n\)\(n\) 列に並べたとき,すべての行の数の和,すべての列の数の和,二つの対角に並ぶ数の和が等しくなったものを魔法陣と呼びます

\(\mbox{algorithm}\)\(\mbox{coding}\)

プログラミングの作業は,幾つかの工程に分けられますが,大きく分けるとこの二つではないかと思います
ある問題を解決するための作業手順をアルゴリズムといい,アルゴリズムが明確になっていないと,プログラム(アプリ)を作ることはできません
それに対して,コンピュータがアルゴリズムを処理できるように,言語の構文に従って命令を記述することをコーディングといいます
今回は,コーディングのできることを目指しています
ですから,奇数次数の魔法陣なのです

まず,奇数次数の魔法陣の作り方の一つを示します
これは,私が中学生のときに友人から教わったものです
何故その方法でできるかは,ここでの趣旨ではありませんので,説明しません
興味のある方は考えてみてください・・・難しくはありません

\(3 \times 3\) の魔法陣の作り方

\( \) \( \) \( \)
\( \) \( \) \( \)
\( \) \(1\) \( \)

第3行第2列のマス,つまり,最下行の真ん中のマスに \(1\) を書いて,ここから右下のマスに数を1ずつ増やしながら順に書いていきます
と言っても,右下に進むと枠からはみ出してしまします
この枠は,上下と左右がそれぞれつながっていると考えてください

\( \) \( \) \(2\)
\( \) \( \) \( \)
\( \) \(1\) \( \)

\(1\) の右下は第4行第3列のマスになり,枠の上下がつながっていると考えれば第4行は第1行になるので,\(2\) の入るマスは第1行第3列になります
ここに \(2\) を書き入れて,更に右下に進みましょう
第2行第4列のマスになり,上と同様に第4列は第1列であると考えれば

\( \) \( \) \(2\)
\(3\) \( \) \( \)
\( \) \(1\) \( \)

第2行第1列に \(3\) が入ります
ここまで宜しいでしょうか?
ところが,ここで問題が生じます
\(3\) の右下のマスには,すでに \(1\) が入っているのです
さぁ,どうしましょう?

\(4\) \( \) \(2\)
\(3\) \(5\) \( \)
\( \) \(1\) \(6\)

どうしましょう,と言われても,知らないものは即座には分かりませんよね
実は,同じ列で一つ上の行のマスに上がります
ここに \(4\) を書き入れたら,更に右下に進んで \(5\)\(6\) を書き入れます
第3行第3列の右下は第1行第1列になり,ここには,先ほど \(4\) が入りました

\(4\) \(9\) \(2\)
\(3\) \(5\) \(7\)
\(8\) \(1\) \(6\)

したがって,\(6\) の上のマスに \(7\) が入って,以下同様に \(8\)\(9\) の位置も決まります
これで \(3 \times 3\) の魔法陣の完成です

1. 最下行真ん中のマスに \(1\) を記入する

2. 枠は上下と左右でそれぞれつながっていると考えて,右下にマスに次の数を記入する

3. すでに右下のマスに数字が埋まっていたら,一つ上のマスに上がる

このアルゴリズムで,奇数次数の魔法陣を作ることができます
因みに,魔法陣は一通りではありませんが,その議論を始めると数学的に迷路に入り込んでしまいますので,もし機会があればということにいたしましょう

\(\mbox{coding}\)

まずは,上のアルゴリズムを処理できるよう書いたプログラムをご覧ください

#include <stdio.h>

int main(void) {

int dim;

int a[19][19] = {};

printf("魔法陣の次数は?:");

scanf("%d", &dim);

int i = dim - 1;

int j = (dim - 1) / 2;

for (int num = 1; num <= dim * dim; num++) {

a[i][j] = num;

if (a[(i + 1) % dim][(j + 1) % dim] == 0) {

i = (i + 1) % dim;

j = (j + 1) % dim;

} else {

i = i - 1;

}

}

for (i = 0; i < dim; i++) {

for (j = 0; j < dim; j++) {

printf("%3d ", a[i][j]);

}

printf("\n");

}

return 0;

}

それでは一つずつ確認していきましょう

変数

まずは,変数からです
最初に変数を定めても,私のように見通しの甘い人間は処理の途中で変数を追加することになるのですが,プログラムの流れをイメージすることにもつながるので,やっておくに越したことはありません

int dim ; 魔法陣の次数を格納します
int a[19][19] ;  魔法陣の各セルに入る数を格納します
プログラム的には,魔法陣の次数が決まったあと動的に配列の要素数を定めた方が美しいのですが,今回は楽をしました
また,a[19][19] = {} ; により,配列全体を \(0\) で初期化しています
int i ; 魔法陣のセルの行参照に使います
ただし,実際の行より \(1\) 小さい整数が入ります
int j ; 魔法陣のセルの列参照に使います
ただし,実際の行より \(1\) 小さい整数が入ります
int num ; 魔法陣のセルに入れる数を格納し,\(1\) から \(\mbox{dim}^2\) まで変化します

入出力処理

つづいて,処理の流れです

まず,コンピュータとやり取りをするためには,こちらの意志をコンピュータに伝え,コンピュータの処理結果を受けとる必要があります
そのために,ここでは scanf( ) と printf( ) という二つの関数を利用しています
scanf( ) はキーボードからの入力を受け取り,printf( ) は画面への出力(表示)を行うものであり,プログラムの中で次のとおり使われています

printf("魔法陣の次数は?:") ;

正直これは,あってもなくても宜しいものですが,魔法陣の次数の入力を求めますので,その入力を促すメッセージを表示しています
printf( ) 関数は,引数として " " で囲った文字列をとると,その文字列をプロンプト画面に表示します

scanf("%d", &dim) ;

魔法陣の次数を受け取って,変数dimに格納します
scanf( ) 関数は,第1引数で指定されたデータ形式でキーボードの入力を受け取り,第2引数で指定された変数に格納します
このとき,第2引数には変数名ではなく変数のアドレスを指定します
また,第1引数の "%d" は十進数を表します
つまり,この場合,キーボードから入力された十進数を変数dimに格納せよ,という命令になります

printf("%3d ", a[i][j]) ;

こちらが,printf( ) 関数のより一般的な使い方です
引数を二つとっており,第1引数が出力形式,第2引数が表示データです
第1引数の "%3d " は,scanf( ) の場合の "%d" と同様に十進数で表示することを表していて,さらに,d の前の 3 により3桁で表示することを指定しています
もう一つ,d の後ろに半角のスペースがあることにも注意しておきましょう
結局,この命令は,配列変数 a[i][j] に格納されているデータの値を3桁の右詰め十進数により表示し,その後ろに半角のスペースを出力することになります
この文については,配列変数 a[i][j] に何が入るかが分からないとイメージしにくいので,下でもう一度説明します

さて,ここで気になるのが scanf( ) 関数の第2引数です
dim ではなく,アドレス &dim であるところです
これがC言語特有のポインタという概念でして,ポインタはC言語習得のハードルを高くしている要因の一つです
したがって,いきなりポインタに深入りするのは abandon の危険性があります
ここは,「scanf( ) という関数を使うときには,入力データを格納する変数の前に & をつけるものだ」と覚えておくくらいに止めましょう

さて,変数 dim すなわち魔法陣の次数の入力(決定)を受けて,

int i = dim - 1;
int j = (dim - 1) / 2;

としています
これは,\(1\) の入る位置を決めるものです
変数の説明で書いたように,実際の行数,列数より \(1\) 小さい値を i と j に入れるので,最下行である第 dim 行を表す i は i = dim - 1; なのです
列は,dim 列ある列の真ん中ですから j = (dim - 1) / 2; となります
この変数 a[i][j] に \(1\) が入ります

繰り返し処理

同じ処理を繰り返し行う,正にコンピュータが得意とするところです
その代表的な構文が for 文です

for 文

for (変数 = 初期値; 繰り返す条件; 変数の増減) {

繰り返しを行う処理

}

このコーディング中の for (int num = 1; num <= dim * dim; num++) { } であれば,

変数 num を 1 から始めて dim * dim まで,num の値を 1 ずつ増やしながら { } 内の処理を繰り返すという意味になります

num の値を 1 ずつ減らして繰り返したいときには,インクリメント n++ を デクリメント n-- に換えれば宜しく,num を 2 ずつ増やしたいときには,num += 2 のように換えてやれば宜しいのです
dim に 3 が入った場合は,まず最下行の真ん中に 1 を入れて,上で示したアルゴリズムにより,9 まで繰り返して配列 a[i][j] を埋めることなります

繰り返し処理は,数をいれるセルを右下に右下に移動しますから,そのように i と j の値を決めてやります
それが,i = (i + 1) % dimj = (j + 1) % dim です
% は剰余を求める演算子です
例えば,dim = 3 ならば,i = 3(つまり 4行目)になったり,j = 3(つまり,4列目)になったりしたときには,i = 0 または j = 0 に戻す必要がありますから,そのためのものです

ところが,右下のセルにすでに数が入っている場合は,上のセルに移らなければなりません
ここで,条件分岐が起こります

条件分岐処理

if 文

  1. if (条件式) {条件が真の場合の処理}
  2. if (条件式) {条件が真の場合の処理} else {条件が偽の場合の処理}

if (a[(i + 1) % dim][(j + 1) % dim] == 0) {

i = (i + 1) % dim;

j = (j + 1) % dim;

} else {

i = i - 1;

}

したがって,この部分は「a[i][j] の右下のセルが 0 であるかどうかを見て,0 すなわち数が入っていなければ次の数が入るセルを右下にし,そうでなければ列は変えずに一つ上の行のセルにする」という処理になります

魔法陣の出力

以上で,奇数次数の魔法陣を作るアルゴリズムがコーディングされ,変数 a[i][j] には,第 i + 1 行 第 j + 1 列のセルに入る数が収まりました
最後に,a[i][j] に格納された数を表示すれば,魔法陣の完成です
dim = 3 のときには,次のような表示になります

a[0][0] a[0][1] a[0][2]
a[1][0] a[1][1] a[1][2]
a[2][0] a[2][1] a[2][2]

for (i = 0; i < dim; i++) {

for (j = 0; j < dim; j++) {

printf("%3d ", a[i][j]);

}

printf("\n");

}

魔法陣になるためには,数が縦横に並ばなければなりません
そのために,printf( ) の第1引数で "%3d " として数を3桁の右寄せで表示するようにしました
また,C言語の printf( ) は,指示をしなければ出力後に改行をしません・・・このところは,プログラミング言語によって異なります
そこで,一行を表示したところで,printf("\n"); を実行して改行をします
¥n は改行を表します

本当は,「これで完成~!!」と喜んではいけません
何故かと言えば,利用者が魔法陣の次数に3以上の奇数を入力してくれるとは限らないからです
このアルゴリズムで偶数次数の魔法陣を作ることは出来ませんし,エラーが出ます
小数を入力する人がいるかもしれません
そのための例外処理をする必要があります
ただ,ここでは,そのようなうるさいことを言わずにおきましょう

実行画面

実のところ,今後,私が C/C++ でアプリを作ることはなかろうと思います
自身の興味は,ブラウザ上で動く所謂アプレット,Webページ上のDOMの操作,そしてWebアプリケーションです
プラットフォームに依存することなく利用できるということと,webページに表現力をもたせられることからです
しかしながら,大昔にプログラミングをかじった人間としては,「基本はC言語」が染みついてしまっているようで,まずC言語と考えてしまいます
C言語を思い出せば,他の言語学習の参考になりますし,arduino で遊ぶこともあるので,まぁ宜しいでしょう

因みに,C言語の学びなおしに当たり,PCにインストールした環境は gcc です
いかにも年寄りです vv
少しばかり,言い訳をしますと・・・Windowsのノートの他,アマゾンのセールにて 19,800円 で購入した C223NA を使っております
これは,なかなかにヨキです
中身は debian系 なのでしょうか? Crostini は ubuntu とほぼ同じように使え,sudo apt でアプリケーションの管理が簡単に行えます
プログラミング学習には最適,ほとんどメインマシン化しており,Crostini を使うのであれば可能な限りターミナルでとなってしまうのです

Last modified: Thursday, 24 March 2022, 9:06 AM