数独终局生成与求解python实现

本文介绍了使用Python实现数独终局的生成和解法,通过回溯算法解决数独问题。文章还涉及性能分析,包括文件读写优化,并进行了详细的单元测试以确保程序的正确性。最后,文章提到了简单的用户界面设计。
摘要由CSDN通过智能技术生成

Github项目地址

Personal Software Process Stages预估耗时(分钟)实际耗时(分钟)
计划3020
估计在这个任务需要多少时间1015
需求分析(包括学习新技术)60120
生成设计文档200100
设计复审
代码规范6060
具体设计120200
具体编码20002500
代码复审
测试60180
报告60100
测试报告3020
计算工作量3030
事后总结3030
合计26903375

终局生成

思路

第一行9个数,除去第一个数要求固定为8外,其余的8个数有8!= 40,320种排列
而对应每种排列情况,可以把排列依次旋转0,3,6,1,4,7,2,5,8个数,生成40320个终局,而对此终局还可以,交换1~3行内可以交换两行(即这3行可以任意顺序排列),4~6行、7~9行亦是如此,同理列方向也可如此。
由此,我们可以得到8!・3!・3!=1,451,520已经大于一百万的要求
又因为第一个数要求固定,我们只需要任意改变4~6行、7~9行的排列顺序即可

相关部分代码如下:
    # 对第一行不同的排列,生成不同的局面
    def create_grid(self, row):
        grid = []
        for slice_x in self.order:
            grid.append(row[-slice_x:]+row[:-slice_x])
        return grid

    # 生成第一行的全排列
    def permutation(self, a_row):
        if not a_row:
            self.perm.append(list(self.tmp_row))
            return
        for i in a_row:
            self.tmp_row[self.cur] = i
            self.cur += 1
            tmp_ls = list(a_row)
            tmp_ls.remove(i)
            self.permutation(tmp_ls)
            self.cur -= 1

函数的调用关系图如下:
在这里插入图片描述

解数独

算法的基本思路

采用回溯法,逐行逐列的进行回溯直到全部求解出来或者判断无解

函数的调用关系如下:
在这里插入图片描述

相关部分代码如下
    def solve(self, row_n, col_n):
        # 如果当前列超出总列数则进入下一行第一列
        if col_n == 9:
            row_n += 1
            col_n = 0
        # 直到找到一个空格
        while True:
            # 若遍历完仍没有空,说明已完成填空,返回
            if row_n > 8:
                return True
            if self.mark[row_n][col_n]:
                break
            col_n += 1
            if col_n == 9:
                row_n += 1
                col_n = 0
        while True:
            self.a_plz[row_n][col_n] = self.find_next(row_n, col_n, self.a_plz[row_n][col_n] + 1)
            if self.a_plz[row_n][col_n] == 0:
                break
            self.rule(row_n, col_n, False)
            tmp_flag = self.solve(row_n, col_n+1)
            if tmp_flag:
                return True
            self.rule(row_n, col_n, True)
        return False

性能分析与改进

按照上述思路,把代码实现后发现性能远不如想象中的好

用性能分析工具进行分析,结果如下:

生成终局的性能分析

在这里插入图片描述
从上面分析可以看出有很大一部分时间花费在了文件读写上,于是,就想到通过一块一块的读写来代替现在的逐字符读写,改进之后,果然性能好了很多

解数独的性能分析

在这里插入图片描述
消耗最大的是求解数独的主要函数
在这里插入图片描述
后来发现,同学用C写的和我用的同样的算法,各方面性能都远远的超过我的。
于是想到用cython进行优化,把频繁调用的函数用cython改写

单元测试

由于用cython优化后,文件内大多函数对外不可见,无法进行单元测试。
故本单元测试是在cython优化之前进行的,即先通过单元测试确保了程序的正确性之后,再用cython进行优化

针对每个单元设计多个测试用例(总用例数大于10个),确保能覆盖大部分情况
对输入进行测试

给出参数个数过多,过少,格式错误的用例

对类初构造函数测试

构造多个不同的类的实例,检测是否被正确初始化

对生成终局函数进行测试

测试输出到文件中的结果是否符合数独规则,
以及检查生成的终局是否有重复

对解数独函数进行测试

对比数独题目文件和解文件中的结果是否相符并且正确
测试当文件中题目无效时,输出是否和预期相符

对类中的负责处理各种情况的main函数测试

对函数的各分支分别进行测试
对比输出结果是否与预期相符

在这里插入图片描述

下面是测试代码覆盖率

其中test.py是测试代码,因为其中一些语句只有在测试未通过时才会执行,所以当全部通过时这部分测试代码未被覆盖到

在这里插入图片描述

界面

在这里插入图片描述

点击Next按钮生成下了题目,点击OK按钮检查当前的填的解的正确性,分别会有相应的弹窗提醒

相关部分代码如下
    def gui(self):
        root = tk.Tk()
        # 设置窗口宽度与高度不可变
        root.resizable(False, False)
        root.title("Sudoku")

        # 从文件中读取并解码生成临时图标,用完后立马删除
        tmp = open("tmp.ico", "wb+")
        tmp.write(base64.b64decode(ICO_IMG))
        tmp.close()
        #im = Image.open("tmp.ico")
        #img = ImageTk.PhotoImage(im)
        #root.tk.call('wm', 'iconphoto', root._w, img)
        root.iconbitmap('tmp.ico')
        os.remove("tmp.ico")
        tmp = open("tmp.jpg", "wb+")
        tmp.write(base64.b64decode(JPG_IMG))
        tmp.close()
        tmp_image = Image.open("tmp.jpg")
        photo = ImageTk.PhotoImage(tmp_image)
        label = tk.Label(root, image=photo)
        os.remove("tmp.jpg")
        # for i in range(11):
        #     root.rowconfigure(i,weight=1)
        #     root.columnconfigure(i,weight=1)
        # root.rowconfigure(11,weight=1)

        # 生成空格
        for i in range(11):
            for j in range(11):
                if i != 3 and i != 7 and j != 3 and j != 7:
                    self.value[i][j] = tk.StringVar()
                    self.ety[i][j] = tk.Entry(root, textvariable=self.value[i][j], width=2, font=90)
                    self.ety[i][j].grid(row=i, column=j, padx=12, pady=12, sticky='NSEW')

        # 生成第一个题目并显示
        self.bind()

        label.grid(row=0, column=0, rowspan=12, columnspan=11, sticky='NSEW')

        # 确定按钮
        submit_btn = tk.Button(root, text='OK', command=lambda:self.check())
        submit_btn.grid(row=11, column=9, pady=10, ipadx=30, columnspan=2)

        # next按钮
        next_btn = tk.Button(root, text='Next>', command=lambda:self.bind())
        next_btn.grid(row=11, column=0, pady=10, ipadx=20, columnspan=2)

        root.mainloop()

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值