BLOG-1

22207109-李世杰發表於2024-10-25

pta1-3次大作業部落格總結

一、前言

​ 在前三次的PTA大作業中,我們依次完成了答題判題程式1至3。這三個程式彼此緊密相連,層層遞進,每一次的完成都是在前一次基礎上的持續修改、完善與最佳化。這三次大作業的順利進行要求我們對類、Map、List、正規表示式等知識點有深入的理解與熟練的運用。從一開始,我們就需要構建一個合理的結構,因為這三個題目是緊密相扣的。

1.答題判題程式1

​ 答題判題程式1的任務是模擬一個小型的測試系統,要求輸入題目資訊和答題資訊,並根據題目資訊中的標準答案來判斷答題的結果。這個過程不僅需要準確地解析和處理輸入資料,還要求我們設計合理的演算法來評估答案的正確性。

​ 為了順利完成這個題目,我們必須熟練掌握多種知識點,包括類的使用、字串的分割、以及集合框架中的Map和List等。該題目的題量和難度均為中等,適合用來鍛鍊我們的程式設計能力和邏輯思維。

​ 對我而言,作為一名從未接觸過物件導向程式設計的初學者,這個任務在最初接手時確實顯得有些棘手。面對物件導向的概念、類的設計以及如何高效地儲存和處理資料,我需要花費額外的時間進行理解和練習。然而,經過不斷的努力和嘗試,我逐漸掌握了這些知識,併成功地完成了這個程式。

2.答題判題程式2

​ 答題判題程式2相比於答題判題程式1新增了試卷資訊模組。為順利完成此題,我們同樣需要熟練掌握類的使用、字串的分割操作,以及集合框架中的Map和List等。這個題目的題量以及難度都屬於中等。

3.答題判題程式3

​ 答題判題程式3在答題判題程式2的基礎上新增了學生資訊管理和題目資訊刪除功能。除了前面提到的知識點外,要完成此題目還需我們熟練掌握正規表示式,以判斷輸入格式的正確性。相比前兩次PTA任務,第三次因新增了更多輸入輸出判斷,題量和難度均大幅提升,完成此任務需要投入較多的時間和精力。

二、設計分析

1.答題判題程式1

​ 本題目要求設計一個答題程式,用於模擬小型測試。程式需輸入題目資訊和使用者答題資訊,並依據題目資訊中的標準答案判定答題結果。

為實現此功能,我們設計了四個核心類:

  1. 題目類:用於封裝每道題目的資訊,包括題目描述、選項(如有)、標準答案等內容,便於統一儲存和訪問。
  2. 試卷類:用於管理所有題目例項,將題目資訊集中封裝,方便對整個題目集合進行操作。
  3. 答卷類:用於封裝使用者的答題資訊,將使用者的每個答案與對應的標準答案比對,以便進行評判和記錄。
  4. 主類:作為程式的入口,負責接收並解析輸入的題目資訊和答題資訊,呼叫上述各類的功能,實現題目載入、答題記錄、結果計算等功能。

透過這四個類的協作,程式可以有效地管理題目、記錄答案,並計算出測試結果,使整個答題流程清晰流暢。

類圖

image-20241025154312482

時序圖

image-20241025155630092

1.題目類

該類用於表示單個題目,包含以下屬性:題目編號、題目內容和標準答案。類中定義了多種方法,支援基本的操作,例如設定和獲取題目內容。此外,提供了答案校驗方法,用於判斷使用者的回答是否正確。

//判斷答案是否正確
    public boolean isCorrect(String answer) {
        return standardAnswer.trim().equals(answer.trim());
    }

2.試卷類

該類用於表示一組題目集合,使用 Map 結構來儲存多個題目。類中包含了一些基礎操作方法,如新增題目、刪除題目、查詢題目等。

3.答卷類

該類用於儲存使用者輸入的答案、試卷資訊及判題結果。類中包含以下主要屬性:paper(試卷類例項)、Map 型別的答案集合、以及 Map 型別的判題結果集合。主要操作包括儲存答案、進行判題等功能,其中判題方法是該類的核心功能,用於對比使用者答案與正確答案並生成結果。

判題方法首先透過 for 迴圈遍歷試卷中的每個問題,使用 paper.getAllQuestions() 獲取所有問題列表並依次進行評估。對於每個問題,從 answers 中提取使用者的回答,並檢查是否為空 (answer != null)。最終,所有題目的評估結果都會記錄在 results 集合中。

