软件工程第三次作业

这个作业属于哪个课程 https://edu.cnblogs.com/campus/zswxy/software-engineering-2017-1
这个作业要求在哪里 https://edu.cnblogs.com/campus/zswxy/software-engineering-2017-1/homework/10494
这个作业的目标 个人编写程序——实现数独算法
作业正文 https://www.cnblogs.com/W4ter/p/12577481.html
其他参考文献 百度

https://github.com/waterrr/Software-Engineering-Homework/blob/master/sudoku.py

PSP表格

PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
Planning 计划 10 30
Estimate 估计这个任务需要多少时间 20 30
Development 开发 180 150
Analysis 需求分析 (包括学习新技术) 30 120
Design Spec 生成设计文档 30 30
Design Review 设计复审 15 15
Coding Standard 代码规范 (为目前的开发制定合适的规范) 15 15
Design 具体设计 30 60
Coding 具体编码 30 30
Code Review 代码复审 30 60
Test 测试(自我测试,修改代码,提交修改) 30 120
Reporting 报告 30 30
Test Repor 测试报告 30 20
Size Measurement 计算工作量 20 10
Postmortem & Process Improvement Plan 事后总结, 并提出过程改进计划 30 30
合计 530 750

写在前面

其实一开始听到这个消息我其实是拒绝的,我根本不会这么复杂的算法啊,上次走迷宫我都走了很久,完全不会,我对python的认知,仅仅存在会写简单脚本的水平,一旦涉及到复杂的算法,对不起,打扰了。

但是问题来了,既然已经布置了,还是硬着头皮也要做完,没办法,无奈之下,在网上找了大量资料,包括但不限于朱旭炜大佬、刘涛大佬、刘印传大佬、邓畅伟大佬、唐巍神仙的博客、github等,在这里表示感谢,虽然参考了这么多大佬的写法,感觉真的是一个比一个神仙,算法写得我根本看不懂,没法理解具体过程。

但是好在,我还是写出来了,在网上参考了一个比较容易理解的方法,鬼知道我经历了什么。

需求分析以及代码实现

首先,我跑去看了数独的规则,详细研究了一下,虽然这是一个很经典的益智游戏,但是我真的从小到大都没玩过,可能我对这些需要高智商的游戏不怎么感兴趣,题目要求我们做一个解数独的程序。当然三宫格的就没什么可玩的呢,就暂时不考虑,我们直接从四宫格开始吧。

1、建立坐标

我们首先把棋盘化为一个坐标,建立一个坐标系,把每个位置都变为一个坐标。

class point:
"""
初始化方法
建立坐标系
"""
    def __init__(self,x,y):
        self.x = x
        self.y = y
        self.available = []
        self.value = 0

2、判断行列

判断行重复和列重复,简单判断一下就好了,比如在第一行的数字为[4,0,0,0],那么第一行填充数只能为[1,2,3]。

def rowNum(p,sudoku):
"""
判断行
:return:
"""
    row = set(sudoku[p.y * 4:(p.y + 1) * 4])
    row.remove(0)
    return row


def colNum(p,sudoku):
"""
判断列
:return:
"""
    col = []
    length = len(sudoku)
    for i in range(p.x,length,4):
        col.append(sudoku[i])
    col = set(col)
    col.remove(0)
    return col

3、找到需要填充的位置

如图所示。假设给定一个四宫格,需要填充的地方就是数字0所在的地方,所以我们现在把他们都找出来。

举个例子,第一行为[4,0,0,0],那么在坐标关系上,4代表(0,0),那么需要填充的位置就为(1,0)、(2,0)、(3,0)。

