最小限の手がかりの数独アンソルバーを構築する


16

この質問を述べようとする私の試みですが、より客観的な解決基準があります。

あなたのタスクはS、選択したフォーマットで解決された数独グリッドを取得Sし、独自のソリューションとして可能な限り少ない手がかりで問題グリッドを生成しようとするプログラムまたは関数を構築することです。(Sソリューションが一意であることが証明されている限り、ブルートフォースを含め、どのメソッドが一意のソリューションであるかは関係ありません。)


プログラムは、このファイルにある100,000のソリューショングリッドのセット(7.82 MBのダウンロード)で実行し、ソリューションが生成するすべての100,000の問題グリッドの手がかりの数を合計することでスコア付けされます。

上記のテストファイルの数独ソリューションは、左から右、上から下の81文字の文字列として表されます。テストファイルの入力を使用可能なソリューションに変換するために必要なコードは、ソリューションのバイトカウントにはカウントされません。

私のFlood Paintチャレンジのように、プログラムは、有効なソリューションと見なされるために、100,000個のパズルすべてに対して有効な出力を実際に生成する必要があります。すべての100,000個のテストケースについて最も少ない合計手がかりを出力するプログラムが勝者であり、短いコードが同点になります。


現在のスコアボード:

  1. 2,361,024 -nutki、C
  2. 2,580,210 -es1024、PHP
  3. 6,000,000 -CarpetPython、Python 2
  4. 7,200,000 -Joe Z.、Python

また、1,700,000未満のソリューションを主張するソリューションは偽物であると確信できますが、これらがどれだけ低いかを確認したいと思います。
ジョーZ.

回答:


8

C-2,361,024 2,509,949手がかり

ブルートフォースソルバーが一意のソリューションを1つだけ見つけた場合、最後のセルから始まる手がかりを削除します。

2回目の試行:ヒューリスティックを使用して、最後から開始するのではなく、手がかりを削除する順序を決定します。これにより、コードの実行が非常に遅くなります(結果を計算するのに2ではなく20分)。ソルバーを高速化して、さまざまなヒューリスティックを試すことができますが、今のところは可能です。

#include <stdio.h>
#include <string.h>
char ll[100];
short b[81];
char m[81];
char idx[81][24];
int s;
char lg[513];
void pri2() {
    int i;
    for(i=0;i<81;i++) putchar(lg[b[i]]);
    putchar('\n');
}
void solve(pos){
int i,p;
if (s > 1) return;
if (pos == 81) { s++; return; }
if (b[pos]) return solve(pos+1);
for (p=i=0;i<24;i++) p |= b[idx[pos][i]];
for (i = 0; i < 9; i++) if (!(p&(1<<i))) {
    b[pos] = 1 << i;
    solve(pos + 1);
}
b[pos] = 0;
}
int main() {
    int i,j,t;
    for(i=0;i<9;i++) lg[1<<i]='1'+i;
    lg[0] = '.';
    for(i=0;i<81;i++) {
    t = 0;
    for(j=0;j<9;j++) if(i/9*9 + j != i) idx[i][t++] = i/9*9 + j;
    for(j=0;j<9;j++) if(i%9 + j*9 != i) idx[i][t++] = i%9 + j*9;
    for(j=0;j<81;j++) if(j/27 == i/27 && i%9/3 == j%9/3 && i!=j) idx[i][t++] = j;
    }
    while(scanf("%s ",ll)>0) {
    memset(m, 0, sizeof(m));
    for(i=0;i<81;i++) b[i] = 1 << (ll[i]-'1');
    for(i=0;i<81;i++) {
    int j,k,l = 99;
    for(k=0;k<81;k++) if (m[k] <= l) l = m[k], j = k;
    m[j] = 24;
    t = b[j]; b[j] = 0;
    s = 0; solve(0);
    if (s > 1) b[j] = t;
    else for(k=0;k<24;k++) m[idx[j][k]]++;
    }
    pri2();
    }
    return 0;
}

1

Python — 7,200,000手がかり

いつものように、ここに最後の参照ソリューションがあります:

def f(x): return x[:72] + "." * 9

数字の一番下の行を削除すると、すべての場合でパズルが解ける可能性があります。各列にはまだ9つの数字のうち8つが入力されているためです。

深刻な候補者が法的にこれよりも悪い得点をとることができたら、私は驚かされるでしょう。


つまり、最後の1つだけを削除できます。
seequ

また、すべてを解決したままにすることもできますが、どちらも深刻な競争相手ではありません。
ジョーZ.

では、なぜこれが深刻な競争相手なのでしょうか?
-theonlygusti

そうではありません。だからこそ、深刻な候補者がこの深刻ではない候補者よりも悪いスコアを付けられたら、私は驚かされると言ったのです。
ジョーZ.

