內容目录

上一个主题

课程计画

下一个主题

撰写 Python 程式来解数独了

本页

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

我们认为, 类别定义, 是在OOP程式设计中最困难的部分. 所以在这些课程中, 我们不准备详细地解释如何设计类别, 属性与方法. 我们只会解说在这个程式库中既有的类别, 属性与方法. 所以我们可以类比这些类别, 属性与方法所构成的资料结构是一个解决数独的模拟环境, 以便让学习者以人的思考模式来学习如何解开一个数独.

首先, 我们可以想像在一个美丽的山谷中, 有9x9 间方方正正的房子, 它们排列如下:

_images/p4.png

一个假想的联合王国

这里有9 个国家, 每一个国家有9 个人, 他们决定一起定居在这个美丽的山谷. 而这个山谷共有9x9 间房子. 他们决定每一排(x-way line), 每一列(y-way line ), 及每一个3x3 区块都包含了每一个国家的人. 如此, 他们才会认为他们这个团体才是一个真正的联合王国, 可以永久和平地生活在一起. 你可以帮助他们达到这个目的吗?

接着, 我们就可以开始协助这些人来促成这一个美好的世界...

什么是类别? 什么是物件?

在OOP中,类别(class)的定义是主角。它就如同人类j为了探索、沟通或者纪录等目的,会将有同样行为、特性与外表的事物归类一样。如同我们会将动物当作一类,而大象就是动物类别的一个子类别,一只大象就是同属于动物及大象这两类的一个物件。

虽然在自然上,一只大象这个物件同时隶属于大象类及动物类,但看目标的需要,也可以直接将一只大象当做动物类别的一个物件即可。所以我们可以说在OOP 所说的物件(Object)就是我们现实世界的一个实体,就如同人类若是一个类别,那你就是隶属此类别的一个唯一独立的物件。

依据我们设定的范围与需要,我们可能设计出不同的类别,而让同一个物件同时隶属于它们。如我们想要研究城市生态时,我们可能会设计一个animal 类别,而这个类别中的物件包括一些人,一些宠物等等...但当我们想要制作一个电话簿程式时,那我们就会设计一个person 类别,一些人会成为此类别的物件,但不会包括宠物,除非这些宠物也都拥有手机。

在这个专案中,我们设计的主要类别有:

  • Number Class:

    我们可以视数独游戏中每个数字是一个人。而整个数独世界中有9 个国家,每个国家有9 个人。如此,这个Number Class 就可以视为是一个国家类别。每一个国家都有一个识别代码(ID),在这里是1-9,而每个国家都会纪录它的人民住在这山谷中的位置。

  • Point Class:

    Point 物件就是一间房子。每间房子都会标注它是否已住人,如果已住人,那是哪个国家的人民;如果是空的,那可以让哪些国家的人民来申请入住?

  • GroupBase Class:

    GroupBase是一种群组类别,也就是它的物件不是一个实体,而是一群实体的组合。在这里的GroupBase 包含了三种群组,X​​ 与Y 轴方向的房子群组及区块房子群组。每个物件都将指出它包含了哪些房子,已经住了多少人,还有哪些国家的人民还没住进来?

  • Box Class:

    它是GroupBase的子类别,为区块群组。在数独世界中总共有9 个区块物件,从左到右,从上到下,被标注其区块代码如下:

    _images/p5.png
  • lineX Class:

    这是GroupBase 子类别,为X 轴的房子群组。在数独世界中共有9 个物件,从左到右被标注1-9,如下图:

    _images/p6.png
  • lineY Class:

    这是GroupBase 子类别,为Y 轴的房子群组。在数独世界中共有9 个物件,从上到下被标注1-9,如下图:

    _images/p7.png
  • Matrix Class:

    Matrix class定义了数独游戏的整个世界。它是一个美丽的山谷,包含了9 个国家的人民,每一个国家有9 个人,山谷中建造了9x9 间房子以提供给这些人民来居住,以组成一个永远和平的联合王国。

什么是属性(Property)?

属性(Property)是在类别(Class)的定义里面,类别用它来定义成员,特征及纪录状况。如在一个人的类别里面可能会包含以下一些属性: 这人拥有多少钱、他有几个小孩子、第一个小孩是男或是女、每个小孩各是几岁?

