VOOZH about

URL: https://qiita.com/SatoshiTerasaki/items/bb9f4862ac79cbfa9759

⇱ [Python]Z3で数独パズルを解くをリファクタリング #Python3 - Qiita


👁 Image
7

Go to list of users who liked

2

Share on X(Twitter)

Share on Facebook

Add to Hatena Bookmark

More than 5 years have passed since last update.

@SatoshiTerasaki

[Python]Z3で数独パズルを解くをリファクタリング

7
Last updated at Posted at 2017-10-01

本日は

数独のソルバーを紹介している
[Python]Z3で数独パズルを解く
のソルバーをクラス化させてみました.理由としては,

  • 再現実験として試したかった(楽しそうだった)
  • これをベースに簡単なGUIアプリを作ってみたかったこと
  • そのためにクラス化によるリファクタリングでコードの再利用性を高めたかった.

z3 のインストール

Macの場合

$pip install z3-solver

でインストールが可能になります.
ビルドに時間がかかります.ゆっくり待ちましょう.

実装例

z3solver
import z3
from itertools import product

grid = lambda i, j: z3.Int("grid[%d,%d]" % (i, j))


class Z3Solver(z3.Solver):
 GRID_SIZE = 9
 SUB_GRID_SIZE = 3

 def __init__(self, problem):
 super(Z3Solver, self).__init__()
 self.problem = problem
 self._set_problem()

 def solve(self):
 self._set_restriction()
 return self.check()

 def _set_problem(self):
 N = Z3Solver.GRID_SIZE
 for i, j in product(range(N), range(N)):
 if self.problem[i][j] > 0:
 self.add(grid(i, j) == self.problem[i][j])

 def _set_restriction(self):
 N = Z3Solver.GRID_SIZE
 B = Z3Solver.GRID_SIZE//Z3Solver.SUB_GRID_SIZE
 SUB_GRID_SIZE = Z3Solver.SUB_GRID_SIZE
#set initial value
 self.add(*[1 <= grid(i, j) for i, j in product(range(N), repeat=2)])
 self.add(*[grid(i, j) <= 9 for i, j in product(range(N), repeat=2)])
 #distinct w.r.t col 
 for row in range(N):
 self.add(z3.Distinct([grid(row, col) for col in range(N)]))
 #distinct w.r.t row 
 for col in range(N):
 self.add(z3.Distinct([grid(row, col) for row in range(N)]))
 #distinct
 for row in range(B):
 for col in range(B):
 block = [grid(B*row+i, B*col+j)
 for i, j in product(range(SUB_GRID_SIZE), repeat=2)]
 self.add(z3.Distinct(block))


def solve_sudoku(problem):
 solver = Z3Solver(problem)
 result = solver.solve()
 if result == z3.sat:
 model = solver.model()
 # print result
 for i in range(Z3Solver.GRID_SIZE):
 for j in range(Z3Solver.GRID_SIZE):
 print("%d " % model[grid(i, j)].as_long(), end="")
 print("")
 print("")
 else:
 print(result)


def main():
 problem1 = [[0, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 1, 0, 8, 0],
 [6, 4, 0, 0, 0, 0, 7, 0, 0],
 [0, 0, 0, 0, 0, 3, 0, 0, 0],
 [0, 0, 1, 8, 0, 5, 0, 0, 0],
 [9, 0, 0, 0, 0, 0, 4, 0, 2],
 [0, 0, 0, 0, 0, 9, 3, 5, 0],
 [7, 0, 0, 0, 6, 0, 0, 0, 0],
 [0, 0, 0, 0, 2, 0, 0, 0, 0]]
 solve_sudoku(problem1)

 problem2 = [[0, 0, 0, 0, 0, 0, 0, 3, 9],
 [0, 0, 0, 0, 0, 1, 0, 0, 5],
 [0, 0, 3, 0, 5, 0, 8, 0, 0],
 [0, 0, 8, 0, 9, 0, 0, 0, 6],
 [0, 7, 0, 0, 0, 2, 0, 0, 0],
 [1, 0, 0, 4, 0, 0, 0, 0, 0],
 [0, 0, 9, 0, 8, 0, 0, 5, 0],
 [0, 2, 0, 0, 0, 0, 6, 0, 0],
 [4, 0, 0, 7, 0, 0, 0, 0, 0]]
 solve_sudoku(problem2)


if __name__ == '__main__':
 main()

簡単な変更点として

  • z3を継承して数独のソルバーとするクラスを実装した.
  • 入力データフォーマットを整数値を持つ二重配列リストにした(不定部分を0にする)
  • 変数をアンダースコアを用いて単語の区切りをするようにした.
  • 多重ループを itertool.product を用いて見た目の行数を減らす.
  • 制約条件を設定する部分を一つにまとめる

制約条件についてはどうやら

self.add(*[1 <= grid(i, j) <=9 for i, j in product(range(N), range(N))])

という書き方はダメみたい.z3.addしたときに解析が失敗します.
複数の条件を一気に add したい場合 add したい対象をリストに入れて

self.add(*[P,Q,R,S,T,U])

のようにリストの前に*をつけて展開させるのがミソ

この後は

GUIアプリを作ってみたい.(また後日)
追記:アルファ版作りました.ー> https://qiita.com/SatoshiTerasaki/items/e98c9a1c88c0280f54c4

7

Go to list of users who liked

2
0

Go to list of comments

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
  3. You can use dark theme
What you can do with signing up
7

Go to list of users who liked

2