[Python]使用Z3解决数独难题


简介

第一个汤和第n个汤

关于数独

数独是什么?


↑这样的人
-一种铅笔拼图,可将1到9的数字放在9x9的正方形框中,分成3x3块
-数独

https://ja.wikipedia.org/wiki/数独

数独规则

  • 将1到9之间的任何数字放入空白处
  • 每个垂直列和水平列以及由粗线包围的3x3块中均不得包含相同的数字。

关于Z3

什么是Z3?

  • Microsoft Research开发的SMT求解器
  • 麻省理工学院执照

https://github.com/Z3Prover/z3

什么是SMT求解器?

  • 确定一阶谓词逻辑的可满足性

    • 如果满足(SAT),它将输出满意的分配解决方案之一。
    • 如果不能令人满意(UNSAT),则不会发出(是的)

尝试解决

准备

  • 从github拾取z3

https://github.com/Z3Prover/z3

这次要解决的问题


我将尝试解决Sudoku问题收集高级001

实施

  • 在Python 3中实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# -*- coding: utf-8 -*-

# z3をインポート
from z3 import *

#全ての変数valは1<=val<=9であることの制約を追加するメソッド
def betweenOneToNine(val,s):
    for i in range(n):
        for j in range(n):
            s.add(1 <= val[i][j], val[i][j] <= n)    

#rowがそれぞれ他と値が異なっていることの制約を追加するメソッド
def distinctRow(val,s):
    for i in range(n):
        tmpList = []
        for j in range(n):
            tmpList.append(val[i][j])
        s.add(Distinct(tmpList))

#columnがそれぞれ他と値が異なっていることの制約を追加するメソッド
def distinctColumn(val,s):
    for i in range(n):
        tmpList = []
        for j in range(n):
            tmpList.append(val[j][i])
        s.add(Distinct(tmpList))

#ブロックの中身がそれぞれ他と値がことなっていることの制約を追加するメソッド
def distinctBlock(val,s):
    for k in range(3):
        for l in range(3):
            tmpList = []
            for i in range(3):
                for j in range(3):
                    tmpList.append(val[3*k+i][3*l+j])
            s.add(Distinct(tmpList))

#あるマス目に任意の数値をセットすることの制約を追加するメソッド
def setNum(val,s,x,y,num):
    s.add(val[x-1][y-1]==num)

#数独問題集上級001の問題設定の制約を追加するメソッド
#http://www.sudokugame.org/archive/printsudoku.php?nd=2&xh=1
def setProb001(val,s):
    setNum(val,s,2,6,1)
    setNum(val,s,2,8,8)
    setNum(val,s,3,1,6)
    setNum(val,s,3,2,4)
    setNum(val,s,3,7,7)
    setNum(val,s,4,6,3)
    setNum(val,s,5,3,1)
    setNum(val,s,5,4,8)
    setNum(val,s,5,6,5)
    setNum(val,s,6,1,9)
    setNum(val,s,6,7,4)
    setNum(val,s,6,9,2)
    setNum(val,s,7,6,9)
    setNum(val,s,7,7,3)
    setNum(val,s,7,8,5)
    setNum(val,s,8,1,7)
    setNum(val,s,8,5,6)
    setNum(val,s,9,5,2)            

#n*n個の変数を用意
n = 9
val = [[Int("val[%d,%d]" % (i,j)) for j in range(n)] for i in range(n)]
s = Solver()

#制約を追加
betweenOneToNine(val,s)
distinctRow(val,s)
distinctColumn(val,s)
distinctBlock(val,s)
setProb001(val,s)

#充足可能性を判定、unsatならexit
r = s.check()
if r == sat:
    m = s.model()
    #印字
    for i in range(n):
        for j in range(n):
            print("%d " % m[ val[i][j] ].as_long(), end="")
        print()
else:
    print(r)

评论

我是用这种策略做到的
准备了1.9 * 9整数类型变量val
2.对于所有变量val,1≤val≤9
3.列中的9个变量都是不同的
4.该行中的9个变量都是不同的
5. 3 * 3块中的所有9个变量都不相同
6.输入最初放置的变量

1.准备9 * 9整数类型变量val

  • 创建一个整数变量如下
1
x = Int("x")
  • 需要9 * 9整数变量才能解决此问题
  • 我们这里还有一个将受到约束的求解器。
1
2
3
4
#n*n個の変数を用意
n = 9
val = [[Int("val[%d,%d]" % (i,j)) for j in range(n)] for i in range(n)]
s = Solver()

2.对于所有变量val,1≤val≤9

  • 要将约束添加到求解器:
  • 在此示例中,命题P和命题Q的乘积被添加为约束。
1
s.add(P,Q)
  • 为了解决此问题,我希望所有9 * 9 val都等于1或大于等于9,所以如下。
1
2
3
4
5
#全ての変数valは1<=val<=9であることの制約を追加するメソッド
def betweenOneToNine(val,s):
    for i in range(n):
        for j in range(n):
            s.add(1 <= val[i][j], val[i][j] <= n)

3.列中的9个变量都不同

  • 多个表达式具有不同值的限制如下:
  • 在这种情况下,x,y和z均为不同的值。
1
Distinct(x, y, z)
  • 为了解决这个问题,我希望九列中的所有数字都不同,所以如下
1
2
3
4
5
6
7
#rowがそれぞれ他と値が異なっていることの制約を追加するメソッド
def distinctRow(val,s):
    for i in range(n):
        tmpList = []
        for j in range(n):
            tmpList.append(val[i][j])
        s.add(Distinct(tmpList))

4.该行中的九个变量都不同

  • 几乎与专栏中的相同
  • 为了解决这个问题,我希望九行中的所有数字都不同,所以如下
1
2
3
4
5
6
7
#columnがそれぞれ他と値が異なっていることの制約を追加するメソッド
def distinctColumn(val,s):
    for i in range(n):
        tmpList = []
        for j in range(n):
            tmpList.append(val[j][i])
        s.add(Distinct(tmpList))

5. 3 * 3块中的所有9个变量都不相同

  • 从列和行的时间开始有点独创性
  • 为了解决这个问题,我希望9 3 * 3块中的所有数字都不同,因此如下。
1
2
3
4
5
6
7
8
9
#ブロックの中身がそれぞれ他と値がことなっていることの制約を追加するメソッド
def distinctBlock(val,s):
    for k in range(3):
        for l in range(3):
            tmpList = []
            for i in range(3):
                for j in range(3):
                    tmpList.append(val[3*k+i][3*l+j])
            s.add(Distinct(tmpList))

6.输入最初放置的变量

  • 准备像setter的方法

    • 使用第3个和第4个参数指定坐标
    • 指定要放置在第5个参数中的数值
1
2
3
#あるマス目に任意の数値をセットすることの制約を追加するメソッド
def setNum(val,s,x,y,num):
    s.add(val[x-1][y-1]==num)
  • 我将把最初放置在这个问题中的数值放入
  • 强硬
  • 应该更聪明
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#数独問題集上級001の問題設定の制約を追加するメソッド
def setProb001(val,s):
    setNum(val,s,2,6,1)
    setNum(val,s,2,8,8)
    setNum(val,s,3,1,6)
    setNum(val,s,3,2,4)
    setNum(val,s,3,7,7)
    setNum(val,s,4,6,3)
    setNum(val,s,5,3,1)
    setNum(val,s,5,4,8)
    setNum(val,s,5,6,5)
    setNum(val,s,6,1,9)
    setNum(val,s,6,7,4)
    setNum(val,s,6,9,2)
    setNum(val,s,7,6,9)
    setNum(val,s,7,7,3)
    setNum(val,s,7,8,5)
    setNum(val,s,8,1,7)
    setNum(val,s,8,5,6)
    setNum(val,s,9,5,2)

结果

  • 解决方案已输出
1
2
3
4
5
6
7
8
9
1 8 7 6 3 2 5 4 9
5 9 2 4 7 1 6 8 3
6 4 3 9 5 8 7 2 1
4 7 6 2 9 3 8 1 5
3 2 1 8 4 5 9 7 6
9 5 8 7 1 6 4 3 2
2 6 4 1 8 9 3 5 7
7 1 5 3 6 4 2 9 8
8 3 9 5 2 7 1 6 4

摘要

  • 我想使用Z3解决一个简单的逻辑问题
  • 用Python实现
  • 现在,您可以根据需要申请Sudoku Puzzle Sweepstakes!