一、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.尋找參考資料
- https://github.com/zxw0621/demo/blob/master/20177596/src/sudoku.py#L153
- https://github.com/changorz/work/blob/master/20177583/src/Sudoku.java
- https://blog.csdn.net/sunyanxiong123/article/details/76401590
- GitHub操作參考博客
- PSP表格設計參考
四、設計實現過程
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倉庫(還好這波網絡沒炸)
- 寫博客
- 終於完成了!!!蕪湖~
總結