数独棋盘生成器
Github鏈接
作業(yè)鏈接
項(xiàng)目要求
項(xiàng)目需求
利用程序隨機(jī)構(gòu)造出N個已解答的數(shù)獨(dú)棋盤 。
輸入
數(shù)獨(dú)棋盤題目個數(shù)N(0<N<=1000000)
輸出
隨機(jī)生成N個不重復(fù)的已解答完畢的數(shù)獨(dú)棋盤,并輸出到sudoku.txt中,輸出格式見下輸出示例。
[2017.9.4 新增要求] 在生成數(shù)獨(dú)矩陣時,左上角的第一個數(shù)為:(學(xué)號后兩位相加)% 9 + 1。例如學(xué)生A學(xué)號后2位是80,則該數(shù)字為(8+0)% 9 + 1 = 9,那么生成的數(shù)獨(dú)棋盤應(yīng)如下(x表示滿足數(shù)獨(dú)規(guī)則的任意數(shù)字):
測試
測試機(jī)為Windows環(huán)境,所以提交到Github上的項(xiàng)目均需要建立一個名字為BIN的文件夾,里面必須含有可執(zhí)行文件(以exe為后綴)與相關(guān)的依賴庫,請注意以下兩點(diǎn):
- 確保可執(zhí)行文件的名字命名為 sudoku.exe。
- 確保生成的棋盤文件 sudoku.txt 與可執(zhí)行文件在同一目錄下,生成文件時請使用相對路徑!
正確性測試中輸入范圍限制在 1-1000,要求程序在 60 s 內(nèi)給出結(jié)果,超時則認(rèn)定運(yùn)行結(jié)果無效。
性能測試中輸入范圍限制在 10000-1000000,沒有時間的最小要求限制,輸入100w,要求在10分鐘內(nèi)給出結(jié)果。
PSP
| Planning | 計(jì)劃 | 1 | 0.5 |
| ·Estimate | · 估計(jì)這個任務(wù)需要多少時間 | 1 | 0.5 |
| Development | 開發(fā) | 25 | 47 |
| · Analysis | · 需求分析 (包括學(xué)習(xí)新技術(shù)) | 5 | 4 |
| · Design Spec | · 生成設(shè)計(jì)文檔 | 0.5 | 0 |
| · Design Review | · 設(shè)計(jì)復(fù)審 (和同事審核設(shè)計(jì)文檔) | 0 | 0 |
| · Coding Standard | · 代碼規(guī)范 (為目前的開發(fā)制定合適的規(guī)范) | 0 | 0 |
| · Design | · 具體設(shè)計(jì) | 0.5 | 1 |
| · Coding | · 具體編碼 | 16 | 24 |
| · Code Review | · 代碼復(fù)審 | 1 | 3 |
| · Test | · 測試(自我測試,修改代碼,提交修改) | 2 | 15 |
| Reporting | 報(bào)告 | 5 | 7 |
| · Test Report | · 測試報(bào)告 | 2 | 2.5 |
| · Size Measurement | · 計(jì)算工作量 | 0.5 | 1 |
| · Postmortem & Process Improvement Plan | · 事后總結(jié), 并提出過程改進(jìn)計(jì)劃 | 2.5 | 3.5 |
| 合計(jì) | 31 | 54.5 |
(由于是第一次做PSP,最后發(fā)現(xiàn)實(shí)際耗時竟然比預(yù)估耗時多出那么多,安排的時間不是很充裕,到后面差點(diǎn)沒做完,心態(tài)簡直爆炸,還好趕出來了。)
算法及實(shí)現(xiàn)
看到題目,第一反應(yīng)就是暴力解,但是冷靜下來想想,這個顯然是不現(xiàn)實(shí)的。后來看到百度上有提到用深搜和回溯法,大概看了一下,感覺太過麻煩。之后,受到同學(xué)的啟發(fā),打算采用投機(jī)取巧得方法,具體思路為:
先寫一個符合要求的數(shù)獨(dú)棋盤,然后通過各種矩陣變化,變出其他的數(shù)獨(dú)棋盤并輸出我的做法為:先構(gòu)造一個滿足要求的矩陣(由于使用9x9矩陣表示數(shù)獨(dú)棋盤,下面都直接說矩陣),即滿足數(shù)獨(dú)規(guī)則且左上角為5的矩陣(我的尾號為13,按要求左上角需為5),如下
然后通過相關(guān)變化,生成其他矩陣,這里采用替換法、行變換和列變換三種:
替換法:通過一個一維數(shù)組來替換矩陣中各元素的位置,一維數(shù)組的元素為1-9九個數(shù)字,遍歷矩陣,每一個矩陣點(diǎn)的數(shù)(假設(shè)為a)用一維數(shù)組中下標(biāo)為a的元素替換。比如一維數(shù)組array[10]={0,1,2,3,4,5,6,7,8,9}(矩陣元素為1-9,故數(shù)組大小為10,舍棄第一個元素),遍歷矩陣,sudo[0][0]=5,故將該元素?fù)Q為array[5],即5,以此類推。發(fā)現(xiàn)在這種情況下,原矩陣沒有發(fā)生改變,就可以將原矩陣輸出。然后只要不斷改變array[]元素的順序,就可以生成不同的矩陣。理論上有9!種可能,即數(shù)字1-9的全排列。對于全排列,有一個next_permutation(str.begin(), str.end())的庫函數(shù)可以用,使用時要加上#include<algorithm>。值得注意的是,該函數(shù)返回一個布爾值,它的兩個參數(shù)均為字符型,其中str為string型,所以要將字符轉(zhuǎn)化為數(shù)字。另外,為了達(dá)到左上角為5的要求,只要保證array[4]=='5'即可,生成數(shù)組array[]的代碼如下:
替換代碼為:
…… int *ptr = aryGene.toGenerate(); //獲取隨機(jī)數(shù)組for (int i = 0; i < 9; i++){for (int j = 0; j < 9; j++){sudo[i][j] = ptr[sudo[i][j]]; //構(gòu)造新棋盤 } } ……行變換:根據(jù)矩陣的行變換生成新的矩陣,為了保證左上角為5,第一行不進(jìn)行交換,為了使變換后的矩陣符合數(shù)獨(dú)規(guī)則,行變換只在組內(nèi)進(jìn)行,根據(jù)行號為分為012、345、678三組,為了方便,第一組均不進(jìn)行交換。
行變換代碼:
…… for (int row = 3; row < 9; row++) {for (int i = 0; i < 9; i++){swap(sudo[row % 3 + 3][i], sudo[(row + 1) % 3 + 3][i]);}…… }for (int row = 6; row < 12; row++) {for (int i = 0; i < 9; i++){swap(sudo[row % 3 + 6][i], sudo[(row + 1) % 3 + 6][i]);}…… } ……列變換:根據(jù)矩陣的列變換生成新的矩陣,為了保證左上角為5,第一列不進(jìn)行交換,為了使變換后的矩陣符合數(shù)獨(dú)規(guī)則,列變換只在組內(nèi)進(jìn)行,根據(jù)列號為分為012、345、678三組,為了方便,第一組均不進(jìn)行交換。
列變換代碼:
…… for (int line = 3; line < 9; line++) {for (int i = 0; i < 9; i++){swap(sudo[i][line % 3 + 3], sudo[i][(line + 1) % 3 + 3]);}…… }for (int line = 6; line < 12; line++) {for (int i = 0; i < 9; i++){swap(sudo[i][line % 3 + 6], sudo[i][(line + 1) % 3 + 6]);}…… } ……基于這種思路,我的項(xiàng)目分為5個文件,2個.h文件和3個.cpp文件。包含兩個類:ArrayGenerate和SudoCreate。
ArrayGenerate: 包含一個方法int* toGenerate(),用以生成上述一維數(shù)組。
SudoCreate:包含void toCreate(int n)方法,用以生成矩陣,void printSudo()方法,用以打印矩陣。
Main:包含bool isNumber(string str)方法,判斷命令行第三個參數(shù)是否為正整數(shù),從而確定是否為有效命令,對于命令的處理如下:
…… str1 = argv[1]; str2 = argv[2];if (argc != 3 || (argc==3&&(str1 != "-c" || !isNumber(str2)))) //錯誤命令判斷 {cout << "輸入錯誤命令" << endl; }else {sstream.clear();sstream << str2;sstream >> n; //獲取棋盤數(shù)量NsudoCrt.toCreate(n); } ……各類和函數(shù)之間的調(diào)用關(guān)系如下圖所示:
命令行運(yùn)行.exe文件以及文件輸出
命令行運(yùn)行.exe文件
main()函數(shù)主要形式為:
(1) int main( ) ,一般方式。
(2) int main( int argc , char *argv[ ] ),這里采用此種方式。
其參數(shù)argc和argv[ ]用于運(yùn)行時,把命令行參數(shù)傳入主程序,參數(shù)具體含義如下:
int argc:英文名為arguments count(參數(shù)計(jì)數(shù))
運(yùn)行程序傳送給main函數(shù)的命令行參數(shù)總個數(shù),包括可執(zhí)行程序名,其中當(dāng)argc=1時表示只有一個程序名稱,此時存儲在argv[0]中.
char *argv[ ]:英文名為arguments value/vector(參數(shù)值)
字符串?dāng)?shù)組,用來存放指向字符串參數(shù)的指針數(shù)組,每個元素指向一個參數(shù),空格分隔參數(shù),其長度為argc,數(shù)組下標(biāo)從0開始,argv[argc]=NULL。
argv[0] 指向程序運(yùn)行時的全路徑名,這里即為“sudoku.exe”
argv[1] 指向程序在DOS命令中執(zhí)行程序名后的第一個字符串,這里即為“-c”
argv[2] 指向執(zhí)行程序名后的第二個字符串,這里即為矩陣個數(shù)N,需要把字符串轉(zhuǎn)化為整型數(shù),轉(zhuǎn)化方法見上面代碼。
argv[argc] 為NULL。
文件輸出
文件輸出有多種方式,這里采用的是C++文件流輸出:
fstream outfile("sudoku.txt", ios::out); //創(chuàng)建文件流對象,out為覆蓋上次運(yùn)行結(jié)果 if(outfile.is_open()) {for (int i = 0; i < 9; i++){for (int j = 0; j < 9; j++){outfile << sudo[i][j] << ' ';}outfile << endl;}outfile << endl; }性能分析及優(yōu)化
使用vs2017的性能探查器進(jìn)行性能測試,測試報(bào)告結(jié)果如下:
發(fā)現(xiàn),發(fā)現(xiàn)程序運(yùn)行時間過長,需要優(yōu)化,用以輸出矩陣的函數(shù)printSudo調(diào)用次數(shù)特別多,要加快運(yùn)行速度,可以從這個函數(shù)著手,所以返回源代碼去查看。原代碼如下:
void SudoCreate::printSudo() //輸出新棋盤 { fstream outfile("sudoku.txt", ios::app); //創(chuàng)建文件流對象,app為追加到文件末尾if(outfile.is_open()){for (int i = 0; i < 9; i++){for (int j = 0; j < 9; j++){outfile << sudo[i][j] << ' ';}outfile << endl;}outfile << endl;} }從上述代碼可以看出,每一次調(diào)用printSudo函數(shù)時,都會重新創(chuàng)建文件流對象,并且打開文件,100w個矩陣就要打開100w次文件,這樣就導(dǎo)致大量的時間耗在打開文件上,實(shí)際上只要打開一次就好。遂作如下修改:
fstream outfile("sudoku.txt", ios::out); //創(chuàng)建文件流對象,out為覆蓋上次運(yùn)行結(jié)果 void SudoCreate::printSudo() //輸出新棋盤 { //fstream outfile("sudoku.txt", ios::app); //創(chuàng)建文件流對象,app為追加到文件末尾if(outfile.is_open()){for (int i = 0; i < 9; i++){for (int j = 0; j < 9; j++){outfile << sudo[i][j] << ' ';}outfile << endl;}outfile << endl;} }現(xiàn)在重新進(jìn)行測試,結(jié)果如下:
可以看出運(yùn)行速度明顯提升。
單元測試及代碼覆蓋率
單元測試
就寫了一個測試,測試toGenerate函數(shù),該函數(shù)的作用是生成一個一維數(shù)組,數(shù)組元素由數(shù)字1-9組成,且a[5]=5。
測試代碼如下:
#include "stdafx.h" #include "CppUnitTest.h" #include "../Sudoku/ArraysGenerate.h"using namespace Microsoft::VisualStudio::CppUnitTestFramework;namespace UnitTest1 { TEST_CLASS(UnitTest1){public:TEST_METHOD(TestToGenerate){// TODO: 在此輸入測試代碼int *ptr;ArraysGenerate arryGen;ptr = arryGen.toGenerate();Assert::AreEqual(isCorrect(ptr), 1);}int isCorrect(int a[]) //檢查生成數(shù)組是否為以數(shù)字1-9為元素且每個數(shù)字僅出現(xiàn)一次{int cnt[10] = { 0 };int i;for (i = 1; i <= 9; i++){if (a[i] < 1 || a[i]>9 || a[5] != 5)return 0;cnt[a[i]]++;}for (i = 1; i <= 9; i++){if (cnt[i] != 1)break;}if (i == 10)return 1;elsereturn 0;}}; }測試結(jié)果
代碼覆蓋率
代碼覆蓋率是本次作業(yè)最虐的地方,花了1天半都沒做好。一開始用vs2017企業(yè)版的做,總是出不了結(jié)果,截圖如下
百般折騰還是出不來,后來參考助教給的教程(鏈接),好像是出來了,我也不太確定,反正截圖如下:
閱讀《構(gòu)建之法》的收獲及本次作業(yè)總結(jié)
本次作業(yè)總結(jié)為以下幾點(diǎn):
??對于1、4之前接觸過,并沒有什么新的收獲。本次主要收獲來自于《構(gòu)建之法》以及對性能測試、單元測試和代碼覆蓋率的了解。通過閱讀《構(gòu)建之法》,學(xué)會了做PSP,知道一個項(xiàng)目從開始到結(jié)束要經(jīng)歷怎樣的過程,了解什么是性能測試和單元測試。
??性能測試對于程序的重要性不言而喻,但為什么要進(jìn)行單元測試呢?最后能出結(jié)果不就行了嗎?我的理解是:單元測試是對某個具體的單元(一個類或是一個方法)進(jìn)行正確性測試,單元測試通過可以增加自己的信心。誠然,在簡單的程序中,單元測試的作用并不明顯,比如寫一個計(jì)算a+b的程序,單元測試就沒有太大的必要。但是,如果是一個大項(xiàng)目,需要大量的類和方法,那么如果不進(jìn)行單元測試,結(jié)果出錯了又該到哪里去找呢?又或是自己是否曾有過不知道自己寫的方法到底能不能實(shí)現(xiàn)自己的要求的困擾呢?如果在寫程序的時候進(jìn)行單元測試,測試通過了,我們就有足夠的信心往下繼續(xù)寫,當(dāng)結(jié)果出錯時,那些測試通過的部分顯然不是查錯的重點(diǎn),這樣的話,單元測試的作用就特別明顯了。
??在進(jìn)行單元測試的過程中,遇到了一個問題,就是當(dāng)我在vs2017(企業(yè)版)寫完測試代碼,要進(jìn)行測試時,出現(xiàn)“無法解析的外部符號”的錯誤,在網(wǎng)上搜索修改的方法都沒有用,后來看到一篇博客(鏈接),發(fā)現(xiàn)上面出現(xiàn)跟我我一模一樣的錯誤,就按照上面的改,還是沒成功。折騰了一早上,后來把方法的實(shí)現(xiàn)放到了頭文件里,即在頭文件中同時給出方法的聲明和實(shí)現(xiàn),這樣就通過了測試。雖然通過了測試,但還是不明白為什么會這樣。明明方法的實(shí)現(xiàn)是可以放到.cpp文件里,但為什么單元測試時就是不行。而且為什么別人可以,我的就有問題,難道這還看人品?難受。
??另外,對于單元測試還有另外一個疑問:以前在編程的時候,對于某個函數(shù)如果不確定它是否能得到想要的輸出,會用另一個編譯器單獨(dú)寫一下那個函數(shù)來進(jìn)行測試,這算不算是另類的單元測試呢?如果是,那么這兩種單元測試方法的優(yōu)劣性如何。個人覺得,自己以前得方法比較簡單,只要加上一些輸入輸出語句然后把函數(shù)復(fù)制粘貼就好了,但是vs的單元測試還要進(jìn)行各種操作,甚是麻煩,而且還可能出現(xiàn)各種問題,所以到底該如何去看待單元測試呢?
??關(guān)于vs的單元測試代碼,最后都要用到一個驗(yàn)證函數(shù)Assert.AreEqual,這里給出這個函數(shù)的重載列表:
??最后附加一則求組隊(duì)告示:
《構(gòu)建之法》第三章提到團(tuán)隊(duì)對個人的期望,具體如下:
1.交流:能有效的和其他隊(duì)員交流,從大的技術(shù)方向,到看似微小的問題。
2.說到做到:就像上面說的“按時交付”
3.接受團(tuán)隊(duì)賦予角色要求工作:團(tuán)隊(duì)要完成任務(wù),有很多事情要做,是否能接受不同的任務(wù)并高質(zhì)量完成?
4.全力投入團(tuán)隊(duì)的活動:就像一些評審會議,代碼復(fù)審,都要全力以赴地參加,而不是游離于團(tuán)隊(duì)之外。
5.按照團(tuán)隊(duì)流程的要求工作:團(tuán)隊(duì)有自己的流程(見“團(tuán)隊(duì)和流程”一章),個人的能力即使很強(qiáng),也要按照團(tuán)隊(duì)制定的流程工作,而不要認(rèn)為自己不受流程的約束
6.準(zhǔn)備:在開會討論之前,開始一個新功能之前,一個新項(xiàng)目之前,都要做好準(zhǔn)備工作。
7.理性地工作:軟件開發(fā)有很多個人的、感情驅(qū)動的因素,但是一個成熟的團(tuán)隊(duì)成員必須從事實(shí)和數(shù)據(jù)出發(fā),按照流程,理性地工作。很多人認(rèn)為自己需要靈感和激情,才能為宏大的目標(biāo)奮斗,才能成為專業(yè)人士,著名的藝術(shù)家說Chuck Close說:我總覺得靈感是屬于業(yè)余愛好者的,我們職業(yè)人士只是每天持續(xù)工作。今天你繼續(xù)昨天的工作,明天你繼續(xù)今天的工作,最終你會有所成就。
首先聲明以上要求我都可以做到,然后求組隊(duì)。
轉(zhuǎn)載于:https://www.cnblogs.com/jiuweilinghu/p/7499991.html
總結(jié)
- 上一篇: MacOS13系统升级动态壁纸无法安装解
- 下一篇: 电力电子仿真软件---PLECS