內容目录

上一个主题

如何在Python 中制作一个解数独的模拟环境?

下一个主题

程式详解: sudoku套件

本页

撰写 Python 程式来解数独了

当我们已经在电脑建构好一个数独模拟世界时,对那些我们以前使用手写的方式所发现的解数独方法,我们就可以着手来将其以电脑语言(这里是Python)来重新阐述。就术语来说,称为程式设计(Programming)。所以程式设计可以视为我们在教导电脑如何去执行我们解决问题的方法。

一开始,我们先介绍这个专案整个求解数独的环境,然后我们再说明一些基本方法。

求解环境

我们设计了solve() 这个函数来做整个求解数独的入口,另外也定义了两个事件类别,SudokuDon 及SudokuError,以处理求解过程中发现已经解开数独或者产生违反数独规则的状况。

注解

什么是「例外处理(Exception)」?

「例外处理(Exceptions)」是一种事件定义,当构成这个事件的条件满足时,系统就会停止处理目前的事务,而跳到该事件的处理程序。在这个专案中有两个主要的例外处理:

SudokuDone:

当这个数独已经被解开时,这个例外处理会被叫醒

SudokuError:

当求解过程中发现违反数独规则时,这个例外处理会被叫醒

为了让整个求解环境能够知道有多少种求解方法它能够使用,我们设计了一个类别,SolveMethod。我们以这个类别来将所有的求解方法置入成一个**虚拟大脑**。我们可以将这个虚拟大脑视为是这个美丽山谷的守护神。每当有新进来此山谷者,找不到居住所时,都可以透过祂来选择出适合的房子,但祂也可能回答:「对不起,我也不知道该如何选择!」

对**虚拟大脑**而言,每一个解数独的方法都化为一个SovleMethod 物件储存在里面,这些物件有以下主要属性:

  1. fun: 解数独方法的 Python 函数名称

  2. idx: 方法的排序,从简单的方法开始到困难的方法依序排列,虚拟大脑将依序一个一个地使用这些方法来解开数独。

  3. name: 此方法的名称,使用者可以自订其名称

  4. level: 困难度,对人的直觉而言。系统用此来计数解开整个数独的困难积分。

求解过程

下面是主要求解函数,solve(),的流程图:

_images/flowchart.png

注解

**「有用(work)」或「没用(not work)」? **

一个求解方法如果是「有用」,表示它能够:

  1. 让一个或多个人找到他们的居所

  2. 或者是可让一间或多间房子知道一个或多个国家的人不能居住在他们的房子

在这张流程图中,我们知道:

  1. 当一个求解方法有用时,设定了一个人的住所或降低了一间房子可居住者的可能性时,整个虚拟大脑会重新回到第一个求解方法来继续求解。

  2. 假如一个求解方法没用时,它会交给下一个方法来解决。

  3. 当到最后一个方法都没用时,他会离开整个求解环境并说:「我无法解开这个游戏,抱歉!」

  4. 在整个求解过程中,如果发现已解开数独(Done)或者违反了数独规则(Error)时,它就会跳离整个求解环境。

一些基本求解方法

  1. fill_only_one_possible:

    在每一个房子群组中,检查每一间房子,是否只有一间房子能够让某国的人民居住,如果是,则该间房子必然要让那国的人民迁入。

  2. fill_last_position_of_group:

    当一个房子群组中只剩一间空房时,那必然是只有一个国家的人民尚未入住。

  3. check_obvious_number:

    检查每一个国家中已经定居的国民,看他们所在的位置所交互影响、而尚未有该国人民入住的区块中,是否可以找到仅剩一间房子容许该国国民来定居,如果是,则可以很明显地配给该房子给与该国的人民。

  4. check_inobvious_number:

    这个求解方法与check_obvious_number 相同,只是先找到一些区块的虚拟国民,让他们以及已定居的国民一起来探测其他尚未有本国国民居住的区块。

  1. reduce_by_group_number:

    如果在一个房子区块中形成了一个虚拟国民时, 那与这个虚拟国民同方向的房子群组中的其他空房子,就不可能居住这个国民。

  2. update_chain:

    当一些房子开始住人以后,会影响一些房子不再能够允许一些国家的居民,而这些空房子可能因此在一个房子群组中(x轴、y轴、区块)中产生了一个**键链(Chain)**。这个方法就是去寻找可能的键链,并将键链所影响的空房子减少它们被这些键链的国民居住的可能。

如何去实作一个求解方法呢?

我们来实作一个只为一个国家的人民来看是否能有一个直觉的方法来为一个尚未有该国住民的区块找到住所,我们命为check_obvious_for_a_country(m, num):

 1  def check_obvious_for_a country(m, num):
 2  checked = list()
 3  for p1 in m.n[num].p:
 4      for b in m.b[p1.b].effects:
 5          possible = []
 6          if b in checked:
 7              continue
 8          else:
 9              checked.add(b)
10          if num not in m.b[b].possible:
11              continue
12          for p2 in m.b[b].p:
13              if p2.v != 0 or p2.can_see(p1) > 0:
14                  continue;
15              if not m.lineX[p2.x].allow(num):
16                  continue
17              if not m.lineY[p2.y].allow(num):
18                  continue
19              possible.append(p2)
20          if len(possible) == 1:
21              m.setit(possible[0].x, possible[0].y, num, d="Obvious For a Country People")
  1. line#1, 定义一个求解方法, m 是这个游戏的数独世界,num 则是一个国家代码, 可能值为1-9。

  2. line#3, 将每一个已经入住此山谷的此国国民都找出他们的住所。

  3. line#4-9, 检查每一个尚未被检查的房子区块。

  4. line#10-11, 如果此区块已有该国人民居住,那就不用检查了。

  5. line#12-19, 检查此区块每一个空房是否可以让该​​国的人民来居住。如果是,则将该房子放进去可能的名单里面。

  6. line#20-21, 最后检查可能的名单中,如果只有一个时,那就表示该房子必然由该国人民来居住。