LoginSignup
9

More than 3 years have passed since last update.

posted at

updated at

Pythonで数独を解く

はじめに

hello😇初めてのアドベントカレンダーですがいつも通り書いていきます。今回はpythonで数独を解いていこうと思います。
※コードはこちらに挙げておくので主要なコードのみ説明します。

数独のルール

  1. あいているマスには1から9までの数字が必ず1つ入る。
  2. 縦9マスに1から9までの数字が1つずつ入る。(重複はしない)
  3. 横9マスに1から9までの数字が1つずつ入る。(重複はしない)
  4. 太線で囲まれた3×3のブロックのどれにも1から9までの数字が1つずつ入る。(重複はしない)

(例)
image.png

実装

今回は最適化問題でよく使われるmypulpモジュールを使います。データはエクセルに格納しopenpyxlを使い扱います。必要なモジュールはrequirements.txtに格納しておいたので下記コードをコマンドラインで実行してください。

pip install -r requirements.txt

データ作成

まず数独のデータを保管するリストを用意します。


N = [i for i in range(1,10)]    #行番号,列番号,数
C = [(3*i+2, 3*j+2) for i in range(3) for j in range(3)]   #ブロックの中心位置の集合
D = [(i-1, j-1) for i in range(3) for j in range(3)]       #周囲8マス+そのマスの相対的な位置の集合

そしてエクセルからヒントの数を読み込みます。


n = {}
for i in N:
    for j in N:
        n[i, j] = sheet.cell(row=i+1, column=j+3).value

制約条件

制約条件は主に四つ挙げられます。
① 何かしらの数字が入る(ヒントの数字含む)

for i in N:
    for j in N:
        if type(n[i, j]) is int and 1 <= n[i, j] <= 9:
            model.addConstr(x[i,j,n[i, j]]==1)#ヒントの数字
        else:
            model.addConstr(quicksum(x[i,j,k] for k in N)==1)#それ以外

② 行に各数字が1つずつ入る

for i in N:
    for k in N:
        model.addConstr(quicksum(x[i,j,k] for j in N)==1)

③ 列に各数字が1つずつ入る

for j in N:
    for k in N:
        model.addConstr(quicksum(x[i,j,k] for i in N)==1)

④ ブロックに各数字が1つずつ入る

for (ci, cj) in C:
    for k in N:
        model.addConstr(quicksum(x[ci+di, cj+dj, k] for (di, dj) in D)==1)

目的変数

通常最適化問題では何らかの数値を最大、最小にすることが多いですが今回は特にありません。

実行

今回はsudoku_ans.xlsxという別のファイルに書き込むようになっています。

model.optimize()
# 出力
if model.Status == GRB.Status.OPTIMAL:
    print('解が見つかりました。')
    for i in N:
        for j in N:
            for k in N:
                if x[i, j, k].X > 0.01:
                    font = copy(sheet.cell(row=i+1, column=j+13).font)
                    if type(n[i, j]) is int:
                        font.color = styles.colors.Color(rgb='ffff0000')     #ヒントの数
                    else:
                        font.color = styles.colors.Color(rgb='ff000000')   #求めた数
                    sheet.cell(row=i+1, column=j+13).font = font
                    sheet.cell(row=i+1, column=j+13).value = k                    
    book.save('sudoku_ans.xlsx')
else:
    print('解が見つかりませんでした。')

最後にsudoku_ans.xlsxを確認ししっかり数が入っていれば成功です。

まとめ

今回は数独を解きましたが、数学パズルゲーム全般解けるのでいろいろ試してみたいですね。それでは😇

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
9