def initPoint(sudoku):
"""
找到需要填充的位置
:return:
"""
    pointList = []
    length = len(sudoku)
    for i in range(length):
        if sudoku[i] == 0:
            p = point(i % 4,i // 4)   #遍历所有的点
            for j in range(1,5):      #找到不重复的结果
                if j not in rowNum(p,sudoku) and j not in colNum(p,sudoku):
                    p.available.append(j)
            pointList.append(p)
    return pointList

4、填充位置

对每个位置进行所有可能性地填充重复操作,直到满足数独的要求为止。

def tryInsert(p,sudoku):
"""
对于每个需要的位置进行填充
:return:
"""
    availNum = p.available
    for v in availNum:
        p.value = v
        if check(p,sudoku):
            sudoku[p.y*4 + p.x] = p.value
            if len(pointList) <= 0:
                exit()
            p2 = pointList.pop()
            tryInsert(p2,sudoku)
            sudoku[p2.y * 4 + p2.x] = 0
            sudoku[p.y * 4 + p.x] = 0
            p2.value = 0
            pointList.append(p2)
        else:
            pass

5、判断填充条件

在填充之前,首先检查需要填充的点是否为0,然后再次检查点有没有重复。

def check(p,sudoku):
"""
判断填充条件
:return:
""" 
    #如果为0就不填充
    if p.value == 0:
        return False
    #再次检查填充的点是否与当前已经填充的点重复
    if p.value not in rowNum(p,sudoku) and p.value not in colNum(p,sudoku):
        return True
    else:
        return False

6、主函数

if __name__ == '__main__':
    sudoku = [
        4,0,0,0,
        0,0,0,1,
        0,0,1,0,
        0,0,3,4,
    ]
    pointList = initPoint(sudoku)
    p = pointList.pop()
    tryInsert(p,sudoku)

半成品代码

至此,我们就大概得出了一个基本的模型,我们来看一下现在的半成品代码(并不是最终版)。

class point:
"""
初始化方法
建立坐标系
"""
    def __init__(self,x,y):
        self.x = x
        self.y = y
        self.available = []
        self.value = 0


def rowNum(p,sudoku):
"""
判断行
:return:
"""
    row = set(sudoku[p.y * 4:(p.y + 1) * 4])
    row.remove(0)
    return row


def colNum(p,sudoku):
"""
判断列
:return:
"""
    col = []
    length = len(sudoku)
    for i in range(p.x,length,4):
        col.append(sudoku[i])
    col = set(col)
    col.remove(0)
    return col


def initPoint(sudoku):
"""
找到需要填充的位置
:return:
"""
    pointList = []
    length = len(sudoku)
    for i in range(length):
        if sudoku[i] == 0:
            p = point(i % 4,i // 4)   #遍历所有的点
            for j in range(1,5):      #找到不重复的结果
                if j not in rowNum(p,sudoku) and j not in colNum(p,sudoku):
                    p.available.append(j)
            pointList.append(p)
    return pointList


def tryInsert(p,sudoku):
"""
对于每个需要的位置进行填充
:return:
"""
    availNum = p.available
    for v in availNum:
        p.value = v
        if check(p,sudoku):
            sudoku[p.y*4 + p.x] = p.value
            if len(pointList) <= 0:
                exit()
            p2 = pointList.pop()
            tryInsert(p2,sudoku)
            sudoku[p2.y * 4 + p2.x] = 0
            sudoku[p.y * 4 + p.x] = 0
            p2.value = 0
            pointList.append(p2)
        else:
            pass


def check(p,sudoku):
"""
判断填充条件
:return:
""" 
    #如果为0就不填充
    if p.value == 0:
        return False
    #再次检查填充的点是否与当前已经填充的点重复
    if p.value not in rowNum(p,sudoku) and p.value not in colNum(p,sudoku):
        return True
    else:
        return False
        

if __name__ == '__main__':
    sudoku = [
        4,0,0,0,
        0,0,0,1,
        0,0,1,0,
        0,0,3,4,
    ]
    pointList = initPoint(sudoku)
    p = pointList.pop()
    tryInsert(p,sudoku)

当然,我忘记把DEBUG阶段写出来了,当然没人知道我的DEBUG过程的痛苦过程,改一个BUG,多了十几个报错,但是好在我花了很长一段时间把报错都解决了。都是一些常见的错误,缩进问题,中文符号问题,变量名打错这几个问题,还好逻辑上没有错误,不然会花上更多的时间。

至此,一段解数独的胶水代码就完成了,我们来看看结果吧!

结果正确!!!!!

此时我的心情:

等等,好像离要求的还差很远,但是这不是正常现象吗。

静态代码检查

#!/usr/bin/env python3
# -*- encoding: utf-8 -*-
"""
@File    :   sudoku.py
@Contact :   admin@gksec.com
@License :   (C)Copyright 2007, GNU General Public License V3

@Modify Time      @Author    @Version    @Desciption
------------      -------    --------    -----------
2020/3/26 15:28   W4ter      1.0         None
"""

class point:
    """
初始化方法
建立坐标系
    """
    def __init__(self,x,y):
        self.x = x
        self.y = y
        self.available = []
        self.value = 0


def rowNum(p,sudoku):
    """
判断行
:return:
    """
    row = set(sudoku[p.y * 4:(p.y + 1) * 4])
    row.remove(0)
    return row


def colNum(p,sudoku):
    """
判断列
:return:
    """
    col = []
    length = len(sudoku)
    for i in range(p.x,length,4):
        col.append(sudoku[i])
    col = set(col)
    col.remove(0)
    return col


def initPoint(sudoku):
    """
找到需要填充的位置
:return:
    """
    pointList = []
    length = len(sudoku)
    for i in range(length):
        if sudoku[i] == 0:
            p = point(i % 4,i // 4)    #遍历所有的点
            for j in range(1,5):       #找到不重复的结果
                if j not in rowNum(p,sudoku) and j not in colNum(p,sudoku):
                    p.available.append(j)
            pointList.append(p)
    return pointList


def tryInsert(p,sudoku):
    """
对于每个需要的位置进行填充
:return:
    """
    availNum = p.available
    for v in availNum:
        p.value = v
        if check(p,sudoku):
            sudoku[p.y*4 + p.x] = p.value
            if len(pointList) <= 0:
                showSudoku(sudoku)
                exit()
            p2 = pointList.pop()
            tryInsert(p2,sudoku)
            sudoku[p2.y * 4 + p2.x] = 0
            sudoku[p.y * 4 + p.x] = 0
            p2.value = 0
            pointList.append(p2)
        else:
            pass


def check(p,sudoku):
    """
判断填充条件
:return:
    """ 
    #如果为0就不填充
    if p.value == 0:
        return False
    #再次检查填充的点是否与当前已经填充的点重复
    if p.value not in rowNum(p,sudoku) and p.value not in colNum(p,sudoku):
        return True
    else:
        return False


def showSudoku(sudoku):
    """
    定义输出格式
    """
    for j in range(4):
        for i in range(4):
            print('%d '%(sudoku[j * 4 + i]),end='')
        print('')


if __name__ == '__main__':
    sudoku = [
        4,0,0,0,
        0,0,0,1,
        0,0,1,0,
        0,0,3,4,
    ]
    pointList = initPoint(sudoku)
    p = pointList.pop()
    tryInsert(p,sudoku)

这是改完之后的,基本上都符合规范了,因为Pycharm在写的时候就会提示你的规范,所以改起来并没有什么很大的难度。

性能分析

这显然对我来说太难了,涉及到了我的知识盲区,我想不到更好的方法了,更好的方法我看不太懂,如果你真想看,请移步唐巍神仙的github去康康吧,对不起,我一个都不会。

单元测试

简单测试一下吧

import unittest
from sudoku import point

class SudokuTestCase(unittest.TestCase):
    def test_point(self):
        p = point(x=1, y=2)
        self.assertEqual(p.x,1)
        self.assertEqual(p.y,2)
        self.assertTrue(isinstance(p, point))


if __name__ == '__main__':
    unittest.main()

大工告成,别问我为什么不写其他的单元测试,因为我懒。

后记

文件输入输出

我还有很多东西没写完,比如要求的是文件输入输出,但是我还没有了解Python对文件的操作,我不太会,我看看我这两天有没有空,有空地话再补上。

关于高宫格

我的以上代码是基于四宫格的,也适用于像三宫格、四宫格、五宫格、七宫格只考虑格而不考虑宫的数独,四宫格是一个特殊情况,因为结果是唯一的,虽然他有宫,但是并不用考虑。至六宫格、八宫格、九宫格还要考虑宫,并且宫里面也有很多种情况,我没有很好的思路,我我的脑细胞已经死完了,简单了解了一下好像非常麻烦,超过我的能力范围了。

总结

我不会编程,我不适合开发。

image.png

原文地址:https://www.cnblogs.com/W4ter/p/12577481.html