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

第6話 1次元ポインタを使い第7章第13話のコードを書き換える

1次元ポインタを使って
実行結果


1 2 3 4 5 6 7 8
9 10 11 12 13 14 15 16
17 18 19 20 21 22 23 24
25 26 27 28 29 30 31 32
33 34 35 36 37 38 39 40
41 42 43 44 45 46 47 48
49 50 51 52 53 54 55 56
57 58 59 60 61 62 63 64

64 2 3 4 5 6 7 57
9 55 11 12 13 14 50 16
17 18 46 20 21 43 23 24
25 26 27 37 36 30 31 32
33 34 35 29 28 38 39 40
41 42 22 44 45 19 47 48
49 15 51 52 53 54 10 56
8 58 59 60 61 62 63 1

64 2 59 4 5 62 7 57
9 55 11 52 53 14 50 16
41 18 46 20 21 43 23 48
25 34 27 37 36 30 39 32
33 26 35 29 28 38 31 40
17 42 22 44 45 19 47 24
49 15 51 12 13 54 10 56
8 58 3 60 61 6 63 1

64 2 59 5 4 62 7 57
16 55 11 52 53 14 50 9
41 23 46 20 21 43 18 48
25 34 30 37 36 27 39 32
33 26 38 29 28 35 31 40
17 47 22 44 45 19 42 24
56 15 51 12 13 54 10 49
8 58 3 61 60 6 63 1

を実現するコード例
←2026年3月7日訂正
#include<iostream>//インクルードファイルiostreamの読み込み

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

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

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

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

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

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

const int n = 8;//具体的な数字を使うのではなく、 n を使うと汎用性のあるプログラムになる!

//部長の任務は社長の仕事の一部を引き受け、
//社員(部の社員)に仕事を命令することです。
void 部長(int* a);

void 課長1(int* a);//自然配列担当社員と表示担当社員の統率を担当

void 課長2(int* a);//対角線部分交換社員fと表示担当職員の統率を担当

void 課長3(int* a);//緑部分交換社員gと表示担当職員の統率を担当

void 課長4(int* a);//明るい紫部分交換社員fと表示担当職員の統率を担当

void 自然配列担当社員(int* a);

void 表示担当社員(int* a);

void 交換係社員(int* x, int* y);

//fの任務は各対角線部分を点対称移動させることである
void f(int* a);

//gの任務は緑の部分を上下線対称移動させることである
void g(int* a);

//hの任務は明るい紫部分を左右線対称移動させることである
void h(int* a);

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

  int* a = (int*)calloc(n * n, sizeof(int)); // 1次元ポインタ 

  部長(a);//部長に統率を依頼 

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

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

}

void 部長(int* a) {

  課長1(a);//自然配列担当社員と表示担当社員の統率を担当
 
  課長2(a);//対角線部分交換社員fと表示担当職員の統率を担当

  課長3(a);//緑部分交換社員gと表示担当職員の統率を担当

  課長4(a);//明るい紫部分交換社員fと表示担当職員の統率を担当

}

void 課長1(int* a) {

  自然配列担当社員(a);

  表示担当社員(a);

}

void 課長2(int* a) {

  f(a);//対角線部分交換

  cout << endl;

  表示担当社員(a);

}

void 課長3(int* a) {

  g(a);//緑部分交換

  cout << endl;

  表示担当社員(a);

}

void 課長4(int* a) {

  h(a);//明るい紫部分交換

  cout << endl;

  表示担当社員(a);

}


void 自然配列担当社員(int* a) {

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

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

      *(a + n * i + j) = n * i + j + 1;//自然配列を2次元配列に収納

    }

  }

}

void 表示担当社員(int* a) {

  //自然配列を体裁を整えてコンソール画面に表示する
  for (int i = 0; i < n; i++) {

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

      if (*(a + n * i + j) < n)cout << " ";

      cout << *(a + n * i + j) << " ";

    }

    cout << endl;

  }

}

void 交換係社員(int* x, int* y) {

  int u;//交換のための受け皿

  u = *x;//xから間接参照されるデータがyから間接参照されるデータに上書きされる前にuにxの間接参照されるデータを保存

  *x = *y;//yから間接参照されるデータでxから間接参照されるデータを上書きする

  *y = u;//xから間接参照される元データでyから間接参照されるデータを上書き

}

//fの任務は対角線部分を点対称移動させることである
void f(int* a) {

  //対角線点対称移動
  for (int i = 0; i < n / 2; i++) {

    int x = *(a + n * i + i);

    int y = *(a + n * (n - i - 1) + n - i - 1);

    交換係社員(&x, &y);

    *(a + n * i + i) = x;

    *(a + n * (n - i - 1) + n - i - 1) = y;

  }
  //対角線点対称移動終了

  
  //逆対角線点対称移動
  for (int i = 0; i < n / 2; i++) {

    int x = *(a + n * i + n - i - 1);

    int y = *(a + n * (n - i - 1) + i);

    交換係社員(&x, &y);

    *(a + n * i + n - i - 1) = x;

    *(a + n * (n - i - 1) + i) = y;

  }
  //逆対角線点対称移動終了

}

