前言
前三次pta作業最後一題都是答題判題程式,題目難度逐級提升但寫完後收穫也不小。首先一點是需求分析,不應上來就寫程式碼而是從業務需求整體分析,在確定好程式的層次結構再開始實現相應的功能。
在這三次作業中,將所學的程式設計知識很好地運用,其次,三次作業也同樣考驗我們的自學能力比如正規表示式獲取文字提取所需要的資訊。下面是對題目的具體分析。
三次作業實現
第一次作業
第一次作業主要考察的是熟練運用正規表示式從字串中讀取資訊,其次是善於建立類和物件並實現類之間的關聯來實現題目的業務功能。在題目的下面給出了設計類的建議如下
按照建議提示的類,屬性和方法,對整個程式就有了一個深刻的理解,也體會到了OOP物件導向程式設計的優勢,答題判題程式1難度並不大,但也能很好地體現對知識點的領會與運用,把握類與類之間的設計,遵守物件導向的幾點編碼原則。
我認為難度有以下三點:
1.題目內容較大,並非解決一個問題,而是對整體分析把握,對初學物件導向滿城的初學者來說在思維轉換方面可能有一定困難
2.正規表示式的運用,題目在做需求分析時可知,在接受鍵盤輸入讀取一段字串後要用正規表示式獲取需要的資訊,而並非簡單的if-else語句可以解決,而正規表示式的掌握屬於自學內容,這對學生的自學能力提出了一定要求
3.題目測試點較多,有些測試點需要考慮題目或答案的特殊輸入情況,有的未必會在提示中顯示,需要學生在實現時多重考慮,在符合邏輯的條件下測試複雜的測試用例,判斷程式碼邏輯是否完整
以下是對程式碼原始碼的分析與反思
Question[] q = new Question[number + 1];
String[] ansWer = new String[number + 1]; //答案陣列
boolean[] jude = new boolean[number + 1]; //
for (int i = 1; i <= number; i++) { //讀題目
String s1 = input.nextLine();
int x=0;
if(x==0)
{
Pattern pattern3 = Pattern.compile("#N:.{2}");
Matcher matcher3 = pattern3.matcher(s1);
while(matcher3.find()){
count = Integer.parseInt(matcher3.group(0).replaceAll("#N:","").trim()); //題號
}
x++;
}
if(x==1)
{
Pattern pattern4 = Pattern.compile("#Q:.+#A");
Matcher matcher4 = pattern4.matcher(s1);
while(matcher4.find()){
content = matcher4.group(0).trim(); //題目內容
}
content = content.replaceAll("#Q:","");
content = content.replaceAll("#A","");
x++;
}
if(x==2)
{
Pattern pattern5 = Pattern.compile("#A:.+");
Matcher matcher5 = pattern5.matcher(s1);
while(matcher5.find()){
standAnswer = matcher5.group(0).replaceAll("#A:", "").replaceAll(" ",""); //標準答案
}
x++;
}
q[count] = new Question(count, content, standAnswer);
}
Paper p = new Paper(q, number); //試卷類
for (int j = 1; j <= number; j++) { //讀取寫的答案
String s2 = input.next();
Pattern pattern2 = Pattern.compile("#A:.+");
Matcher matcher2 = pattern2.matcher(s2);
while (matcher2.find()) {
Answer = matcher2.group(0).replaceAll("#A:", "");
}
ansWer[j] = Answer;
jude[j] = p.judgefun2(j, Answer);
}
input.next();
Answer A = new Answer(p, ansWer, jude);
for (int i = 1; i <= number; i++) {
A.Print(i);
}
if(number==1)
{
System.out.print(jude[1]);
}else{
System.out.print(jude[1]);
for(int i=2;i<=number;i++){
System.out.print(" "+jude[i]);
}
}
}
首先是對輸入資料相應寫正規表示式獲取需要的資料,再使用類中的構造方法將資料放入物件中,透過引用物件中的方法來訪問這些資料,再講資料封存在相應的方法中就可以根據業務的需要使用了。我認為重點是正規表示式分組捕獲IDE運用,也是一個難點,在於要精確分析整體資料與你所需要的資料資訊之間的結構關係,從而根據這種關係將重要資料精準提取出來。
這裡尤其要注意正規表示式的分組捕獲方式,分組與不分組在形式上有巨大的區別,我一開始使用分組卻使用了非分組的提取方式導致程式有誤,這樣會導致提取出來的文字和你所需要的不一致,但是這種錯誤在透過除錯可以比較容易發現,跟蹤後發現BUG及時更改正規表示式或者提取式即可,當然我並不全用正規表示式,還結合lspilt分割語法,有的目標資料與整體資料的關係不方便單一使用正則,那麼這時候也可以嘗試使用spilt切割將字串按某一個標準分段再分別處理,這樣在邏輯上更清晰,不容易出錯。
心得
1.此類程式設計題不同以往,資訊量很大,要冷靜分析每個要求和資料的含義。我一開始在寫這道題的時候心情很煩躁,看了幾天都沒看懂題目的意思,甚至懷疑題目的正確性,但在同學的指點下就能豁然開朗,自然就能迎刃而解了。所以在遇到這種幾首問題時千萬不要急於寫程式碼,在沒有真正理會題目的內涵的情況下直接上程式碼就會出現各種錯誤
這就是我沒認真審題的情況下犯的各種錯誤,這樣還是得重新回去讀題理解題意,重新寫程式碼,實在是浪費了大量時間。因此一個最大的收穫就是在遇到專案或題目分析時一定不要心急,冷靜分析專案需求,在完成程式碼之前先要構思好整體的框架以及邏輯的層次分析,在一切準備都就緒的情況下再用程式語言表述清楚基本上就可以了。一定不要心急,切記。
2.程式設計題一定不是優先寫程式碼,對題目的分析與構造的邏輯才是一個程式的精華,寫程式碼不過是將你的分析結果用程式碼語言準確無誤地表現出來,若是程式碼後面出現的邏輯問題可能需要重寫程式碼了。一定不能心浮氣躁,遇到複雜的問題也報保持冷靜和資訊,不畏難,保持冷靜清晰的頭腦。心態真的很重要,做自己的事,不要輕易被外界干擾。
3.基礎知識的重要性。對java的基礎語法一定要牢固,否則寫程式碼非常困難而且錯誤特別多,最難的是你並不知道問題出現在什麼地方。
比如這裡在接受輸入的字串時使用next接受,開始不知道還有一個末尾的空格要處理,導致程式碼都顯示錯誤,原因在於對Scanner類的各種方法之間的區別於特點並不清楚,想到了哪個就用那個,到後面程式碼出錯的時候也build會想到是這裡出了問題,這告訴我們在日常學習中一定要打好基礎,只有打下牢固的基礎才有可能融會貫通。
設計分析
`class Answer {
private Paper paper;
private String[] ans;
private boolean[] judgement;
public Answer() {
}
public Answer(Paper paper, String[] ans, boolean[] judgement) {
this.paper = paper;
this.ans = ans;
this.judgement = judgement;
}
public void judgedfun3(int num) { //判題方法
if (paper.questions[num].getStandardAnswer().equals(ans[num]))
judgement[num] = true;
else
judgement[num] = false;
}
public void Print(int num) {
System.out.println(paper.questions[num].getContent().trim() + "~" + ans[num]);
}
public void store2(int num, String answer) {
ans[num] = answer;
}
public Paper getPaper() {
return paper;
}
public void setPaper(Paper paper) {
this.paper = paper;
}
public String[] getAns() {
return ans;
}
public void setAns(String[] ans) {
this.ans = ans;
}
public boolean[] getJudgement() {
return judgement;
}
public void setJudgement(boolean[] judgement) {
this.judgement = judgement;
}
}`
在類的設計中將屬性都用private修飾使其私有化,再提供getter和setter方法實現封裝,保護資料的安全性;其次,在類中的定義的方法,儘量做到見名知意,且一個方法實現一個功能,符合程式設計的基本原則,提高了程式碼的擴充性和複用性;第二,將實現類和呼叫方法結合氣起來,其重點還是在寫程式碼之前的模式設計階段,只要設計出了合理的結構,加之以程式碼的最佳化和改進;第三,不能僅僅靠題目給定的資訊分析題目,很多情況下即使你看似實現類所有的功能也透過了給出的測試樣例,但程式依然無法透過,其原因是未考慮某些特殊情況,程式碼的邏輯依然存在漏洞,題目不會將所有的測試點一一給你列舉出來,這就需要你在符合事實的前提下適當擴充,用一些邊界值的資料對你的程式碼進行測試,如若發現問題,應該即使改正。這樣保證你的程式的應用場景更加廣闊。
改進意見
for (int j = 1; j <= number; j++) { //讀取寫的答案
String s2 = input.next();
Pattern pattern2 = Pattern.compile("#A+”);
Matcher matcher2 = pattern2.matcher(s2);
while (matcher2.find()){
Answer = matcher2.group(0.replaceAll("#A:", "");
}
ansWer[j] = Answer;
jude[j] = p.judgefun2(j, Answer);
}
為防止main方法中過於冗長導致程式可讀性低,可重新寫一個類,寫input方法和output方法,將接受輸入的語句放在input中,將要輸出的語句放在output方法內;其次,為增強類之間的關聯,可按需求將幾個類進行關聯,增加程式的可維護性和可擴充性;另外,可以使用物件導向的設計原則,將功能拆分成不同的類,每個類負責單一的功能,提高程式碼的複用性和可維護性。同時,可以引入設計模式,如工廠模式、策略模式等,來最佳化程式結構,使程式碼更加清晰和易於理解。最後,建議在程式碼中新增註釋,說明每個方法的作用和實現邏輯,以便他人閱讀和維護。
第二次作業
該次題目在第一次的基礎上進行了改進,增加了試卷和多張答卷以及一些特殊的判斷輸出資訊,同樣考察正規表示式的正確使用,更重要的是對題意的把控以及思路和設計邏輯。首先是題目要求做什麼,其次是怎麼實現,在此基礎上考慮如何設計類之間的結構。
設計分析
參考第一次的提示在此基礎上新增了試卷類,用於存放多張試卷,在正確讀入所有資訊後開始輸出處理。
Set<String> keys = pp.getQuestions().keySet();
for (String key : keys) {
int k = Integer.parseInt(key); //題目編號
flag=1;
int questionScore = pp.getQuestions().get(key); // 獲取題目分數
for(x=0;x<questions.size();x++){
if(questions.get(x).getNum()==k)
break;
}
if(t>=ans.size())
{
flag=0;
break;
}
if (questions.get(x).getStandardAnswer().equals(ans.get(t))) { //
System.out.println(questions.get(x).getContent().trim() + "~" + ans.get(t) + "~" + "true");
score[t] += questionScore; // 答對加分
} else {
System.out.println(questions.get(x).getContent().trim() + "~" + ans.get(t) + "~" + "false");
}
t++;
}
該題設計思路應為先從是建立Questin類陣列用於存放多個Question,Answer類陣列存放多張答卷,Paper類陣列存放多張答卷,將所有讀入資訊透過正規表示式提取後存入陣列中封存,由於題目輸出是按照試卷上的題目順序號輸出的且題目序號與順序號不一定相同,因此用for迴圈辦理每張答卷,再從Paper陣列中找每張答卷對應的試卷,如若試卷不存在,則可直接輸出並找下一張答卷。(由於試卷可能多餘,而多餘的試卷不做處理,因此遍歷答卷開始),而答卷的每道題的答案與試卷上順序號一一對應,因此找到試卷對應的問題後即可將答案與題目的標準答案比較,如果答案正確,就增加這道題的分數。
踩坑心得
1.一開始整個程式碼邏輯有問題,特意去考慮不了特殊情況導致整個程式碼邏輯混亂
if(ans.size()>3&&papers.size()==1){ //只有一張試卷但有兩張答卷
for (String key : keys) {
int k = Integer.parseInt(key); //題目編號
flag=1;
int questionScore = pp.getQuestions().get(key); // 獲取題目分數
if (questions.get(k - 1).getStandardAnswer().equals(ans.get(n))) {
System.out.println(questions.get(k - 1).getContent().trim() + "~" + ans.get(n) + "~" + "true");
score[n] += questionScore; // 答對加分
} else {
System.out.println(questions.get(k - 1).getContent().trim() + "~" + ans.get(n) + "~" + "false");
}
n++;
}
if(flag==0)
System.out.println("answer is null");
System.out.println(score[2] + " " + score[3] + "~" + (score[2] + score[3]));
}
根本就沒必要考慮這種特殊情況,不但把程式碼搞得很複雜,一些正常的輸入和輸出也出現了問題,所以開始的思考顯得尤為重要,作為一名合格的程式設計師,我們更多的時間應該花在構思在整個業務邏輯與邏輯分析上,只有這一點保證不出錯誤,後面寫起程式碼來也會很輕鬆。邏輯的混亂導致了程式執行經常報錯,而要調整邏輯思路又必須重新編排程式碼,前功盡棄,最後導致放棄。
2.
題目沒看仔細,急於做題而忽略了這句話,在類的設計的時候沒有考慮類的設計,知識單純的符合要求,在主方法裡吧所有的業務全部處理,導致程式的耦合性太高,程式可讀性也太差。
class Answer {
private String paperNum;
private List<String> ans;
public Answer(){}
public Answer(String paperNum, List<String> ans) {
this.paperNum = paperNum;
this.ans = ans;
}
public String getPaperNum() {
return paperNum;
}
public void setPaperNum(String paperNum) {
this.paperNum = paperNum;
}
public List<String> getAns() {
return ans;
}
public void setAns(List<String> ans) {
this.ans = ans;
}
}
在設計的類中僅僅是考慮題目的文字增加了部分屬性然後對屬性進行封裝處理,沒有考慮類與類之間的關聯,沒有體現出物件導向程式設計的優點,思維仍然停留在程序導向程式設計。將所有的處理過程全部寫在一起使得程式碼十分冗餘,當程式碼出現問題時難以對
程式碼出現的問題進行除錯。
3.程式碼的層次結構差,這樣程式的可讀性差,在除錯和維護的時候回變得更加困難。如果,我在準備好所有資料的儲存準備一次取資料進行比對輸出操作的時候,當渠道一張答卷但是不知道怎麼在另一個試卷類中找到對應的試卷,只能不斷改變程式碼結構導致程式邏輯十分混亂。實際上這個問題很好解決,就是用類之間的關聯和依賴關係把一個類的物件作為另一個類的屬性,在這裡就可以在答卷類中增加應用個試卷類的paper,這樣一張試卷和對應的答卷就找到了,在答卷類中以試卷和答卷寫一個比較方
法,在主方法中只需呼叫比較方法直接輸出所需資料即可。這樣操作不但邏輯清晰,而且易維護。
改進建議
在寫程式碼的時候儘量不使類獨立,儘可能地使幾個類之間以某種關係串聯起來,這樣在資料處理以及後續的改進提供便利。另外,在寫一個類的時候要理清這個類的邏輯情況,比如這個類應該有哪些屬性,哪些功能的實現要用到這個類,然後從新增方法,保證一個方法只完成一個任務,提高程式碼的複用性。當後續程式執行時如果難以找到想要的資料,首先應該考慮是否可以擴充套件類與類之間的關係,把兩個或者多個類形成關聯關係,而儘可能保證程式碼邏輯的完整性與可操作性,此外,建議在編寫程式碼時遵循設計模式的原則,比如單一職責原則、開閉原則、依賴倒置原則等,這些原則可以幫助我們更好地組織程式碼結構,提高程式碼的可維護性和可擴充套件性。另外,及時進行程式碼的重構也是非常重要的,可以透過不斷地最佳化程式碼結構和邏輯,使程式碼更加清晰易懂。
第三次作業
第三次作業有在第二次題目的基礎上增加了學生資訊以及刪除題目資訊,還要透過正規表示式對接受的字串判斷是否符合格式,還增加了許多異常的判斷和處理,程式的複雜性進一步提高,更加驗證的設計的重要性。
設計分析
基本思路與第二次作業分析基本相同,但是由於在異常處理中答案不存在的優先順序最高,因此應該優先判斷是否有答案,如果沒有答案,則直接輸出然後找下一題。
for (Answer an : answers) { //取一份答卷
boolean paperExists = false;
for (Paper pp : papers) { //找對應試卷
if (pp.getNumber().equals(an.getPaperNum())) { //試卷是pp,答卷是an
paperExists = true;
Set<String> keys = pp.getQuestions().keySet();
int shunXu=1; //題目順序號
for (String key : keys) { //遍歷試卷上的題目
int k = Integer.parseInt(key); //題目編號
if(!an.isAnsExist(shunXu+"")){
System.out.println("answer is null");
shunXu++;
continue;
}
if(!pp.isQuestionExist(k+"")){
System.out.println("non-existent question~0");
shunXu++;
continue;
}
//從題目找到該題看是否有效
int flag=1;
for(Question q:questions){
if(q.getNum()==k) {
if (!q.isValid()){
flag=0;
break;
}
}
}
if(flag==0){
System.out.println("the question " + k + " invalid~0");
shunXu++;
continue;
}
pp.Print(k,pp,shunXu);
shunXu++;
}
if(!an.isStudentExist(an.getIDNum())){
System.out.println(an.getIDNum()+" not found");
continue;
}
這裡把類的關係很好地體現出來了,各種處理的細節也全部放在了各自的類的方法中解決,提高了程式碼的可擴充套件性,解決了以上兩次作業的不足。
改進建議
在類中的關聯關係處理不當,把一個類的陣列物件作為某個類的引數,這個在所有資訊全部獲取完畢後在將完整的陣列傳進該類,並且要在類或具體方法中遍歷陣列找到需要的資料,應該在主方法中找到一一對應的試卷和答卷封存在某個類中,這樣比較處理會更加清楚和簡便。
對三次作業的大總結
1.經過三次作業的練習,對如何物件導向程式設計有了一個清晰的認識,懂得了如何對專案進行分析和梳理,懂得了類之間的多種關係的好處,也明白了編寫程式碼應該遵守的各種原則,逐漸培養了編寫程式碼的風格
2.對專案整體的把握還不夠,不知道設計的哪些類之間要建立一個怎樣的關係,在寫程式碼之前的邏輯還不夠清晰,還需要大量的專案練習思考和總結,最重要的不是程式碼,而是在寫程式碼前的思路和對整個專案整體的設計流程,要多話UML圖,理清關係
3.以後的最後一題的測試點儘量描述的清楚一點,不如第三次的格式錯誤,未給出明確的說明,有空格行不行,有幾個空格算錯。不同的同學討論有不同的意見,最後只能靠帶入測試用例嘗試,太麻煩,給出的測試用例儘量多且全面點,往往都是測試用例都能透過但是測試點一大堆過不去,感覺測試用例沒什麼代表性。