Github项目地址
Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|
计划 | 30 | 20 |
估计在这个任务需要多少时间 | 10 | 15 |
需求分析(包括学习新技术) | 60 | 120 |
生成设计文档 | 200 | 100 |
设计复审 | ||
代码规范 | 60 | 60 |
具体设计 | 120 | 200 |
具体编码 | 2000 | 2500 |
代码复审 | ||
测试 | 60 | 180 |
报告 | 60 | 100 |
测试报告 | 30 | 20 |
计算工作量 | 30 | 30 |
事后总结 | 30 | 30 |
合计 | 2690 | 3375 |
第一行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进行优化
给出参数个数过多,过少,格式错误的用例
构造多个不同的类的实例,检测是否被正确初始化
测试输出到文件中的结果是否符合数独规则,
以及检查生成的终局是否有重复
对比数独题目文件和解文件中的结果是否相符并且正确
测试当文件中题目无效时,输出是否和预期相符
对函数的各分支分别进行测试
对比输出结果是否与预期相符
其中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()