juliaでSudokuを解く

julia

今回は、juliaでSudoku(Number Place)を解きます。

Sudokuを解くアルゴリズムは色々ありますが、今回は、深さ優先探索っていうの?再帰を使った解法(一番単純?)なやつでやっていきます。

Sudokuのルール

Sudokuは、9×9の盤面に1から9の数字を入れていくパズルです。

ルールは、縦・横、3×3の枠内のマスに1から9の数字を一つずつ入れる。

とっても簡単なルールです。

Sudokuを解くアルゴリズム

Sudokuを解くアルゴリズムを考えていきましょう。

今回実装するアルゴリズムは、人が、Sudokuを解くのとほぼ同じ手順を踏んでいきます。

  1. 空いた枠を見つける
  2. 空いた枠にどんな数字が入るか探す
  3. 可能性のある数字を入れてみて、進めてみる
  4. ダメだったら、違う数字を入れてみる
  5. 全部埋まるまで、2,3,4の作業を繰り返す
  6. 全部埋まったら終わり。

Juliaで実装

Juliaで今考えたアルゴリズムを実装していきましょう。

アルゴリズムから次の3つの関数を作って、解いていきます。

  • 空白を探す関数
  • どんな数字が入るか調べる関数
  • 数字を入れて解を進めていく関数

Sudokuのデータを保管する

関数を定義して、解いていく前にSudokuのデータ表現をします。

Sudokuは9×9のマス目になってますので、処理するときに便利なように行列で保存しておきます。

juliaだと素直に行列を書けば、行列として保存できるので、やっていきましょう。

grid = [4 0 0 0 0 7 0 0 8;
        0 0 0 0 0 0 0 1 0;
        0 0 2 0 3 0 5 0 0;
        0 0 0 0 0 8 0 0 7;
        0 5 3 0 0 0 0 9 0;
        0 0 0 0 0 4 0 0 0;
        0 0 0 0 6 0 0 0 0;
        0 0 0 0 5 0 0 3 0;
        8 0 0 0 0 0 0 0 0;]

最初から数字の入っているところにはその数字を入れます。空白の部分は0で埋めます。

要素の指定方法は、

grid[行,列]

例えば、1行6列目の要素は7

grid[1,7]

これでアクセスできます。

ここから先の解説では、行は縦なのでy、列は横なのでxとして、表記していきます。

grid[y,x]

こういう感じで使います。

空白を探す

実際に数字を入れていくセル、空白(ゼロ)を探す関数を定義していきましょう。

問題データを渡したら、空白の座標(x、y)を返値として返してくる関数を考えます。

もし、空白が無ければ-1をそれぞれ返します。

function find_cell(grid)
    for y=1:9, x = 1:9
        if grid[y,x] == 0
            return x,y
        end
    end
    return -1,-1
end

ひたすら上から順番にループ回して、探してくるっていう感じです。

juliaのforループでは、多重ループをする時に

for i=1:9
    for j=1:9
      処理
    end
end

とは書かずにfor一本で書けるのでスッキリします。

for i=1:9,j=1:9
    処理
end

上も下も同じ結果が返ってきます。

どんな数字が入るか調べる関数

空白が見つかったので、そこに数字を入れますが、どんな数字が入れれるのか調べる必要があります。

今入っている数字を被らないのを見つけないといけないです。

調べるのは、縦、横、3×3の枠内の3つについてです。

まずはコードを見てみましょう

function possible(x,y,n,grid)

#y方向でチェック
    for i in 1:9
        if grid[i,x] == n
            return false
        end
    end

#x方向のチェック
    for i in 1:9
        if grid[y,i] ==n
            return false
        end
    end

#3x3マスのチェック
    yo = div(y-1,3)*3
    xo= div(x-1,3)*3
    for i = 1:3,j =1:3
        if grid[yo+i,xo+j] == n

            return false
        end
    end

    return true
end

上から順番にy方向、x方向、3×3マス内のチェックと進んでいきます。

引数で座標(x、y)と数字(n)を渡してます。渡した座標にnが入るかどうかを調べてます。

上の2つのは、単純にそれぞれの方向に進めていけば、チェックできます。

3×3マスは、3×3マスを取り出してこないとチェックできません。

そこで、9×9マスの座標と3×3マスの座標を変換して処理をする必要があります。

3×3マスの起点になる座標を計算するのが最初の2行です。

yo = div(y-1,3)*3
xo = div(x-1,3)*3

Sudokuを解くプログラムのサイトをいくつか見て参考にしたときに、3で割って整数部を見ればOKですよってことでした。

pythonの場合、添え字が0,1,2,3,4,5,6,7,8となっています。すると、0,1,2を3で割ると整数部は0,同様に4,5,6は1,6,7,8は2となって、そこに3を掛けると元の座標と3×3の座標の変換ができちゃうっていう優れものです。

Juliaの場合は、添え字が1からスタートで、1,2,3,4・・・となっています。そこで、座標を1引いて計算することで、同じように処理できるようにしています。

for文の中は、単純にループを回して、与えたnが入るかどうか調べています。

数字を入れて解を進めていく関数

空白のチェック、数字が入るかどうか調べる関数ができたので、実際に数字を入れて解を進めていく関数を作っていきましょう。

function solv(x,y,grid)
    x,y = find_cell(grid)
    if  x==-1
        return true
    end

    for n =1:9
        if possible(x,y,n)
            grid[y,x]=n
            if solv(x,y)
                return true
            end

            grid[y,x]=0
        end
    end
    return false
end

パートごとに見ていきましょう

x,y = find_cell(grid)
if  x==-1
    return true
end

まずは、空白セルを探します。もし、‐1が返ってきたら、空白なし=解が求められているということで、trueを返して関数から抜けます。

for n =1:9
    if possible(x,y,n)
       grid[y,x]=n
       if solv(x,y)
            return true
       end
            grid[y,x]=0
    end
end

possible関数を使って、空白セルに対して、数字nが入るかどうか調べます。

もし入るのであれば、その数字を入れてみます。

次のif文でそれをもう一度自身の関数を呼び出します。

このif文がfalseであれば、その座標は0に戻して、やり直します。それを繰り返して、解を求めていきます。

まとめ

実際に解いてみると、簡単なものから超難問みたいなものまで、解を求めることができました。

簡単な問題は本当0.02秒とかで求めることができます。一瞬です。ただ、難易度が上がると時間がかかります。これは、単純な解き方ゆえの問題点でしょう。

実際人が解くときは、まずは当てはめやすいところをやって、そして、入る数字のリストを作って・・・と効率的に解いていくところ、今回のアルゴリズムの場合は、もう力業ですから、効率もくそもなく、時間がかかるのは仕方がないです。

数独を解くアルゴリズムは他にもいくつかあるので、他のものも実装してみるも楽しいと思います。

是非チャレンジしてください

コメント

タイトルとURLをコピーしました