201971010235-阮凱 實驗三 結對專案—《{0-1}KP 例項資料集演算法實驗平臺》專案報告
專案 | 內容 |
---|---|
課程班級部落格連結 | 2022年春軟體工程課程班(2019級電腦科學與技術) |
這個作業要求連結 | 實驗三 軟體工程結對專案 |
我的課程學習目標 |
|
我實現的學習目標 |
|
結對方學號-姓名 | 201971010146-楊凱 |
結對方本次部落格作業連結 | 201971010146-楊凱 實驗三 結對專案—《{0-1}KP 例項資料集演算法實驗平臺》專案報告 |
本專案Github的倉庫連結地址 | 倉庫連線 |
任務1的完成情況如下:
閱讀《現代軟體工程—構建之法》第3-4章內容,理解及掌握情況如下:
1. 程式碼風格規範:
- 主要是文字上的規定,其原則是:簡明,易讀,無二義性。
內容 | |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2. 程式碼設計規範:
- 牽涉到程式設計、模組之間的關係、設計模式等方方面面的通用原則。
內容 | |
---|---|
關於函式的重要原則是:只做一件事,並且要做好。 | |
goto可以讓函式有單一的出口。 | |
引數處理:所有引數都要驗證其正確性。 斷言:使用斷言來驗證引數的正確性。 |
3. 程式碼複審:
- 定義:看程式碼是否在程式碼規範的框架內正確的解決了問題。
- 形式:自我複審、同伴複審、團隊複審。
- 目的:
- 找出程式碼的錯誤;
- 發現邏輯錯誤,程式可以編譯通過,但是程式碼的邏輯是錯的;
- 發現演算法錯誤,比如使用的演算法不夠優化,邊界條件沒有處理好等;
- 發現潛在的錯誤和迴歸性錯誤-當前的修改導致以前修復的缺陷又重新出現;
- 發現可能需要改進的地方;
- 教育(互相教育)開發人員,傳授經驗,讓更多的成員熟悉專案各部分的程式碼,同時熟悉和應用領域相關的實際知識。
- 程式碼複審的步驟:
- 程式碼必須成功地編譯,在所有要求的平臺上,同時要編譯Debug|Retail 版本。
- 程式設計師必須測試過程式碼。
- 程式設計師必須提供新的程式碼,以及檔案差異分析工具。
- 在面對面的複審中,一般是開發者控制流程,講述修改的前因後果。但是複審者有權在任何時候打斷敘述,提出自己的意見。
- 複審者必須逐一提供反饋意見。
- 開發者必須負責讓所有的問題都得到滿意的解釋或解答,或者在TFS中建立新的工作項以確保這些問題會得到處理。
- 對於複審的結果,雙方必須達成一致意見。
4. 結對程式設計:
- 在結對程式設計模式下,一對程式設計師肩並肩、平等地、互補地進行開發工作。
- 優點:
- 結對程式設計能提供更好的設計質量和程式碼質量。
- 對開發人員來說,結對程式設計可以帶來更多的信心,高質量的產能帶來更高的滿足感。
- 在企業管理層次上,結對能更有效地交流,相互學習和傳遞經驗,分享知識,能更好地應對人員流動。
- 若對結對程式設計運用得當,則可取得更高的投入產出比。
任務2的完成情況如下:
1. 結對方專案二部落格連結:楊凱的部落格
2. 結對方Github專案倉庫連結:楊凱的Github倉庫
3. 結對方專案二部落格評論連結:對楊凱專案二部落格評論
- 點評內容如下:
4. 克隆結對方專案原始碼:
- 在克隆的過程中,出現了錯誤,在查閱相關材料後,克隆成功,克隆情況如下所示:
- 執行結對方的程式碼,其執行結果和結對方在其部落格中所描述的一致,仔細檢視程式碼後,瞭解到結對方沒有實現對一組{0-1}KP資料按重量比進行非遞增排序,進行程式碼的修改後,幫其實現了此功能。
5. 程式碼核查表:
- 專案二的開發者:楊凱
- 專案二的複審者:阮凱
部分 | 要求 | 實際完成情況 |
---|---|---|
1. 概要部分 | 1)程式碼符合需求和規格說明麼? | 較符合; |
2)程式碼設計是否考慮周全? | 考慮的較為周全; | |
3)程式碼可讀性如何? | 程式碼的可讀性較好; | |
4)程式碼容易維護麼? | 較易維護; | |
5)程式碼的每一行都執行並檢查過了嗎? | 多數執行並檢查了,有少數並未執行; | |
2. 設計規範部分 | 1)設計是否遵從已知的設計模式或專案中常用的模式? | 遵從; |
2)有沒有硬編碼或字串/數字等存在? | 沒有; | |
3)程式碼有沒有依賴於某一平臺,是否會影響將來的移植(如Win32到 Win64 )? | 沒有,不會影響移植; | |
4)開發者新寫的程式碼能否用已有的 Library/SDK/Framework 中的功能實現?在本專案中是否存在類似的功能可以呼叫而不用全部重新實現? | 能,存在,有些程式碼可以呼叫; | |
5)有沒有無用的程式碼可以清除? | 有; | |
3. 程式碼規範部分 | 1)修改的部分符合程式碼標準和風格麼? | 基本符合; |
4. 具體程式碼部分 | 1)有沒有對錯誤進行處理?對於呼叫的外部函式,是否檢查了返回值或處理了異常? | 對相關錯誤進行了處理,沒有異常; |
2)引數傳遞有無錯誤,字串的長度是位元組的長度還是字元(可能是單/雙位元組)的長度,是以 0 開始計數還是以 1 開始計數? | 沒有錯誤,本專案不涉及字串 ; | |
3)邊界條件是如何處理的?switch語句的default分支是如何處理的?迴圈有沒有可能出現死迴圈? | 對switch語句的default分支沒有進行處理,沒有出現死迴圈; | |
4)有沒有使用斷言(Assert)來保證我們認為不變的條件真的得到滿足? | 沒有; | |
5)對資源的利用,是在哪裡申請,在哪裡釋放的?有無可能存在資源洩漏(記憶體、檔案、各種GUI資源、資料庫訪問的連線,等等)?有沒有優化的空間? | 對資源的利用,在最開始申請,在完成相關功能的計算後將其釋放,沒有資源洩露,沒有優化空間; | |
6)資料結構中有沒有用不到的元素? | 沒有; | |
5. 效能 | 1)程式碼的效能(Performance)如何?最壞的情況是怎樣的? | 基本完成了具體任務要求; |
2)程式碼中,特別是迴圈中是否有明顯可優化的部分(C++中反覆建立類,C# 中 string 的操作是否能用 StringBuilder 來優化)? | 沒有; | |
3)對於系統和網路的呼叫是否會超時?如何處理? | 目前沒有出現超時現象,若出現則將會進行防毒處理,減少執行的程序,釋放記憶體等; | |
6. 可讀性 | 1)程式碼可讀性如何?有沒有足夠的註釋? | 可讀性較好,註釋較為全面; |
7. 可測試性 | 1)程式碼是否需要更新或建立新的單元測試?針對特定領域的開發(如資料庫、網頁、多執行緒等),可以整理專門的核查表。 | 程式碼需要更新; |
6. 結對方專案倉庫中的日誌資料:
-
Fork:對結對方的倉庫進行克隆。
-
Clone:對結對方的倉庫進行fork操作後,在自己的賬號中會出現結對方的專案,之後就可以將其克隆到自己的電腦進行操作。
-
Push:通過此命令可以將改動同步到自己的Github倉庫中。
-
Pull request:將已經修改的內容申請同步到結對方的專案中。
任務3的完成情況如下(專案必須包含src資料夾。):
1. 需求分析陳述:
Who 為誰設計,使用者是誰?
0-1揹包問題在現實中有著廣泛的應用背景,如預算控制、專案選擇、材料切割、貨物裝載等,使用者是在相應的場景中需要拿到最優解得群體。
What 要解決那些問題?
需要解決如下問題:
- 讀入資料:
- 可正確讀入實驗資料檔案的有效{0-1}KP資料。
- 繪製散點圖:
- 能夠繪製任意一組{0-1}KP資料以價值重量為橫軸、價值為縱軸的資料散點圖。
- 非遞增排序:
- 能夠對一組{0-1}KP資料按重量比進行非遞增排序。
- 最優解和求解時間:
- 使用者能夠自主選擇:
- 貪心演算法求解指定{0-1} KP資料的最優解和求解時間(以秒為單位)。
- 回溯演算法求解指定{0-1} KP資料的最優解和求解時間(以秒為單位)。
- 動態規劃演算法求解指定{0-1} KP資料的最優解和求解時間(以秒為單位)。
- 使用者能夠自主選擇:
- 資料儲存:
- 任意一組{0-1} KP資料的最優解、求解時間和解向量可儲存為txt檔案或匯出EXCEL檔案。
- 資料儲存:
- {0-1}KP 例項資料集需儲存在資料庫。
- 平臺:
- 平臺可動態嵌入任何一個有效的{0-1}KP 例項求解演算法,並儲存演算法實驗日誌資料。
- 介面:
- 人機互動介面要求為GUI介面(WEB頁面、APP頁面都可)。
- 遺傳演算法:
- 查閱資料,設計遺傳演算法求解{0-1}KP,並利用此演算法測試平臺。
Why 為什麼解決這些問題?
解決這些問題可以加深對0-1揹包問題所涉及的不同演算法的理解。
2. 軟體設計說明:
- 演算法設計:
- 動態規劃演算法:在0-1揹包問題中,物品i或者被裝入揹包,或者不被裝入揹包;wi表示物品的重量,vi表示對應的物品的價值,C表示揹包的總容量,n表示物品的數量。
-
令V(i, j)表示在前i(1≤i≤n)個物品中能夠裝入容量為j(1≤j≤C)的揹包中的物品的最大值,則可以得到如下動態規劃函式:
-
第一個式子表明:如果第i個物品的重量大於揹包的容量,則物品i不能裝入揹包,則裝入前i個物品得到的最大價值和裝入前i-1個物品得到的最大價值是相同的。
-
二個式子表明:如果第i個物品的重量小於揹包的容量,則會有以下兩種情況:
- 如果第i個物品沒有裝入揹包,則揹包中物品的價值就等於把前i-1個物品裝入容量為j的揹包中所取得的價值。
- 如果把第i個物品裝入揹包,則揹包中物品的價值等於把前i-1個物品裝入容量為j-wi的揹包中的價值加上第i個物品的價值vi;
- 取二者中價值較大者作為把前i個物品裝入容量為j的揹包中的最優解。
-
- 貪心演算法:在每一步做出的選擇都是當時看起來的最優選擇,即區域性最優選擇。貪心演算法解決0-1揹包問題有三種策略,在此次專案設計中使用策略二。
- 策略一:每次裝入的都是價值和重量比率最高的,也就是我們常說的價效比最高的;
- 策略二:每次裝入的是當前可選擇的東西中,價值最高的;
- 策略三:每次裝入的是當前可選擇東西中,重量最輕的。
- 回溯演算法:在包含問題的所有可能解得解空間樹中,從根結點出發,按照深度優先的策略進行搜尋,對於解空間樹的某個結點,如果該結點滿足問題的約束條件,則進入該子樹繼續進行搜尋,否則將以該結點為根結點的子樹進行剪枝。
- 解空間:為n個元素所組成的集合的所有子集。
- 剪枝函式:用約束函式在擴充套件結點處減去不滿足約束的子樹,以及用限界函式減去得不到最優解得子樹。
- 約束條件:裝入揹包的物品的總重量不得超過揹包的容量。
- 遺傳演算法:是計算數學中用於解決最優化問題的搜尋演算法,是進化演算法的一種。進化演算法最初是借鑑了達爾文進化生物學中的一些現象而發展起來的,這些現象包括遺傳、突變、自然選擇以及雜交等。
- 編碼:將問題空間轉換為遺傳空間;
- 生成初始種群:隨機生成P個染色體;
- 種群適應度計算:按照確定的適應度函式,計算各個染色體的適應度;
- 選擇:根據染色體適應度,按照選擇運算元進行染色體的選擇;
- 交叉:按照交叉概率對被選擇的染色體進行交叉操作,形成下一代種群;
- 突變:按照突變概率對下一代種群中的個體進行突變操作;
- 返回第3步繼續迭代,直到滿足終止條件。
- 動態規劃演算法:在0-1揹包問題中,物品i或者被裝入揹包,或者不被裝入揹包;wi表示物品的重量,vi表示對應的物品的價值,C表示揹包的總容量,n表示物品的數量。
- 程式碼設計:
- main.py:
-
可正確讀入實驗資料檔案的有效{0-1}KP資料;
-
使用者可以自主選擇資料檔案;
-
使用者能夠自主選擇貪心演算法、動態規劃演算法、回溯演算法、遺傳演算法求解指定{0-1} KP資料的最優解和求解時間(以秒為單位)。
-
任意一組{0-1} KP資料的最優解、求解時間和解向量可儲存為txt檔案。
-
能夠對任意一組{0-1}KP資料繪製以價值重量為橫軸、價值為縱軸的資料散點圖。
-
能夠對任意一組{0-1}KP資料按重量比進行非遞增排序。
-
平臺可以動態嵌入任何一個有效的{0-1}KP 例項求解演算法,並儲存演算法實驗日誌資料。
-
能夠將任意一組{0-1}KP資料存入資料庫。
-
人機互動介面是:通過python實現的GUI介面。
-
- resultD.txt:儲存動態規劃演算法所求解的最優解和求解時間。
- resultG.txt:儲存貪心演算法所求解的最優解和求解時間。
- resultB.txt:儲存回溯演算法所求解的最優解和求解時間。
- resultR.txt:儲存遺傳演算法所求解的最優解和求解時間。
- main.py:
3. 軟體實現:
-
任意檔案的選取以及貪心演算法求解:
-
動態規劃演算法以及回溯演算法的求解,與上面貪心演算法的求解類似,在下拉框裡選擇相應的演算法,之後點解“求最優解”按鈕即可求解。
-
遺傳演算法求解、排序以及日誌資料:
-
連線資料庫,點選“提交到資料庫”按鈕即可將相應的檔案儲存在資料庫:
-
散點圖的繪製情況如下圖所示:
-
資料儲存:
4. 程式碼展示:
- 連線資料庫:
sqlconn = pyodbc.connect(DRIVER='{ODBC Driver 17 for SQL Server}',
SERVER='DESKTOP-S4AIPC9',
DATABASE='SR',
Trusted_Connection='yes'
)
# 連線資料庫
cursor = sqlconn.cursor() # 開啟遊標
cursor.execute("CREATE TABLE hb(\
Weight varchar(32) not null,\
Value varchar(255) not null,)\
") # 執行SQL語句
- 將讀入的資料存入資料表中:
with open("測試資料/" + filename, 'r')as f:
datas = f.readlines()[1:] #第一行預設為總重量和數量,所以不讀入。
for data in datas:
txt = re.split(r'[;,\s]\s*', data)
Weight = txt[0]
Value = txt[1]
cursor.execute("INSERT INTO hb(Weight,Value)VALUES('%s','%s')"%(Weight,Value))
# 呼叫insert方法
print("資料插入完成!")
- 非遞增排序(在實驗二的基礎上進行了修改):
# 計算重量與價值的比值
for i in range(len(a)):
descending = []
F0 = int(a[i][0])
S0 = int(a[i][1])
T0 = F0 / S0
descending.append(T0)
print("非遞增排序前為:")
print(descending)
descending.sort(reverse=True)
print("非遞增排序後為:")
print(descending)
- 遺傳演算法:
#初始化,N為種群規模,n為染色體長度
def init(N,n):
C = []
for i in range(N):
c = []
for j in range(n):
a = np.random.randint(0,2)
c.append(a)
C.append(c)
return C
#評估函式
# x(i)取值為1表示被選中,取值為0表示未被選中
# w(i)表示各個分量的重量,v(i)表示各個分量的價值,w表示最大承受重量
def fitness(C,N,n,W,V,w):
S = []##用於儲存被選中的下標
F = []## 用於存放當前該個體的最大價值
for i in range(N):
s = []
h = 0 # 重量
f = 0 # 價值
for j in range(n):
if C[i][j]==1:
if h+W[j]<=w:
h=h+W[j]
f = f+V[j]
s.append(j)
S.append(s)
F.append(f)
return S,F
#適應值函式,B位返回的種族的基因下標,y為返回的最大值
def best_x(F,S,N):
y = 0
x = 0
B = [0]*N
for i in range(N):
if y<F[i]:
x = i
y = F[x]
B = S[x]
return B,y
#計算比率
def rate(x):
p = [0] * len(x)
s = 0
for i in x:
s += i
for i in range(len(x)):
p[i] = x[i] / s
return p
#選擇
def chose(p, X, m, n):
X1 = X
r = np.random.rand(m)
for i in range(m):
k = 0
for j in range(n):
k = k + p[j]
if r[i] <= k:
X1[i] = X[j]
break
return X1
#交配
def match(X, m, n, p):
r = np.random.rand(m)
k = [0] * m
for i in range(m):
if r[i] < p:
k[i] = 1
u = v = 0
k[0] = k[0] = 0
for i in range(m):
if k[i]:
if k[u] == 0:
u = i
elif k[v] == 0:
v = i
if k[u] and k[v]:
# print(u,v)
q = np.random.randint(n - 1)
# print(q)
for i in range(q + 1, n):
X[u][i], X[v][i] = X[v][i], X[u][i]
k[u] = 0
k[v] = 0
return X
#變異
def vari(X, m, n, p):
for i in range(m):
for j in range(n):
q = np.random.rand()
if q < p:
X[i][j] = np.random.randint(0,2)
return X
- 獲取當前時間:
def get_current_time():
current_time = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time()))
return current_time
- 清屏操作:
def clear():
LB1.delete(0, END)
LB2.delete(0, END)
- 日誌動態列印:
def write_log_to_Text(logmsg):
global LOG_LINE_NUM
current_time = get_current_time()
logmsg_in = str(current_time) + " " + str(logmsg) + "\n" # 換行
if LOG_LINE_NUM <= 7:
log_txt.insert(END, logmsg_in)
LOG_LINE_NUM = LOG_LINE_NUM + 1
else:
log_txt.delete(1.0, 2.0)
log_txt.insert(END, logmsg_in)
- 列表框(用來排序的輸出):
LB1 = Listbox(frameY)
LB2 = Listbox(frameY)
LB1.grid(row=1, column=1, pady=10, rowspan=3)
LB2.grid(row=1, column=3, pady=10, rowspan=3)
5. 程式執行:
程式執行時控制檯的顯示如下所示:
6. commit記錄:
7. 結對照片:
8. 編碼規範:
- 縮排:
- 縮排為4個空格(使用Tab鍵)。
- 行寬:
- 限定為100個字元。
- 常量:
- 常量命名全部大寫,單詞間用下劃線隔開。
- 空行規則:
- 註釋與其上面的程式碼用空行隔開;
- 在每個類聲明後、每個函式定義結束之後都要加空行;
- 在一個函式體內,邏輯上密切相關的語句之間不加空行,其他地方應加空行分隔。
- 變數命名:
- 不以下劃線或美元符號開始或結束;
- 不使用拼音與英文混合的方式;
- 方法名、引數名、成員變數、區域性變數都使用lowerCamelCase(第一個詞的首字母小寫,以及後面每個詞的首字母大寫)風格,遵從駝峰形式。
- 每行最多字元數:
- 單行字元數限制不超過120個,超出需要換行,換行時遵循如下原則:
- 第二行相對第一行縮排 4個空格,從第三行開始,不再繼續縮排;
- 運算子與下文一起換行;
- 方法呼叫的點符號與下文一起換行;
- 在多個引數超長,逗號後進行換行;
- 在括號前不換行。
- 單行字元數限制不超過120個,超出需要換行,換行時遵循如下原則:
- 函式命名:
- 函式名用大寫字母開頭的單詞組合而成。
- 註釋規則:
- 將複雜的註釋放在函式頭;
- 註釋和程式碼同時更新,不用的註釋刪除;
- 註釋與所描述內容進行同樣的縮排;
- 註釋掉的程式碼要配合相關說明;
- 操作符前後空格:
- 賦值運算子、邏輯運算子、加減乘除符號、三目執行符等二元操作符的左右必須加一個空格;
- 一元操作符前後不加空格;
- 中括號、點等這類操作符前後不加空格。
9. PSP展示:
(min) |
(min) |
||
---|---|---|---|
Planning | 計劃 | ||
間,並規劃大致工作步驟 |
|||
Development | 開發 | ||
(包括學習新技術) |
|||
(和同事稽核設計文件) |
|||
(為目前的開發制定合適的規範) |
|||
(自我測試。修改程式碼, 提交修改) |
|||
Reporting | 報告 | ||
Process Improvement Plan |
10. 小結感受:
兩人合作真的能夠帶來1+1>2的效果,在結對程式設計的過程中,需要進行有效的交流,在完成各自負責部分的情況下,對結對方的完成情況會進行檢閱,提出不足之處,之後會加以改進,這樣能夠提供更好的設計質量和程式碼質量。