マルチスレッド版数独自動生成ソフトC++コードを題材とする超初心者のためのVisual Studio C++講義
第13章 様々な魔方陣の作成および自動生成
第12話 構造解析型とはなにか
私は、長いこと魔方陣の研究をしてきました。
「先生、そんなことを研究して何になるですか」
と生徒からよく言われました。
純粋なパズルで実用性はないのではないですか、
という意味の質問です。
前にも一度触れましたが、
国立大学の電子工学科の教授が6次魔方陣の総数を何十年も研究してきて、
奥さんから「こんなこと研究して何になるの?」という質問に答えられなかったと。
私も、その教授の悩みがよく分かります。
ですが、私の場合は魔方陣の研究が数独自動生成ソフトの開発に直結しました。
その国立大学の教授は世界で初めて6次魔方陣総数カウントに成功し歴史に名を残しました。
朝日新聞はほぼ一面を使ってその業績を称えました。
数独の解き方の第1の考え方であるライン排除法についてはすでに触れています。
そして、2018年の3月に南信州新聞から問い合わせが、来ました。
サイトに載せてある数独自動生成ソフトの使用条件を訊ねてきたのです。
もちろん、その当時から私のサイトにソフト類は自由に使ってよいと、書いてありました。
広報誌から使用したいという問い合わせは、よくありましたが商業誌からの問い合わせには驚きました。
ソフトについては大学の学部における演習程度の価値しか認めていませんでしたから、
驚いたわけです。
新聞社は著作権を厳密に守らないとならないので、サイト上にオールフリーと書いてあっても問い合わせをしてきたのでしょう。
すぐに「もちろん、無料で使用してください」とすぐにメールをしました。
午後8時ぐらいだったのですが、編集部一同が感激しているという返事をいただいてさらに驚いたのです。
感激したのは私の方だったのです。
社会貢献ができると。
2018年の6月に地元の新聞である下野新聞に私の数独自動生成ソフトを使ってのナンプレ連載の企画を提案したのです。
信じられないことに当時の下野新聞のくらし・文化部の部長が乗り気ですぐにその企画は通りました。
読者提案企画が通ったわけです。
幸いなことに、両新聞社ともにナンプレ連載は超人気企画になりました。
南信州新聞は最初は週2回の連載で、下野新聞も週1回の連載でしたが、
南信州新聞はほぼ毎日、下野新聞は週4日週5題になっています。
下野新聞社は、部長と担当者が我が家にやってきて土曜日に掲載していたものを木曜日に移動して、
2題にしたいと言ってきたのです。
木曜日のシニア欄の一つのページを全面パズル欄にして、
ナンプレだけは右上1/4のトップ欄にして、他のパズルは週替わりなのにナンプレだけは固定され毎週掲載されるようになり、
さらに、人気があり週4日5題の出題になりました。
部長も担当者も美人な女性で聡明な方でした。
その2人が年に何回も我が家を訪れるのです。
それで、部長が初めてきたときに過去に取材でお会いしたことがあるというのです。
確かに、下野新聞社から取材を受けたことは5,6回はありました。
取材した方は全員が女性で、取材内容も場面もすべて明確に記憶していましたが、
大変申し訳ないのですが、その部長だけは私の記憶から抜け落ちていました。
ですが、その女性部長のお陰でナンプレの連載という読者提案企画が通っており、
大変感謝の気持ちを持っています。
そして、2024年の12月と2025年の1月に下野新聞社本社で2回講演会が主催されました。
そのときの部長の挨拶は印象に残りました。
会った瞬間に実力のある方であるとわかっていましたが、
挨拶を聞いて大変力のある方であることを確信したのです。
人間の幅の大きい方だと。
ナンプレが人気企画になったことは担当者の力も大きいながら、
女性部長の尽力によるところもあるとも強く感じています。
さて、思い出話は以上にしまして話題を元に戻しましょう。
魔方陣の研究が数独自動生成ソフトに繋がりました。
そして、数独自動生成ソフトの開発が魔方陣の研究に戻ってきて研究を進展させたのです。
それは、これから解説する構造解析型へと結実しました。
解くときのコツが6個あると申し上げました。
そのうちの1個は私が全体構造解析と呼ぶものです。
問題の候補を自動生成した後に数独解法エンジンによって解けるのかどうかを、
調べ解けた時にエクセルに出題するようになっています。
全体構造解析とはたいそうな名前ですが、
この方法は皆さんが最初に使っているものです。
ナンプレのルールからすれば、空欄に入る数字の候補は何かを探ることが全体構造解析です。

