事情開始於大二上學期,有一門叫作《網站建設與管理》的課程。本人因爲對Web方面比較有興趣,又比較喜歡Python語言的風格,因而就邊學老師教的PHP邊學了Django。正好大一下學期學了算法,對算法和各類OJ都比較有興趣,因此就寫了一個不能評測的手動OJ當期末大做業交了上去。這個暑假開始着手將這個網站建成一個能評測的真OJ。在知乎和Github上爬了一段時間,找到了https://github.com/KIDx/Judger這麼一個開源項目,把這個項目的代碼改動了很多以後終於能夠跑起來了。本身的OJ地址在:http://111.230.131.247/JudgeHome/ios
目前使用以後發現原OJ系統對Presentation Error的斷定過於嚴格,因而這裏想將它改成去除行末空格與文末空行後比對的模式。將程序中與文本比對相關的算法提取出來,獲得了一個用於文本比對的小程序:git
1 #include <iostream> 2 using namespace std; 3 4 void compare_until_nonspace(int &c_std, int &c_usr, FILE *&fd_std, FILE *&fd_usr, int &ret) 5 { 6 while((isspace(c_std)) || (isspace(c_usr))) 7 { 8 if (c_std != c_usr) 9 { 10 if (c_std == '\r' && c_usr == '\n') 11 { 12 c_std = fgetc(fd_std); 13 if (c_std != c_usr) 14 ret = 3; //PRESENTATION ERROR 15 } 16 else 17 { 18 ret = 3; //PRESENTATION ERROR 19 } 20 } 21 if (isspace(c_std)) 22 c_std = fgetc(fd_std); 23 if (isspace(c_usr)) 24 c_usr = fgetc(fd_usr); 25 } 26 } 27 28 int tt_compare_output(string &file_std, string &file_usr) 29 { 30 int ret = 1; //ACCEPTED 31 int c_std, c_usr; 32 FILE *fd_std = fopen(file_std.c_str(), "r"); 33 FILE *fd_usr = fopen(file_usr.c_str(), "r"); 34 35 if (fd_std == NULL) 36 { 37 printf("%s open standard file failed %s\n", strerror(errno), file_std.c_str()); 38 } 39 40 if (!fd_std || !fd_usr) 41 { 42 ret = -1; //RUNTIME ERROR (ABORTION) 43 } 44 else 45 { 46 c_std = fgetc(fd_std); 47 c_usr = fgetc(fd_usr); 48 for(;;) 49 { 50 compare_until_nonspace(c_std, c_usr, fd_std, fd_usr, ret); 51 while(!isspace(c_std) && !isspace(c_usr)) 52 { 53 // LOG_DEBUG("std: %c usr: %c", c_std, c_usr); 54 if (c_std == EOF && c_usr == EOF) 55 goto end; 56 if (c_std != c_usr) 57 { 58 ret = 2; //WRONG ANSWER 59 goto end; 60 } 61 c_std = fgetc(fd_std); 62 c_usr = fgetc(fd_usr); 63 } 64 } 65 } 66 end: 67 if (fd_std) 68 fclose(fd_std); 69 if (fd_usr) 70 fclose(fd_usr); 71 return ret; 72 } 73 74 int main() { 75 string file_std = "std.txt"; 76 string file_usr = "usr.txt"; 77 int res = tt_compare_output(*&file_std, *&file_usr); 78 switch(res) { 79 case -1: cout << "RUNTIME ERROR (ABORTION)" << endl; break; 80 case 1: cout << "ACCEPTED" << endl; break; 81 case 2: cout << "WRONG ANSWER" << endl; break; 82 case 3: cout << "PRESENTATION ERROR" << endl; break; 83 default: cout << "SYSTEM ERROR" << endl; 84 } 85 return 0; 86 }
測試該程序,可知usr.txt與std.txt只要有任何一點微小的差異,程序就會輸出PRESENTATION ERROR。增長了以下一個邏輯簡單的函數以後,就能夠實現先將標準輸出和選手輸出中的行末空格和文末空行都去掉再進行比較了。github
void remove_blank(string file) { //刪除文末空行和行末空格 fstream target_file(file.c_str(), fstream::in | fstream::out); string line; //做爲讀取的一行 string temp; //用做緩存 vector<string> obj; //用於儲存整理完成後的每一行 if(!target_file) { cout << "Can't open target file!" << endl; //上線後改爲LOG_DEBUG } while(getline(target_file, line)) { unsigned long len = line.length(); int count = 0; for(unsigned long i=len-1; i>=0; i--) { int temp_char = line[i]; //用於將字符轉換爲ASCII碼,保存ASCII碼使用 if(isspace(temp_char)) count++; else break; } temp = line.substr(0, len-count); obj.push_back(temp); } int count_lines = 0; //文末空行的數量 for(unsigned long i=obj.size()-1; i>=0; i--) { //去除文末空行 if(obj[i].empty()) count_lines++; else break; } cout << "count_lines: " << count_lines << endl; for(int i=0; i<count_lines; i++) obj.pop_back(); //使用ofstream打開再關閉文件是爲了清空源文件 ofstream empty_file(file.c_str()); empty_file.close(); //從新打開文本文件 fstream target(file.c_str(), fstream::out | fstream::in); if(!target) { cerr << "Can't open file" << endl; //上線後改爲LOG_DEBUG } //寫回源文件 for(unsigned long i=0; i<obj.size(); i++) target << obj[i] << endl; target.close(); }
要注意的是,在Linux的g++編譯器下,fstream、ofstream等的參數須要是c類型的字符串,須要將普通的string類型經過c_str轉換爲c類型的字符串才能夠經過編譯。而本人所使用的mac下clang編譯器並無這種要求,直接使用string類型也能夠經過編譯。這裏debug了好久,必定要當心。算法