マルチスレッド版数独自動生成ソフトC++コードを題材とする超初心者のためのVisual Studio C++講義
第5章 配列

第12話 対角線合計を計算して表示する



1 2 3 4
5 6 7 8
9 10 11 12
13 14 15 16

34
1 15 14 4 34
12 6 7 9 34
8 10 11 5 34
13 3 2 16 34
34

34 34 34 34


列合計計算と表示を実現するコード例
#include<iostream>//インクルードファイルiostreamの読み込み

#include<conio.h>//while(!_kbhit());を使うためのお呪い

#include<string> //文字列変数を使えるようにするために組み込む

#include <iomanip> //setprecisionを使えるように組み込む

#include <cmath>//powなどを使うときに必要

#include <ctime>//time()(←現時刻発生する関数)を使うために必要

using namespace std;//coutを使うときに必要なお呪い

void 2次元for文();//横と縦の2方向を持つ2次元for文体験

int main() {//私は社長だ。

  2次元for文();

  while (!_kbhit());//待機させるための命令

  return(0);//int main()終わるためのお呪い

}

void 2次元for文() {
  //横と縦の2方向を持つ2次元for文体験

  int a[4][4];//4行4列の行列を表すことのできる2次元配列の宣言

  for (int i = 0; i < 4; i++) {

    for (int j = 0; j < 4; j++) {

      a[i][j] = 4 * i + j + 1;
    
    }

  }

  for (int i = 0; i < 4; i++) {

    for (int j = 0; j < 4; j++) {

      if (a[i][j] < 10)cout << " ";

      cout << a[i][j] << " ";

    }

    cout << endl;

  }

  //以下
  //a[0][1]とa[3][2]の交換
  //a[0][2]とa[3][1]の交換
  //a[1][0]とa[2][3]の交換
  //a[2][0]とa[1][3]の交換

  int 受け皿;//a[0][1]などのデータを一時的に預かる変数  

  //a[0][1]とa[3][2]の交換
  受け皿 = a[0][1];//a[0][1]のデータを上書きする前に 受け皿 に一時的にデータを預ける

  a[0][1] = a[3][2];
  //a[0][1]のデータがa[3][2]のデータに上書きされてしまうが、値は 受け皿 に残っている

  a[3][2] = 受け皿;
  //a[0][1]の元のデータをa[3][2]に入力
  //a[0][1]とa[3][2]の交換終了

  //a[0][2]とa[3][1]の交換
  受け皿 = a[0][2];//a[0][2]のデータを上書きする前に 受け皿 に一時的にデータを預ける

  a[0][2] = a[3][1];
  //a[0][2]のデータがa[3][1]のデータに上書きされてしまうが、値は 受け皿 に残っている

  a[3][1] = 受け皿;
  //a[0][2]の元のデータをa[3][1]に入力
  //a[0][2]とa[3][1]の交換終了

  //a[1][0]とa[2][3]の交換
  受け皿 = a[1][0];//a[0][1]のデータを上書きする前に 受け皿 に一時的にデータを預ける

  a[1][0] = a[2][3];
  //a[1][0]のデータがa[2][3]のデータに上書きされてしまうが、値は 受け皿 に残っている

  a[2][3] = 受け皿;
  //a[1][0]の元のデータをa[3][2]に入力
  //a[0][1]とa[2][3]の交換終了

  //a[2][0]とa[1][3]の交換
  受け皿 = a[2][0];//a[2][0]のデータを上書きする前に 受け皿 に一時的にデータを預ける

  a[2][0] = a[1][3];
  //a[2][0]のデータがa[1][3]のデータに上書きされてしまうが、値は 受け皿 に残っている

  a[1][3] = 受け皿;
  //a[2][0]の元のデータをa[1][3]に入力
  //a[2][0]とa[1][3]の交換終了

  int 行合計[4] = { 0,0,0,0 };
  //各行の合計を出す配列を定義して、すべてを0に初期化

  //各行の合計を計算する
  for (int i = 0; i < 4; i++) {

    for(int j = 0; j < 4; j++) {

      行合計[i] = 行合計[i] + a[i][j];

    }

  }

  int 列合計[4] = { 0,0,0,0 };
  //各行の合計を出す配列を定義して、すべてを0に初期化

  //各行の合計を計算する
  for (int i = 0; i < 4; i++) {

    for (int j = 0; j < 4; j++) {

      列合計[i] = 列合計[i] + a[j][i];

    }

  }

  cout << endl;

  
//対角線合計計算と表示
  int 対角線合計[2] = { 0,0 };//1次元配列を対角線合計定義してすべての値を0に初期化

  //対角線合計の計算
  for (int i = 0; i < 4; i++) {

    対角線合計[0] = 対角線合計[0] + a[i][i];

  }

  //対角線合計の表示
  for (int i = 0; i < 4; i++) {

    cout << " ";

  }

  cout << " " << 対角線合計[0];

  //逆対角線合計の計算
  for (int i = 0; i < 4; i++) {

    対角線合計[1] = 対角線合計[1] + a[i][3 - i];

  }

  cout << endl;


  for (int i = 0; i < 4; i++) {

    for (int j = 0; j < 4; j++) {

      if (a[i][j] < 10)cout << " ";

      cout << a[i][j] << " ";

    }

    cout << " " << 行合計[i];

    cout << endl;

  }

  
//逆対角線合計の表示

  for (int i = 0; i < 4; i++) {

    cout << " ";

  }

  cout << " " << 対角線合計[1] << endl;


  //各列の合計を表示
  cout << endl;

  for (int i = 0; i < 4; i++) {

    cout << 列合計[i] << " ";

  }

}