// 判題方法
    public void evaluateAnswers() {
        for (Question question : paper.getAllQuestions()) {
            String answer = answers.get(question.getQuestionNumber());
            if (answer != null) {
                boolean result = question.isCorrect(answer);
                results.put(question.getQuestionNumber()-1, String.valueOf(result));
            } else {
                results.put(question.getQuestionNumber()-1,"false");
            }
        }
    }
}

4.主類

該類綜合呼叫了前面提到的三個類,用於實現題目的完整功能。其主要職責是接收使用者輸入,解析題目資訊和答卷資料,並將其傳遞給相關模組進行處理,從而完成對題目的管理和答卷評判等任務。

2.答題判題程式2

答題判題程式2在答題判題程式1的基礎上新增了試卷資訊,並且這三種資訊可能會被打亂順序混合輸入。

為實現此功能,我在答題判題程式1的基礎上進行了以下改進:

  1. 試卷類:增加了試卷編號、分數和總分等屬性。
  2. 答卷類:新增了試卷有效性判斷標誌,並調整了答案評估函式。
  3. 主類:顯著最佳化了輸入解析程式碼,以支援資訊的亂序處理。

類圖

image-20241025164631353

時序圖

image-20241025163750069

1.試卷類

增加了試卷編號、分數和總分等屬性及其相關基本操作。

2.答卷類

對其核心函式答案評估進行了大幅修改

相比於答題判題程式1,答題判題程式2的答案評估函式進行了一下修改:

  1. 增加了試卷有效性檢查:在評估答案前,新增了對試卷有效性的判斷邏輯,如果試卷無效(getEffectiveness() == -1),直接輸出提示並結束評估。
  2. 處理了答案可能為空的情況:如果答案為空,會記錄相應的輸出內容,且分數不會增加。
  3. 詳細的輸出和分數記錄:對每道題的評估結果進行了詳細輸出,包括題目、答案和是否正確的標記(question.getQuestion() + "~" + answer + "~" + isCorrect)。同時,還將每道題的分數存入resultScores列表,並最終輸出各題分數及總分。
  4. 計算總分:透過遍歷所有題目,計算並累積每道題的分數,最終輸出總分。
  5. 逐題輸出結果與分數格式化輸出:輸出每道題的評估結果以及分數列表,並使用格式化輸出的方式將各題分數和總分進行組合顯示。

3.主類