理詰めで解くと言っても各空欄の候補数字の探索は重要です。
というのは、ある程度進んでいけば候補が一つの空欄も出てきますし、
私が2on2対応確定や3on3の対応確定にも使えます。
2on2とは、同一行上または同一列上または同一ブロックに同じ2つの空欄に同じ数字が2個のみがリストされるとき、
どちらの欄に入るかまでは確定できませんが、
2つの空欄に2つの数字が入ることが確定します。
1対1対応になっているからです。
このときに重要なのは、
リストされたその2つの数字は同一行または同一列または同一ブロックからは候補から外されることです。
3on3はもっと強力に作用します。
3つの数字が同一行または同一列または同一ブロックからは候補から外されるからです。
仮に4つしかリストされずその3つが外される数字の場合、
この時点で確定です。
ですから、全体構造解析は理詰め解法エンジン(仮定法を使わずに理詰めで解くエンジン)を考える上で、
絶対不可欠なのです。
仮定法で解いてしまう方は6つの内の1個だけで解こうとするからです。
ライン排除・ブロック排除・2on2・3on3などの6つのコツがそろうときに初めて、
理詰めで解けるのです。
空欄候補数字探索のみしか解法を知らないから、
仮定法になってしまうのです。
さて、皆さん難しい課題ですが、下のコードを参考にして全体構造解析=空欄候補数字探索をしてください。
この課題は本講義第2編数独自動生成ソフトの開発に直結する課題です。
#pragma warning(disable: 4996)//第2編のために必要
#include<iostream>//インクルードファイルiostreamの読み込み
#include<conio.h>//while(!_kbhit());を使うためのお呪い
#include<string> //文字列変数を使えるようにするために組み込む
#include <iomanip> //setprecisionを使えるように組み込む
#include <cmath>//powなどを使うときに必要
#include <ctime>//time()(←現時刻発生する関数)を使うために必要
using namespace std;//coutを使うときに必要なお呪い
#include <process.h>//_beginthreadを使うために必要
void 全体構造解析関数();//スレッドを派生させる関数
const int n = 9;//数独の一辺は9です
int s[n][n][n];//各空欄の候補数字を収納する3次元配列
int a[n][n];//問題及び解答を収納する2次元配列
int mx[n][n];//各空欄の候補数字数
int main() {
//すべてを0に初期化
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
a[i][j] = 0;
}
}
//候補数字数を9で初期化
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
mx[i][j] = 9;
}
}
//データ入力
a[0][0] = 2;
a[0][4] = 8;
a[0][8] = 9;
a[1][3] = 7;
a[1][5] = 1;
a[2][1] = 6;
a[2][7] = 1;
a[3][0] = 6;
a[3][4] = 4;
a[3][8] = 5;
a[4][2] = 7;
a[4][3] = 3;
a[4][4] = 2;
a[4][5] = 9;
a[4][6] = 8;
a[5][1] = 1;
a[5][7] = 3;
a[6][0] = 5;
a[6][8] = 7;
a[7][2] = 6;
a[7][6] = 3;
a[8][1] = 2;
a[8][4] = 6;
a[8][7] = 4;
全体構造解析関数();
//各空欄候補数字表
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (a[i][j] == 0) {
for (int k = 0; k < mx[i][j]; k++) {
cout << "s[" << i << "][" << j << "][" << k << "] = " << s[i][j][k];
cout << endl;
}
cout << endl;
}
}
cout << endl;
}
while (!_kbhit());//待機させるための命令
return(0);
}
void 全体構造解析関数() {
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (a[i][j] == 0) {
for (int k = 0; k < n; k++) {
s[i][j][k] = k + 1;//空欄のすべてに1~9までをリストする
}
}
}
}
//ここにコードを書いていきます。ただし、工夫しても4次元for文ないしは5次元for文になります。
}
工夫しても4次元for文ないしは5次元for文と読んだらびっくり仰天ですね。
実は何度も組んできた私でも
//ここにコードを書いていきます。ただし、工夫しても4次元for文ないしは5次元for文になります。
の部分は混乱します。
初心者には無謀な課題になります。
そこで、段階的実現はかっていきます。

まず1行しかない数独を考えます。
この状態で
#include <cmath>//powなどを使うときに必要
#include <ctime>//time()(←現時刻発生する関数)を使うために必要
using namespace std;//coutを使うときに必要なお呪い
#include <process.h>//_beginthreadを使うために必要
void 全体構造解析関数();//スレッドを派生させる関数
const int n = 9;//数独の一辺は9です
int s[n][n][n];//各空欄の候補数字を収納する3次元配列
int a[n][n];//問題及び解答を収納する2次元配列
int mx[n][n];//各空欄の候補数字数
int main() {
//すべてを0に初期化
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
a[i][j] = 0;
}
}
//候補数字数を9で初期化
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
mx[i][j] = 9;
}
}
//データ入力
//ご自分でデータを入れてください。上のコードをコピペしないでください。
全体構造解析関数();
//各空欄候補数字表
//ご自分でコードを書いてください。なるべく上は見ないでコーティングしてください。
while (!_kbhit());//待機させるための命令
return(0);
}
void 全体構造解析関数() {
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (a[i][j] == 0) {
for (int k = 0; k < n; k++) {
s[i][j][k] = k + 1;//空欄のすべてに1~9までをリストする
}
}
}
}
//ここにコードを書いていきます。ただし、工夫しても4次元for文ないしは5次元for文になります。
}
これ
でも
//ここにコードを書いていきます。ただし、工夫しても4次元for文ないしは5次元for文になります。
の部分は3次元for文となります。
1次元目は
黄色が右へ動いていきます。
空欄候補数字数字探索は空欄のみについて行うので黄色から始まります。
空欄のみを動いていくようにするには当然if文を使います。if( a[0][i] == 0 ){
2次元目は

青色です。つまり数字のあるところを動いていきます。
ここでも当然if文を使います。if( a[0][j] > 0 ){
3次元目は
s[0][i][k];のkです。
これだけヒントがあっても混乱します。ですが粘り強く第1行の空欄候補数字数字探索=第1行構造解析に取り組んでください。
第13章第11話へ 第13章13話へ
本講義トップへ