これで行・列・対角線のすべての合計が計算できるようになりました。

  //対角線合計の計算
  for (int i = 0; i < 4; i++) {

    対角線合計[0] = 対角線合計[0] + a[i][i];

  }

  //逆対角線合計の計算
  for (int i = 0; i < 4; i++) {

    対角線合計[1] = 対角線合計[1] + a[i][3 - i];

  }
は初心者の方には難しいと思いますので、

解説します。

数学では右上から左下の対角線を「副対角線」と呼びますが、

ここでは初心者の方にも分かりやすいように「逆対角線」と呼んでいます。

補足(専門用語)

※左上から右下に向かう線が a[i][i](主対角線)
※右上から左下に向かう線が a[i][3-i](逆対角線)


※この講義では初心者の方にも分かりやすいように「逆対角線」という言葉を使います。

0 1 2 3
0 1 15 14 4
1 2 6 7 9
2 8 10 11 5
3 13 3 2 16

まず、右下がりの対角線の合計は

1 + 6 + 11 + 16

を計算するわけですが、

座標に注目してください。

00)(11)(22)(33

確かに(
)であることがわかります。

C++では座標をa[
i][i]で表しますから

   対角線合計[0] = 対角線合計[0] + a[i][i];

は正しいことがわかります。

0 1 2 3
0 1 15 14 4
1 2 6 7 9
2 8 10 11 5
3 13 3 2 16

次に左下がりの対角線は

4 + 7 + 10 + 13

の計算になりますが、

03)(12)(21)(30

確かに(3 - i)になります。

C++では座標をa[
i][3 - i]で表しますから

   対角線合計[0] = 対角線合計[0] + a[i][3 - i];

も正しいことがわかります。

対角線合計[0] は主対角線

対角線合計[1] は逆対角線

です。

    対角線合計[0] = 対角線合計[0] + a[i][i];

    対角線合計[1] = 対角線合計[1] + a[i][3 - i];

の使い方を忘れる前の反復をやってぜひ覚えてください。




これで第5章は終わりにします。

第6章では6次魔方陣と8次魔方陣の作成を扱います。

尚、すでにお気づきの方もいらっしゃると思いますが、

私は作成と自動生成という言葉を分けて使っています。

魔方陣の作成とは作り方コンピュータに人間が教えて、

この手順に従って魔方陣を作る事を指します。

作成というやり方では200次魔方陣もあっという間にコンピュータは作り出します。

シングルスレッドでも0.01秒もかからないでしょう。

それに、対して魔方陣の自動生成にしても数独の自動生成にしても、

コンピュータが自ら考えて魔方陣や数独を生成します。

前に、本講義の第9章か第10章辺りで数独自動生成を扱う予定だと、

予告しましたが、数独自動生成にはいろいろな要素があって、

1章や2章に収まるものではありません。

そこで、数独自動生成ソフト開発は本講義第2弾として独立させ、

10章構成程度で講義を展開予定です。

私の開発した数独自動生成ソフトは、

南信州新聞と下野新聞に無料提供して、

数独自動生成ソフトの生成したナンプレが、

両新聞社ともに、週5題以上掲載されています。

数独という名称はニコリが商標権を持っており、

他の出版社は使えないのでナンプレとされており、

両新聞社ともにそれに従っています。

特に、私の地元の地方紙である下野新聞においては、

毎週木曜日が全面パズル掲載日になっていて、

数独は右上(すなわち紙面1/4)に2題掲載となり、

他のパズルは週変わりであるのに対して、

ナンプレは紙面トップに固定されていて特別扱いとなっています。

同じく推薦書自動生成ソフト開発とか成績一覧表作成等も第3弾として独立させ、

第3弾は実務的応用偏とする予定です。

第2弾で開発予定の数独自動生成ソフトは、

マルチスレッドプログラミングであり、

各スレッドが異世界を構成して、

異世界は他の異世界の干渉を受けず、

また、他の異世界に干渉せずに

独立して展開され各スレッドが数独自動生成に挑みます。

マルチスレッド版数独自動生成ソフトは、

マルチバース(多元宇宙)的プログラミングになっています。

そして、各異世界が次章で扱う予定の関数の再帰的方法という手法を使っており、

少なく見積もっても324次元展開というとんでもない次元を持っています。

そして、基本スレッド数は36としてありますから

324×36 = 11,664次元展開という信じられない次元展開をしています。

こんな次元をfor文で実現することは不可能ですが、

マルチスレッド版の各スレッドにおける再帰的使用という方法なら可能になるのです。

マルチスレッドとは分身の術です。

忍者ものの分身の術は見掛け倒しに過ぎませんが、

充分なメモリーを積んである場合には本当に36分身を果たして、

マルチバース(多元宇宙)的世界を構成して、

シングルスレッドの数千倍の速さで数独を生成して、

マルチスレッドのすごさを見せつけることになります。

なにしろ、VBA(Excelに付随しているプログラミング統合開発環境)

で生成させていた頃はヒント数20を生成するのに18時間もかかっていたのに、

C++でマルチスレッド版を開発すると、

数秒で生成したときは、

「えっ、ウソ、本当かよ」と心臓が飛び出るほど驚いたものです。

どうですか。胸躍りませんか。ワクワクが止まらないのではいでしょうか。

マルチスレッド版数独自動生成ソフトの開発に成功したならば、

プログラミングは面白くて面白くてやめられなくなります!

尚、すでに皆さんは気付いているでしょうが、

プログラミングとは答が無数にあり、

大変創造的な行為です。

工夫した数だけ答えがあるのです。

最初は、私に教わったソフトをなぞるだけですが、

皆さんは私を超えるマルチスレッド版数独自動生成ソフトを開発することになるでしょう。


第5章第11話へ 第6章第1話へ

本講義トップへ