相比於答題判題程式1,答題判題程式2的主類最佳化完善了使用者輸入解析部分,進行了以下改進:

  1. 支援多個試卷和答卷的管理:透過papersanswerSheets兩個集合來管理多個試卷和答卷。
  2. 輸入格式更加靈活:允許透過特定識別符號(如#N:, #T:, #S:)來識別輸入的不同型別(題目、試卷、答卷)。
  3. 增加了總分校驗:建立試卷時,校驗每張試卷的總分是否達到100分,如果不足,則會輸出警告。
  4. 處理無效答卷:透過設定Effectiveness欄位來標識答卷的有效性。如果試卷不存在或無效,程式會提示“試卷編號不存在”。
  5. 程式碼結構更清晰:改進的程式碼分離了題目解析、試卷解析和答卷解析的邏輯,使每個解析模組更加獨立,邏輯更清晰。

3.答題判題程式3

答題判題程式3相比於前兩次難度大幅提升,相比於前兩次,第三次,第三次新增了學生資訊、刪除資訊,以及大量格式判斷。

為完成該題目,在前兩次的基礎上,進行了以下修改:

  1. 試卷類:新加了刪除題目函式。
  2. 答卷類:對評估函式又一次進行了完善以及最佳化。
  3. 學生類:新增了學生類,儲存學生資訊。
  4. 題庫類:新增了題庫類,儲存題目。
  5. 匹配類:新增匹配類,判斷字串是否合法。

類圖

image-20241025171739639

時序圖

image-20241025183246155

1.答卷類

又一次對評估答案函式進行了進一步完善與最佳化:

  1. 增強了對試卷和題庫的有效性檢查:在evaluateAnswers方法中增加了對questionBankpaper物件的空值檢查,確保這些物件已正確初始化,避免了可能的空指標異常。
  2. 細化的評分處理:在迴圈中根據不同情況將各題得分結果儲存在resultScores中,不僅計算總分數,還將各題的分數單獨儲存,便於後續分析。
  3. 改進輸出結構:在輸出總成績時,新增了學生的基本資訊(學號和姓名)的判定。
  4. 支援非標準情況的處理: 加入了對題號不存在、題目被刪除、題目不在試卷中的判斷條件。針對無效試卷的情況,設定了輸出“non-existent question~0”和“the question X invalid~0”。

2.學生類

定義了屬性學號姓名及其基本操作。

3.匹配類

匹配類中定義了isVaild方法,使用正規表示式判斷輸入是否合法。

    public boolean ifvaild(String line) {
        // 匹配題目
        if (Pattern.compile("^#N:\\d+ #Q:.+ #A:[\\w\\d]+.*$").matcher(line).matches()) {
            return true;
        }
        // 匹配試卷
        else if (Pattern.compile("^#T:\\d+(\\s\\d+-[\\d\\w]+)*\\s*$").matcher(line).matches()) {
            return true;
        }
        // 匹配答卷
        else if (Pattern.compile("#S:(\\d+) (\\d+) (.+)").matcher(line).matches()) {
            return true;
        }
        // 匹配學生
        else if (Pattern.compile("^#X:\\d{8} \\w+(-\\d{8} \\w+)*$").matcher(line).matches()) {
            return true;
        }
        // 匹配刪除題目
        else if (Pattern.compile("^#D:N-\\d+$").matcher(line).matches()) {
            return true;
        }
        // 如果都不匹配,則返回false
        return false;
    }

三、踩坑心得

  1. 在編寫答題判題程式2時,我始終無法透過答卷有效性判斷的測試點,於是加了一個屬性effectiveness來判斷答卷有效性。
private int effectiveness;
if (getEffectiveness() == -1) {
            System.out.println("The test paper number does not exist");
            return; // 如果有效性為 -1,則直接返回,不再評估答案
        }
  1. 在編寫答題判題程式1和2時,由於對如何匹配正確的字串格式不夠清晰,我採用了相對複雜的方法來實現這一功能,透過使用substring以及split以及迴圈來分解輸入的字元。

  2. 由於在編寫答題判題程式1和2時完全沒有考慮輸入格式的問題,因此在開發答題判題程式3時,出現了許多測試點返回非零值的情況。

  3. 在編寫答題判題程式3時,無效試卷的引用資訊與輸出的題目資訊之間持續發生衝突,這導致我始終無法透過測試點4。

            // 題號是否在題庫中存在
            else if (!questionBank.getQuestionBank().containsKey(questionPaper.getQuestionNumber())
                    || question.getQuestion().equals("-1")) {
                output = "non-existent question~0";
                score = 0;
            }
            // 題目是否被刪除
            else if (question.getQuestionNumber() == paper.getDeleteNum()) {
                score = paper.getQuestionScore(question.getQuestionNumber());
                output = "the question " + question.getQuestionNumber() + " invalid~0";
            }
  1. 在編寫答題判題程式3時,刪除題目時直接將其移除,導致無法將被刪除題目的分數賦為0。為了解決這個問題,我首先將所有題目的分數初始化為0,隨後在遍歷時再將未刪除題目的分數重新賦值。
        // 試卷所有分數置0
        for (int score : paper.getScoreMap().values()) {
            score = 0;
            resultScores.put(j, score);
            j++;
        }
  1. 由於答題判題程式1和2的難度相對較低,設計上相對簡單,整體結構僅包含三個類,功能劃分也並不十分細緻。這種設計使得答卷類承載了過多的內容,導致在編寫答題判題程式3時遇到了諸多挑戰。隨著第三次題量的大幅增加,程式需要處理的格式判斷變得複雜,新增的內容和類也相應增多,修改和調整的地方也非常多。為了解決這些問題,不得不花費大量的時間進行重新設計和最佳化,以確保系統能夠高效、準確地處理更多的題目和複雜的判斷邏輯。

  2. 答題判題程式1和2的程式碼註釋相對較少,這給後續的開發工作帶來了困難。在編寫答題判題程式3時,我時常需要回顧之前的程式碼,以便更好地理解某些功能塊的具體作用和實現邏輯。缺乏詳盡的註釋使得我在閱讀程式碼時無法迅速捕捉到每一段程式碼的意圖,增加了開發過程中的時間成本。

四、改進建議

  1. 確保註釋儘可能詳細,以便於後續的閱讀和修改。在程式碼中使用清晰的註釋能夠幫助自己快速理解程式碼的功能、邏輯和設計意圖。每個函式、變數和重要的邏輯分支都應有相應的解釋,以便於自己能夠輕鬆跟蹤和維護程式碼,方便後續的多次開發。
  2. 在設計類時,應儘量追求精細化,切忌將所有功能和屬性堆砌在一個類中。這種做法不僅會導致類的複雜性增加,還會使得後續的開發和維護變得極為困難。一個過於龐大的類難以理解和測試,容易引發錯誤。合理劃分職責、建立小而專注的類,可以提升程式碼的可讀性和可維護性,使得每個類都承擔特定的功能。這樣一來,修改或擴充套件某一部分的功能時,只需關注相關的類,提高了開發效率。
  3. 掌握正規表示式的使用非常重要,它能夠顯著提高程式設計效率並使程式碼更加簡潔,能夠高效地處理字串的搜尋、匹配和替換等任務。透過正確運用正規表示式,可以減少冗餘程式碼,從而簡化邏輯和最佳化程式效能。
  4. 紮實基礎知識是編寫高質量程式碼的關鍵,特別是在資料結構的應用上。正確和合理地使用如 Map、List 和佇列等資料結構,可以極大地提升程式碼的結構性和可維護性。掌握這些基本的資料結構,不僅能幫助你解決實際問題,還能使你的程式碼邏輯更加清晰,結構更加合理,從而為後續的開發和維護打下堅實的基礎。
  5. 在編寫程式碼時,應充分考慮各種可能的情況,尤其是要防止異常值的輸入。這種前瞻性的思維能夠有效提高程式碼的健壯性和穩定性。處理輸入時,進行嚴格的驗證和篩選是至關重要的,以確保程式能夠正確處理使用者輸入的各種形式。

五、總結

在此次這三次大作業中,我們逐步完成了三個答題判題程式的開發,每個程式都是在前一個的基礎上進行了改進與擴充套件。這幾次大作業不僅加深了我對物件導向程式設計、資料結構和演算法設計等知識的理解,還提升了我解決實際問題的能力。

在第一個答題判題程式中,要求設計一個基本的測試系統,輸入題目資訊和答題資訊,並透過標準答案進行判斷。這一過程要求我們熟練掌握類的設計、字串的處理及集合框架的運用。作為一個初學者,我在學習如何設計類及其互動時遇到了一些困難,但在不斷的實踐中,我逐漸掌握了相關的知識併成功實現了程式功能。

第二個程式在第一程式的基礎上增加了試卷資訊模組,處理複雜輸入的需求。我在設計時,增加了試卷編號、分數等屬性,並最佳化了輸入解析的程式碼,以支援資訊的亂序輸入。這一改進不僅提升了程式的靈活性,也讓我對程式設計的模組化有了更深的理解。

進入到第三個程式時,我面臨了更大的挑戰。新增的學生資訊管理、題目資訊刪除功能以及格式判斷使得第三次大作業的複雜性大幅提升。在設計中,我建立了多個新類以分擔各自的功能。此外,我運用正規表示式對輸入格式進行驗證,確保程式在接收資料時的嚴謹性。這一過程讓我深刻體會到基礎知識的重要性,特別是在資料結構和正規表示式的運用上。

透過這三次大作業,我不僅提升了程式設計技能,還培養了分析問題和解決問題的能力。遇到的諸多挑戰,比如如何有效管理輸入輸出、如何進行資料的有效性檢查、如何處理物件之間的關係,都促使我不斷反思和調整我的設計思路。在此過程中,我也意識到註釋的重要性,合理的註釋不僅能幫助我理解程式碼,也為他人閱讀和維護程式碼提供了便利。這次三次大作業讓我在實踐中不斷學習和成長,理解了軟體開發過程中的重要環節,如設計、實現、測試和維護。未來,我會繼續加強這些方面的學習,尤其是提高程式碼的可讀性與可維護性,力求在編寫高質量程式碼的道路上不斷前行。