2020軟件工程作業03


這個作業屬於那個課程 https://edu.cnblogs.com/campus/zswxy/software-engineering-2017-1
這個作業的要求在哪里 https://edu.cnblogs.com/campus/zswxy/software-engineering-2017-1/homework/10494
這個作業的目標 實現一個命令行程序,不妨稱之為Sudoku。
作業正文 https://www.cnblogs.com/liutaodashuaige/p/12539391.html
其他參考文獻 www.baidu.com
https://www.cnblogs.com/ouyangpeng/p/8537616.html
https://blog.csdn.net/sunyanxiong123/article/details/76401590
https://github.com/zxw0621/demo/blob/master/20177596/src/sudoku.py

一、GitHub url:https://github.com/liutaodashuaige/LT_DEMO/tree/master/20177569

二、PSP表格

PSP2.1 Personal Software Process Stages 預估耗時(分鍾) 實際耗時(分鍾)
Planning 計划 30 100
Estimate 估計這個任務需要多少時間 20 30
Development 開發 800 1000
Analysis 需求分析 (包括學習新技術) 60 120
Design Spec 生成設計文檔 30 20
Design Review 設計復審 20 10
Coding Standard 代碼規范 (為目前的開發制定合適的規范) 30 30
Design 具體設計 30 60
Coding 具體編碼 200 250
Code Review 代碼復審 30 30
Test 測試(自我測試,修改代碼,提交修改) 60 200
Reporting 報告 60 150
Test Repor 測試報告 10 30
Size Measurement 計算工作量 20 -
Postmortem & Process Improvement Plan 事后總結, 並提出過程改進計划 30 120
合計 890 1250


三、解題思路

1.理解問題

  • 該算法題的需求是實現一個稱之為Sudoku命令行程序。
  • 程序要實現利用邏輯和推理,在在數獨盤面的空格上填入1-9的數字。使1-9每個數字在每一行、每一列和每一宮中都只出現一次。
  • 輸入要求輸入文件名以命令行參數傳入。
  • 輸出要求輸出n個程序解出的盤面,每兩個盤面間空一行,每個盤面中,每兩個小格之間有一個空格。

2.思考如何實現

  • 由於不同階的數獨盤面雖然空格數量上有差異,但對每一個空格合法性的判斷方法和區塊的構造都是相似的,同時這是一個查找最優策略的問題,因此我認為應該使用遞歸的方式解題。

3.尋找參考資料

四、設計實現過程

1.函數模塊的設計

  • 根據本題的解題要求和思考中確定的遞歸解題思想,應設計以下模塊:
    • 主函數模塊
    • 遞歸查找模塊
    • 合法性判斷模塊
    • 輸出文件生成模塊

2.具體功能函數設計

  • main(argv)函數

    • 接受命令行參數,並進行解析
    • 讀取input文件,並進行解析
    • 根據盤面數量調用DFS()深度優先搜索遞歸函數
  • DFS(i, x, y)函數

    • 對當前遞歸狀態進行判斷
    • 對當前坐標格狀態進行判斷
    • 嘗試填入數值,並調用judge()函數驗證其合法性
    • 根據不同條件進行遞歸
    • 驗證失敗時,回溯
  • judge(i, x, y)函數

    • 對傳入的坐標進行行列重復判斷
    • 對傳入的坐標進行區塊定位
    • 對傳入的坐標進行區塊重復判斷
    • 根據是否合法返回布爾值
  • MY_OTP(i)函數

    • 根據傳入的盤面序號將該盤面矩陣寫入output文件
  • 簡單流程圖

3.全局變量設置

  • M(盤面階數)
  • N(盤面數目)
  • MY_MAPS(儲存所有盤面矩陣的三維列表)
  • op(文件對象)

五、改進思路

1.代碼靜態分析

  • 首先我們使用pylint進行代碼靜態分析

很明顯代碼已經是炸了,不過比起第一次用pylint已經好不少了...第一次可是負分XD

  • 讓PyCharm幫我整理一下...

看下效果,有所進步,剩下的就是命名的規范了

  • 修改不符合規范的命名后

經過一陣搗鼓評分提升了不少,但仍然沒有達到滿分,測試了一下代碼正常運行
只能說,有時候投降不失為一種優雅的退場,我就不折磨自己了


2.代碼性能優化

#改為直接給予參數,而不是從命令行接受
if __name__ == '__main__':
    # sys.argv[1:]為要處理的參數列表,sys.argv[0]為腳本名,因此棄之不用
    #main(sys.argv[1:])
    main(['-m', '9', '-n', '2', '-i', 'input.txt', '-o', 'output.txt'])
  • 利用PyCharm的profile進行代碼性能分析