//gの任務は緑部分を線対称移動させることである
void g(int* a) {

  //緑の部分の第1行から第4行までの上下交換開始
  for (int i = 0; i < n / 2; i++) {

    int x = *(a + n * i + (2 + i) % (n / 2));

    int y = *(a + n * (n - i - 1) + (2 + i) % (n / 2));

    交換係社員(&x, &y);

    *(a + n * i + (2 + i) % (n / 2)) = x;

    *(a + n * (n - i - 1) + (2 + i) % (n / 2)) = y;

  }
  //緑の部分の第1行から第4行までの上下交換終了


  //緑の部分の第5行から第8行までの上下交換開始
  for (int i = 0; i < n / 2; i++) {

    int x = *(a + n * i + n - 1 - (2 + i) % (n / 2));

    int y = *(a + n * (n - i - 1) + n - 1 - (2 + i) % (n / 2));

    交換係社員(&x, &y);

    *(a + n * i + n - 1 - (2 + i) % (n / 2)) = x;

    *(a + n * (n - i - 1) + n - 1 - (2 + i) % (n / 2)) = y;

  }
  //緑の部分の第5行から第8行までの上下交換終了

}

//hの任務は明るい紫部分を線対称移動させることである
void h(int* a) {

  //明るい紫の部分の第1行から第4行までの左右交換開始
  for (int i = 0; i < n / 2; i++) {

    int x = *(a + n * i + n - 1 - (3 + i) % (n / 2));

    int y = *(a + n * i + (3 + i) % (n / 2));

    交換係社員(&x, &y);

    *(a + n * i + n - 1 - (3 + i) % (n / 2)) = x;

    *(a + n * i + (3 + i) % (n / 2)) = y;

  }
  //明るい紫の部分の第1行から第4行までの左右交換終了

  //明るい紫の部分の第5行から第8行までの左右交換開始
  for (int i = 0; i < n / 2; i++) {

    int x = *(a + n * (n - 1 - i) + n - 1 - (3 + i) % (n / 2));

    int y = *(a + n * (n - 1 - i) + (3 + i) % (n / 2));

    交換係社員(&x, &y);

    *(a + n * (n - 1 - i) + n - 1 - (3 + i) % (n / 2)) = x;

    *(a + n * (n - 1 - i) + (3 + i) % (n / 2)) = y;

  }
  //明るい紫の部分の第5行から第8行までの左右交換終了

}

解説

少し難しい課題を出してしまったと反省しています。

お詫びにすべて解説します。

しかも図付きでわかりやすく解説します。

逆対角線は第7章第13話では、

  //逆対角線点対称移動
  for (int i = 0; i < n / 2; i++) {

    int x = a[i][n - i - 1];

    int y = a[n - i - 1][i];

    交換係社員(&x, &y);

    a[i][n - i - 1] = x;

    a[n - i - 1][i] = y;

  }
  //逆対角線点対称移動終了

これを1次元ポインタで表現するには、

第5話で述べたように

    
a[i][j] → *(a + n * i + j)

です。

    int x = a[i][n - i - 1];

今回は i に i を代入して、j に n - i - 1 を代入します。

上の図がなかったために第5話の説明が、

説明が全然理解できなかった方がいると思います。

本当にごめんなさい。

第5話にも今回の図を入れるべきでした。

代入すると

    int x = *(a + n * i + n - i - 1)

となることを上の図を見ながら確認してください。

大事な観点を言います。

n - i - 1 を1つの塊として捉えることです。

塊ごと j に代入したのです。

次の

    int y = a[n - i - 1][i];

についてこそ n - i - 1 を1つの塊として捉えること重要です。



図を見ながら i に 塊 n - i - 1 を代入して、 j に i を代入しましょう。

すると

    int y = *(a + n * (n - i - 1) + i);

となることがわかりますね。

この図を作るやり方は皆さんが考えるよりかなり大変な作業です。

以下の解説は第7話以降とします。



前にも述べましたが、2次元は1次元に還元できることに気が付いたことが、

私の魔方陣研究の跳躍点になったのです。

もう一つは、for文を使うならば、

3次魔方陣なら9次元for文、6次魔方陣なら36次元for文になってしまいますが、

関数の再帰的使用を使えば、

9次元であろうと36次元であろうと簡単に実現できること気が付いたことです。

この2つがなければ、

数独自動生成ソフトも時間割編制ソフトも基本的な発想さえ浮かばなかったことでしょう。



第8章第5話へ 第8章第7話へ

本講義トップへ