以下是这个专案中几个主要类别的主要属性定义:

  • Number Class:

    • v: 这个国家的代码, 1-9

    • p: 这个国家每一个人民所居住的房子列表

    • filled: 有多少人已经住进房子了

  • Point Class:

    • x: 这个房子的 x 轴座标

    • y: 这个房子的 y 轴座标

    • v: 这个房子居住了哪个国家的人民,如果是空的,那它的值就是 0

    • b: 这个房子隶属哪个区块

  • GroupBase Class:

    • idx: 这个群组的代码

    • p: 隶属这个群组的房子列表

    • filled: 在这个群组里面已经居住了多少人

    • possilbe: 在这个群组里面还有哪些国家还没住进来,值为这些国家的代码列表

  • Box Class:

    • 包含所有 GroupBase 的属性

    • effects: 这个区块的所有邻居区块

    • effectsX: 这个区块的 x 轴方相邻居

    • effectsY: 这个区块的 y 轴方相邻居

  • lineX Class:

    • 与 GroupBase 的属性相同

  • lineY Class:

    • 与 GroupBase 的属性相同

  • Matrix Class:

    • p: 一个二阶阵列的房子列表,从p[0][0] 到p[8][8],代表这个山谷的所有房子。

    • lineX: x 轴方向房子群组的列表

    • lineY: y 轴方向房子群组的列表

    • b: 区块房子群组的列表

    • n: 所有国家的列表

    • filled: 纪录已经有多少人入住在这个山谷了

什么是方法(Method)?

方法(methods)是一个类别及其物件的一些特定行为。举个例子,如果我们定义了一个收音机类别,它将包含一些按钮的属性,而当我们按下这些按钮时,我们就必须定义一些方法来执行这个动作。这些动作有可能是开始接收某个电台的节目、或者是录制节目到CD等等...

以下是这个专案中的类别中使用道的主要方法:

  • Number Class:

    • setit(p1): 当有人找到属于自己的房子时,就会启动这个方法

  • Point Class:

    • can_see(p1): 测试一个房子是否能够**看到**另外一个房子(p1)?

    • can_see_those(posList): 测试一个房子能否**看见** posList 所列的房子,并将所有能看见的房子列表传回。

    注解

    什么是「看见」?

    对一个房子而言,与它同一排、同一列、或者同在一个区块的其他房子,都是它能够**看见**的房子。

  • GroupBase Class:

    • allow(v): 测试一个房子群组能否让标记为v 的国家人民来居住?

    • get_num_pos(v): 在这个房子群组中,取得v 国人民居住的房子物件,如果该国尚未有人入住,则以None来回应。

    • count_num_possible(count): 在一个房子群组中,取得国家的id及可供该国人民居住的房子列表,如果有参数count,则表示要取得的可供居住的房子数要等于count 才取回。

    • get_all_pos(method): 如果method = “a”, 取得一个房子群组的所有房屋物件列表;如果method=”u”,则取得所有空房列表;如果method=”s”,则取得所有已住人的房​​子列表。

  • Box Class:

    • 所有 GroupBase 类别的方法

    • get_group_number(num): 测试这个国家代码num 在一个房子区块中能否形成一个**虚拟国民**?

    注解

    什么是「虚拟国民」?

    虚拟国民存在于一个房子区块群组中。在一个区块中尚未有人入住的房子中,如果所有可能让某个国家居住的房子在同一个方向时(无论是x 轴或y 轴),那我们就可以称这些房子形成了一个**虚拟国民**。虽然我还不晓得在这个区块中,这个国家的人民最后将居住在哪里,但我们从虚拟国民中知道,在与它同个方向的其他区域的房子,都将不允许居住这个国家的人民了。

  • lineX Class:

    • 与 GroupBase 有相同的方法

  • lineY Class:

    • 与 GroupBase 有相同的方法

  • Matrix Class:

    • get_all_pos(method): 如果method = “a”, 取得所有房屋物件列表;如果method=”u”,则取得所有空房列表;如果method=”s”,则取得所有已住人的房子列表。

    • sort_unassigned_pos_by_possibles(possibles): 取得所有的空房列表,而这些空房数必须仅能够让possibles 个国家的人入住,如果possibles == 0, 将取得全部空房。回应回来时将以可居住国家数,从小排到大。

    • can_see(p0, method=”u”, num=0): 取得能够看见某一房子(p0)的房子列表,如国num!=0,表示仅取得可让国家代码为num 者居住的房子。

    • setit(x, y, v): 让国家代码为v 的人民安住在座标为(x, y)的房子。

    • reduce(x, y, v): 当一个房子(x, y)被入住时,任何能够看见此房子的其他空房,都可用此方法来减掉已入住这个国家人民(v)的可能性。

    • allow(x, y, v): 测试这个国家(v)的人民是否能够居住于座标(x, y)的房子?

    • read(file): 从一个定义档(file)中读入最初到此山谷的国家、人民与居住位置。

数独游戏定义档案

你能够定义数独游戏的最初状况。在文字档中一行定义一个房子的座标及居住者,格式为x, y, v,如下图,此专案附有一些已定义好的数独,置于[安装的目录]/sudoku/data/ 目录里。

一个定义范例、最初的外观以及解出来的外观
m3.data

起始的数独外观

已解的数独外观

_images/m3.png _images/origin.png _images/result.png