顯然對於我來說這個圖是天書


由此表中可得知被調用次數最多和耗時最多的是judge()合法性判斷函數和_DFS_()遞歸函數

  • 性能優化集中於judge()和_DFS_()兩個函數

3.單元測試

  • 函數judge()有返回值,並且是一個布爾值,對該函數進行單元測試
  • 卡了很久,一直無法導入py文件的函數,通過參考資料中的方法解決了
  • 涉及到全局變量,先對judge()函數進行一些調整,添加一段代碼
    M = 9
    MY_MAPS = []
    # 上面都是給judge()函數運行提供必要的全局變量
    with open('output.txt', 'r', encoding='utf-8') as _fp_:  # 此處直接讀取已解矩陣用來判斷合法性
        _MYMAP_ = []
        for line in _fp_.readlines():
            if line != '\n':  # 用換行符分割矩陣
                _MYMAP_.append(list(map(int, line.strip().split(" "))))
            else:
                MY_MAPS.append(_MYMAP_)
                _MYMAP_ = []
        MY_MAPS.append(_MYMAP_)  # MY_MAPS是集合了所有數據的三維數組
    #單元測試時用來提供全局變量...

    #################
    #global M, MY_MAPS
    # 行列不重復判斷
  • 測試代碼編寫
import unittest
from Sudoku import judge

class test_judge(unittest.TestCase):
    def test_myfun(self):
        test_num = judge(0, 1, 2)#測試數值
        self.assertEqual(test_num, 1)#期望值


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

  • 測試結果

GOOOOOD!測試符合預期結構

4.對代碼全面檢測和優化后,更新GitHub上的倉庫

六、代碼說明

0.導包和定義全局變量

# -*- coding: UTF-8 -*-
import sys
import getopt

# 全局變量
M = ""
N = ""
MY_MAPS = []
OP = ""

1.主函數

if __name__ == '__main__':
    # sys.argv[1:]為要處理的參數列表,sys.argv[0]為腳本名,因此棄之不用
    main(sys.argv[1:])

2.main()函數

def main(argv):
    """
    通過sys模塊來識別參數
    :return:
    """
    # 聲明全局變量
    global M, N
    global MY_MAPS, OP
    in_put = ""
    out_put = ""
    try:  # 獲取參數並處理異常
        opts, args = getopt.getopt(argv, "m:n:i:o:", ["help"])
    except getopt.GetoptError:
        print('Error: Sudoku.py -m -n -i -o')
        sys.exit(2)
    # 處理獲取的參數
    for opt, arg in opts:
        if opt in "--help":  # 給予幫助提示
            print('Error: Sudoku.py -m -n -i -o')
            sys.exit()
        elif opt in "-m":
            M = int(arg)
        elif opt in "-n":
            N = int(arg)
        elif opt in "-i":
            in_put = arg
        elif opt in "-o":
            out_put = arg
    with open(in_put, 'r', encoding='utf-8') as _fp_:  # 以讀狀態打開指定文件讀取矩陣
        _MYMAP_ = []
        for line in _fp_.readlines():
            if line != '\n':  # 用換行符分割矩陣
                _MYMAP_.append(list(map(int, line.strip().split(" "))))
            else:
                MY_MAPS.append(_MYMAP_)
                _MYMAP_ = []
        MY_MAPS.append(_MYMAP_)  # MY_MAPS是集合了所有數據的三維數組

    OP = open(out_put, 'w', encoding='utf-8')

    for i in range(N):
        if i > 0:
            OP.write('\n')  # 分割矩陣
        _DFS_(i, 0, 0)  # 遞歸求解
    OP.close()

3.DFS()遞歸函數

def _DFS_(_i_, _x_, _y_):
    """
    【DFS】深度優先搜索遞歸方式
    :return:
    """
    # 聲明引用全局變量
    global M, MY_MAPS
    if _x_ > M - 1:  # 完成條件
        _MY_OTP_(_i_)  # 保存數值
    elif MY_MAPS[_i_][_x_][_y_] != 0:  # 當前格子不可填
        if _y_ == M - 1:  # 右邊界換行
            _DFS_(_i_, _x_ + 1, 0)
        else:
            _DFS_(_i_, _x_, _y_ + 1)  # 下一格
    else:  # 當前格可填
        for i in range(1, M + 1):
            MY_MAPS[_i_][_x_][_y_] = i  # 試探填入數值
            if judge(_i_, _x_, _y_):  # 判斷其試探值的合法性,當判斷函數返回值為1即合法
                if _y_ == M - 1:  # 邊界情況
                    _DFS_(_i_, _x_ + 1, 0)
                else:
                    _DFS_(_i_, _x_, _y_ + 1)
            # 回溯
            MY_MAPS[_i_][_x_][_y_] = 0