1

Python 2-6,000,000の手がかり

これらのパズルを解決する3つの一般的な方法を使用するシンプルなソリューション:

def f(x): 
    return ''.join('.' if i<9 or i%9==0 or (i+23)%27 in (0,3) else c 
        for i,c in enumerate(x))

この関数は、次のような手がかり形式を生成します。

.........
.dddddddd
.dddddddd
.ddd.dd.d
.dddddddd
.dddddddd
.ddd.dd.d
.dddddddd
.dddddddd

これはいつでも解決できます。4つの3x3パーツが最初に解決され、次に8列、9行が解決されます。


1

PHP — 2,580,210の手がかり

これにより、最初にすべてのボックスの最後の行と列、および右下隅が削除されます。次に、各セルをクリアし、変更のたびに単純なソルバーを介してボードを実行して、ボードがまだ明確に解決できることを確認します。

以下のコードの多くは私の古い答えの1つから変更されました。printBoard空のセルには0を使用します。

<?php
// checks each row/col/block and removes impossible candidates
function reduce($cand){
    do{
        $old = $cand;
        for($r = 0; $r < 9; ++$r){
        for($c = 0; $c < 9; ++$c){
            if(count($cand[$r][$c]) == 1){ // if filled in
                // remove values from row and col and block
                $remove = $cand[$r][$c];
                for($i = 0; $i < 9; ++$i){
                    $cand[$r][$i] = array_diff($cand[$r][$i],$remove);
                    $cand[$i][$c] = array_diff($cand[$i][$c],$remove);
                    $br = floor($r/3)*3+$i/3;
                    $bc = floor($c/3)*3+$i%3;
                    $cand[$br][$bc] = array_diff($cand[$br][$bc],$remove);
                }
                $cand[$r][$c] = $remove;
            }
        }}
    }while($old != $cand);
    return $cand;
}

// checks candidate list for completion
function done($cand){
    for($r = 0; $r < 9; ++$r){
    for($c = 0; $c < 9; ++$c){
        if(count($cand[$r][$c]) != 1)
            return false;
    }}
    return true;
}

// board format: [[1,2,0,3,..],[..],..], $b[$row][$col]
function solve($board){
    $cand = [[],[],[],[],[],[],[],[],[]];
    for($r = 0; $r < 9; ++$r){
    for($c = 0; $c < 9; ++$c){
        if($board[$r][$c]){ // if filled in
            $cand[$r][$c] = [$board[$r][$c]];
        }else{
            $cand[$r][$c] = range(1, 9);
        }
    }}
    $cand = reduce($cand);

    if(done($cand))  // goto not really necessary
        goto end;    // but it feels good to use it 
    else return false;

    end:
    // back to board format
    $b = [];
    for($r = 0; $r < 9; ++$r){
        $b[$r] = [];
        for($c = 0; $c < 9; ++$c){
            if(count($cand[$r][$c]) == 1)
                $b[$r][$c] = array_pop($cand[$r][$c]);
            else 
                $b[$r][$c] = 0;
        }
    }
    return $b;
}

function add_zeros($board, $ind){
    for($r = 0; $r < 9; ++$r){
    for($c = 0; $c < 9; ++$c){
        $R = ($r + (int)($ind/9)) % 9;
        $C = ($c + (int)($ind%9)) % 9;
        if($board[$R][$C]){
            $tmp = $board[$R][$C];
            $board[$R][$C] = 0;
            if(!solve($board))
                $board[$R][$C] = $tmp;
        }   
    }}
    return $board;
}

function generate($board, $ind){
    // remove last row+col
    $board[8] = [0,0,0,0,0,0,0,0,0];
    foreach($board as &$j) $j[8] = 0;

    // remove bottom corner of each box
    $board[2][2] = $board[2][5] = $board[5][2] = $board[5][5] = 0;

    $board = add_zeros($board, $ind);

    return $board;    
}
function countClues($board){
    $str = implode(array_map('implode', $board));
    return 81 - substr_count($str, '0');
}

function generateBoard($board){
    return generate($board, 0);
}

function printBoard($board){
    for($i = 0; $i < 9; ++$i){
        echo implode(' ', $board[$i]) . PHP_EOL;
    }
    flush();
}
function readBoard($str){
    $tmp = str_split($str, 9);
    $board = [];
    for($i = 0; $i < 9; ++$i)
        $board[] = str_split($tmp[$i], 1);
    return $board;
}
// testing
$n = 0;
$f = fopen('ppcg_sudoku_testing.txt', 'r');
while(($l = fgets($f)) !== false){
    $board = readBoard(trim($l));
    $n += countClues(generateBoard($board));
}
echo $n;
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.