数独ジェネレーターの作成方法と、評価上異なる評価可能なオプションの数について説明します。
執筆時点ではマウスに最も近かったため、すべてをPascalに実装します。 ただし、すべてを他の言語で実装できます。
▍方法1。 アルファベットの置換。
最初に検討する方法は、元の数独の例の一部の数値を他の数値に置き換えることです。 初期バージョンでは、次の表を使用します。
テーブル写真
この方法の本質は、各数字を異なる数字と一致させ、それによって数独全体を全体として変更することです。
これを行うには、1〜9のインデックスを持つ1次元の行列を使用し、インデックスに従って最初からすべてのセルを埋めます。 次に、2つのランダムなセルを取得し、それらの値を互いに変更します。 この操作を奇数回実行すると、マトリックスを埋める初期バージョンを取得できないことに注意してください(証拠は読者に提供されます)。 疑いのある人のために、プログラムの最後に、100万個の数独行列が生成され、元の数独行列との一致が評価されます。
説明図
パスカルコード
タイプrec = record //置換アルファベット
num:整数の配列[1..9]。
コンストラクタCreate();
var i:整数;
始める
for i:= 1から9
num [i]:= i;
終わり;
procedure rand(); //アルファベットの2つの数字のランダムな交換
var i、j、k:整数;
始める
i:=ランダム(1.9);
j:= i;
while(i = j)do j:= random(1,9);
k:= num [i];
num [i]:= num [j];
num [j]:= k;
終わり;
procedure wr(); //画面にアルファベットを表示
var i:整数;
始める
for i:= 1から9
書き込み(num [i] + '');
writeln;
終わり;
終わり;
num:整数の配列[1..9]。
コンストラクタCreate();
var i:整数;
始める
for i:= 1から9
num [i]:= i;
終わり;
procedure rand(); //アルファベットの2つの数字のランダムな交換
var i、j、k:整数;
始める
i:=ランダム(1.9);
j:= i;
while(i = j)do j:= random(1,9);
k:= num [i];
num [i]:= num [j];
num [j]:= k;
終わり;
procedure wr(); //画面にアルファベットを表示
var i:整数;
始める
for i:= 1から9
書き込み(num [i] + '');
writeln;
終わり;
終わり;
カウントすると、9になります! オプション。これは362,880通りの組み合わせです。
second 2番目の方法。 マトリックス順列。
これは、数年前にまだHabréにあった方法です。 詳細はこちらをご覧ください 。
そして、ここに簡単な絞り込みがあります:
図に示すように、トリプルで行/列を交換できます
奇数の順列が元のバージョンとの違いを保証することも注目に値します。
したがって、最初に数独マトリックスで行交換と列交換を実装します。 また、コードを簡素化するために、数字をアルファベット順に置き換える機能を挿入します(前述の方法)
パスカルコード
タイプ数独=レコード//数独テーブル
num:整数の配列[1..9]の配列[1..9]。
constructor Create(); // 1〜9の行を循環的にシフトして、テーブルが数独ルールに一致するようにする
始める
num [1] [1]:= 1; num [1] [2]:= 2; num [1] [3]:= 3; num [1] [4]:= 4; num [1] [5]:= 5; num [1] [6]:= 6; num [1] [7]:= 7; num [1] [8]:= 8; num [1] [9]:= 9;
num [2] [1]:= 4; num [2] [2]:= 5; num [2] [3]:= 6; num [2] [4]:= 7; num [2] [5]:= 8; num [2] [6]:= 9; num [2] [7]:= 1; num [2] [8]:= 2; num [2] [9]:= 3;
num [3] [1]:= 7; num [3] [2]:= 8; num [3] [3]:= 9; num [3] [4]:= 1; num [3] [5]:= 2; num [3] [6]:= 3; num [3] [7]:= 4; num [3] [8]:= 5; num [3] [9]:= 6;
num [4] [1]:= 2; num [4] [2]:= 3; num [4] [3]:= 4; num [4] [4]:= 5; num [4] [5]:= 6; num [4] [6]:= 7; num [4] [7]:= 8; num [4] [8]:= 9; num [4] [9]:= 1;
num [5] [1]:= 5; num [5] [2]:= 6; num [5] [3]:= 7; num [5] [4]:= 8; num [5] [5]:= 9; num [5] [6]:= 1; num [5] [7]:= 2; num [5] [8]:= 3; num [5] [9]:= 4;
num [6] [1]:= 8; num [6] [2]:= 9; num [6] [3]:= 1; num [6] [4]:= 2; num [6] [5]:= 3; num [6] [6]:= 4; num [6] [7]:= 5; num [6] [8]:= 6; num [6] [9]:= 7;
num [7] [1]:= 3; num [7] [2]:= 4; num [7] [3]:= 5; num [7] [4]:= 6; num [7] [5]:= 7; num [7] [6]:= 8; num [7] [7]:= 9; num [7] [8]:= 1; num [7] [9]:= 2;
num [8] [1]:= 6; num [8] [2]:= 7; num [8] [3]:= 8; num [8] [4]:= 9; num [8] [5]:= 1; num [8] [6]:= 2; num [8] [7]:= 3; num [8] [8]:= 4; num [8] [9]:= 5;
num [9] [1]:= 9; num [9] [2]:= 1; num [9] [3]:= 2; num [9] [4]:= 3; num [9] [5]:= 4; num [9] [6]:= 5; num [9] [7]:= 6; num [9] [8]:= 7; num [9] [9]:= 8;
終わり;
procedure change(alf:rec); //アルファベットの置換
var i、j:整数;
始める
for i:= 1から9
j:= 1から9まで
num [i] [j]:= alf.num [(num [i] [j])];
終わり;
procedure swip_row(); //行を交換
var i、j、k、r:整数;
始める
i:=ランダム(1.9);
j:= i;
while(j = i)do j:= random(3 *((i-1 + 3)div 3-1)+1,3 *((i-1 + 3)div 3-1)+3);
rの場合:= 1〜9 do
始める
k:= num [i] [r];
num [i] [r]:= num [j] [r];
num [j] [r]:= k;
終わり;
終わり;
procedure swip_line(); //列を交換します
var i、j、k、r:整数;
始める
i:=ランダム(1.9);
j:= i;
while(j = i)do j:= random(3 *((i-1 + 3)div 3-1)+1,3 *((i-1 + 3)div 3-1)+3);
rの場合:= 1〜9 do
始める
k:= num [r] [i];
num [r] [i]:= num [r] [j];
num [r] [j]:= k;
終わり;
終わり;
procedure wr(); //表示
var i、j:整数;
始める
for i:= 1から9
始める
j:= 1から9まで
write(num [i] [j]、 '');
writeln;
終わり;
writeln;
終わり;
終わり;
num:整数の配列[1..9]の配列[1..9]。
constructor Create(); // 1〜9の行を循環的にシフトして、テーブルが数独ルールに一致するようにする
始める
num [1] [1]:= 1; num [1] [2]:= 2; num [1] [3]:= 3; num [1] [4]:= 4; num [1] [5]:= 5; num [1] [6]:= 6; num [1] [7]:= 7; num [1] [8]:= 8; num [1] [9]:= 9;
num [2] [1]:= 4; num [2] [2]:= 5; num [2] [3]:= 6; num [2] [4]:= 7; num [2] [5]:= 8; num [2] [6]:= 9; num [2] [7]:= 1; num [2] [8]:= 2; num [2] [9]:= 3;
num [3] [1]:= 7; num [3] [2]:= 8; num [3] [3]:= 9; num [3] [4]:= 1; num [3] [5]:= 2; num [3] [6]:= 3; num [3] [7]:= 4; num [3] [8]:= 5; num [3] [9]:= 6;
num [4] [1]:= 2; num [4] [2]:= 3; num [4] [3]:= 4; num [4] [4]:= 5; num [4] [5]:= 6; num [4] [6]:= 7; num [4] [7]:= 8; num [4] [8]:= 9; num [4] [9]:= 1;
num [5] [1]:= 5; num [5] [2]:= 6; num [5] [3]:= 7; num [5] [4]:= 8; num [5] [5]:= 9; num [5] [6]:= 1; num [5] [7]:= 2; num [5] [8]:= 3; num [5] [9]:= 4;
num [6] [1]:= 8; num [6] [2]:= 9; num [6] [3]:= 1; num [6] [4]:= 2; num [6] [5]:= 3; num [6] [6]:= 4; num [6] [7]:= 5; num [6] [8]:= 6; num [6] [9]:= 7;
num [7] [1]:= 3; num [7] [2]:= 4; num [7] [3]:= 5; num [7] [4]:= 6; num [7] [5]:= 7; num [7] [6]:= 8; num [7] [7]:= 9; num [7] [8]:= 1; num [7] [9]:= 2;
num [8] [1]:= 6; num [8] [2]:= 7; num [8] [3]:= 8; num [8] [4]:= 9; num [8] [5]:= 1; num [8] [6]:= 2; num [8] [7]:= 3; num [8] [8]:= 4; num [8] [9]:= 5;
num [9] [1]:= 9; num [9] [2]:= 1; num [9] [3]:= 2; num [9] [4]:= 3; num [9] [5]:= 4; num [9] [6]:= 5; num [9] [7]:= 6; num [9] [8]:= 7; num [9] [9]:= 8;
終わり;
procedure change(alf:rec); //アルファベットの置換
var i、j:整数;
始める
for i:= 1から9
j:= 1から9まで
num [i] [j]:= alf.num [(num [i] [j])];
終わり;
procedure swip_row(); //行を交換
var i、j、k、r:整数;
始める
i:=ランダム(1.9);
j:= i;
while(j = i)do j:= random(3 *((i-1 + 3)div 3-1)+1,3 *((i-1 + 3)div 3-1)+3);
rの場合:= 1〜9 do
始める
k:= num [i] [r];
num [i] [r]:= num [j] [r];
num [j] [r]:= k;
終わり;
終わり;
procedure swip_line(); //列を交換します
var i、j、k、r:整数;
始める
i:=ランダム(1.9);
j:= i;
while(j = i)do j:= random(3 *((i-1 + 3)div 3-1)+1,3 *((i-1 + 3)div 3-1)+3);
rの場合:= 1〜9 do
始める
k:= num [r] [i];
num [r] [i]:= num [r] [j];
num [r] [j]:= k;
終わり;
終わり;
procedure wr(); //表示
var i、j:整数;
始める
for i:= 1から9
始める
j:= 1から9まで
write(num [i] [j]、 '');
writeln;
終わり;
writeln;
終わり;
終わり;
計算した結果、列/行のトリプルを1つだけ置換する場合、各トリプルでオプションの数を6倍増やすことができます(列ごとに6 * 6 * 6、行ごとに同じ数、合計46 656オプション)。
また、文字ごとに2つの数独を比較し、一致する/一致しない場合にtrue / falseを返す関数を個別に作成します。
パスカルコード
function compl(s1、s2:sudoku):boolean; // 2つの数独テーブルを比較
var i、j:整数; フラグ:ブール値;
始める
フラグ:= true;
for i:= 1から9
j:= 1から9まで
if(s1.num [i] [j] <> s2.num [i] [j])then flag:= false;
compl:=フラグ;
終わり;
var
a:rec;
nw、だった:数独。
i:整数;
t、n:整数;
cor:整数;
始める
//アルファベットと元のバージョンを置き換えた後の数独一致の確率の計算
cor:= 0;
tの場合:= 1から1,000,000 do
始める
nw:=新しい数独();
だった:=新しい数独();
a:= new rec();
//総論。 奇数の順列では、最初の状況は不可能です
// 6つの順列では、すべてのトリプルの列/行を混在させることができます
for i:= 1から7 do nw.swip_row(); // 6 * 3オプション
for i:= 1から7 do nw.swip_line(); // 6 * 3オプション
// 8ペアの順列の場合、9桁の任意の組み合わせを取得できます。
for i:= 1から9 do a.rand(); // 9! オプション
//オプションの推定数117 573 120
nw.change(a);
if(compl(nw、was))then cor:= cor + 1;
終わり;
writeln((cor / 1000000)* 100、 '%');
nw.wr();
終わり。
var i、j:整数; フラグ:ブール値;
始める
フラグ:= true;
for i:= 1から9
j:= 1から9まで
if(s1.num [i] [j] <> s2.num [i] [j])then flag:= false;
compl:=フラグ;
終わり;
var
a:rec;
nw、だった:数独。
i:整数;
t、n:整数;
cor:整数;
始める
//アルファベットと元のバージョンを置き換えた後の数独一致の確率の計算
cor:= 0;
tの場合:= 1から1,000,000 do
始める
nw:=新しい数独();
だった:=新しい数独();
a:= new rec();
//総論。 奇数の順列では、最初の状況は不可能です
// 6つの順列では、すべてのトリプルの列/行を混在させることができます
for i:= 1から7 do nw.swip_row(); // 6 * 3オプション
for i:= 1から7 do nw.swip_line(); // 6 * 3オプション
// 8ペアの順列の場合、9桁の任意の組み合わせを取得できます。
for i:= 1から9 do a.rand(); // 9! オプション
//オプションの推定数117 573 120
nw.change(a);
if(compl(nw、was))then cor:= cor + 1;
終わり;
writeln((cor / 1000000)* 100、 '%');
nw.wr();
終わり。
calculations計算の終わりに
結果の2つの数値を乗算できる理由を説明する価値があります。 文字列を置換するとき、数字を置換するときと同じオプションは得られません。そうでない場合、異なるアルファベットの置換が得られます(文字列の最後の置換を考えると、矛盾を見つけることができます)。
したがって、このアルゴリズムを使用すると、最大362,880 * 46,656または16,930,529,280のバリアントを作成できます。
この記事では、数独テーブルを使用したすべての変換(列/行のトリプルの置き換え、転置など)については説明していません。これは、数独のバリアントの数がさらに多いことを証明しています。