4.judge()合法性判斷函數

def judge(_i_, _x_, _y_):
    """
    合法性判斷
    :return:
    """
    global M, MY_MAPS
    # 行列不重復判斷
    for i in range(M):
        if i != _x_ and MY_MAPS[_i_][_x_][_y_] == MY_MAPS[_i_][i][_y_]:
            return 0
        if i != _y_ and MY_MAPS[_i_][_x_][_y_] == MY_MAPS[_i_][_x_][i]:
            return 0
    # 區塊重復判斷
    _x1_ = _y1_ = row = col = 0  # 塊內坐標初始值
    # 區塊定位參考於https://github.com/zxw0621/demo/blob/master/20177596/src/sudoku.py#L42
    # 這定位寫的太好了
    # 根據其階數確定其模塊規模以及所屬模塊
    if M % 3 == 0:
        row = 3
        col = int(M / 3)
    elif M % 2 == 0:
        row = 2
        col = int(M / 2)
    _x1_ = int(_x_ // row * row)
    _y1_ = int(_y_ // col * col)
    # 遍歷所屬區塊,檢查其合法性
    for i in range(_x1_, _x1_ + row):
        for j in range(_y1_, _y1_ + col):
            if _x_ != i and _y_ != j and MY_MAPS[_i_][_x_][_y_] == MY_MAPS[_i_][i][j]:
                return 0
    return 1

5.MY_OTP()數據儲存函數

def _MY_OTP_(_i_):
    """
    向文件內寫入所得矩陣
    :return:
    """
    global N, M, MY_MAPS, OP
    # 遍歷當前求解矩陣
    for _x_ in range(M):
        for _y_ in range(M):
            OP.write(str(MY_MAPS[_i_][_x_][_y_]) + ' ')
        OP.write('\n')  # 換行

6.異常處理

  • 當參數輸入異常時,輸出提示幫助輸入
 try:  # 獲取參數並處理異常
        opts, args = getopt.getopt(argv, "m:n:i:o:", ["help"])
    except getopt.GetoptError:
        print('Error: Sudoku.py -m -n -i -o')
        sys.exit(2)
  • 當用戶在命令行輸入-help參數時,給予提示
 # 處理獲取的參數
    for opt, arg in opts:
        if opt in "--help":  # 給予幫助提示
            print('Error: Sudoku.py -m -n -i -o')
            sys.exit()
        elif opt in "-m":
            M = int(arg)
        elif opt in "-n":
            N = int(arg)
        elif opt in "-i":
            in_put = arg
        elif opt in "-o":
            out_put = arg

7.代碼運行結果

命令行

輸入文件

輸出文件

至此程序宣布完成...

七、心路歷程

記錄

  • 3月21日
    • 截至到12:00,目前仍然在研究代碼實現思路(拖,就硬拖)
    • psp表格
    • 研究DFS遞歸函數
    • 設計功能模塊
    • 查閱資料,實現命令行輸入參數
    • 完成main()函數代碼
    • 上傳項目到GitHub,被網絡折磨了半小時
    • 顯然急於求成是愚蠢的,折磨了自己一晚上


  • 3月22日
    • 截至到12:00,完成了博客的基本框架
    • 完成全部代碼塊
    • 完成流程圖
    • 代碼靜態檢測、性能分析、單元測試(折磨王三連折磨)
    • 代碼優化,賊難受
    • 更新GitHub倉庫(還好這波網絡沒炸)
    • 寫博客
    • 終於完成了!!!蕪湖~
總結

——在本次任務中我深刻的認識到了不能閉門造車的道理,死鑽牛角尖最后只會折磨自己,應該具有發散性的思維,充分利用網絡資源查閱資料,從多個角度對問題進行分析。最終得出自己的解題思路。同時在工作學習時不能急於求成,這樣反而會使得自己的效率下降,得不償失。在遭遇挫折的時候不妨帶着問題和其他人互相交流一番,或許問題會用迎刃而解。最后,事實證明寫代碼只占作業的10%,一系列附屬操作把我給折磨的不輕...


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM