答題判題程式1-3總結

李得龙發表於2024-10-26

答題判題程式題目集1-3-總結性部落格

答題判題程式一

一、前言

在“答題判題程式-1”中,我們主要實現了一個小型答題判題系統,用於模擬自動化的答題和判分過程。該系統涵蓋了輸入題目資訊、接收使用者答題資訊以及根據標準答案進行判分的功能。該題目集主要考查以下幾個方面的程式設計能力:

  1. 物件導向程式設計:透過封裝題目、試卷和答卷等核心類,提高了程式碼的結構化和可維護性。
  2. 字串處理:使用正規表示式解析題目資訊和答題內容,考察字串解析與正則匹配的應用。
  3. 資料結構:應用了HashMap儲存題目資訊和使用者答案,使得題目和答案的管理更加高效。
  4. 輸入輸出處理:需要對控制檯輸入的多種格式資訊進行解析和處理,輸出結果準確且格式規範。

接下來,我們將從設計與分析、採坑心得、改進建議和總結四個方面深入探討該題目集的開發過程和心得。

二、設計與分析

題目集的實現可以拆分為三個主要模組:題目類Question、試卷類Exam、答卷類AnswerSheet,以及主程式中的控制邏輯。我們將結合SourceMonitor的生成報表和PowerDesigner的類圖,對每個模組進行詳細分析。

  1. Question類:封裝題目內容和標準答案

Question類包含題號、題目內容和標準答案三個屬性,設計目的是為每道題目提供封裝的資料結構。核心方法如下:

  • isCorrect():用來比對使用者的答案和標準答案,透過trim()確保答案字串的準確性。
  • getContent()getNumber():分別用於獲取題目的內容和題號,便於後續在試卷和答卷中引用題目。

設計分析:該類的封裝性強,資料保護到位,使得題目內容和標準答案僅在構造時設定,避免了在答題流程中的不必要修改。

  1. Exam類:管理和組織試卷

Exam類主要功能是容納題目,提供新增和檢索題目的方法。

  • addQuestion():根據題號將題目儲存到HashMap中,便於快速查詢。
  • getQuestion():透過題號檢索題目,確保題目在任意順序輸入時依然可以準確讀取。
  • getQuestionCount():獲取題目總數量,為後續答卷處理提供參考。

設計分析:Exam類的設計思路是將題目按題號組織為鍵值對,這種方式極大提高了題目檢索速度和穩定性。透過SourceMonitor分析顯示,Exam類程式碼行數精簡,但在複雜度上表現良好,避免了迴圈遍歷的開銷。

  1. AnswerSheet類:管理答題資訊及判分結果

AnswerSheet類是該系統的核心模組,負責儲存使用者答案並進行判題。

  • saveAnswer():按題號儲存使用者的答案。
  • evaluateAnswer()evaluateAllAnswers():分別用於判定單題和所有題的答案是否正確,並儲存判定結果。
  • printResults():格式化輸出使用者的答題情況和判題結果。

設計分析:該類實現了題號和答案的對映關係及判題邏輯,並透過printResults()實現結果輸出。類圖顯示該類依賴於Exam類,這種依賴關係確保答題資訊準確無誤地與試卷題目資訊同步,SourceMonitor報表顯示其複雜度適中,但實現了較高的功能整合。

  1. 主程式(Main類)

主程式中包含題目解析方法parseQuestion()和使用者答案解析方法saveAnswers(),並將解析的內容儲存到ExamAnswerSheet中。控制流程按題目輸入、使用者答題、結果判定及輸出四個步驟依次完成。

Main方法在整個程式中承擔著主要的控制和協調任務,它從輸入獲取資訊,並透過一系列類和方法呼叫實現答題和判題功能。下面是對Main方法的分步分析:

  1. 建立試卷物件
    // 建立試卷
    Exam exam = new Exam();
    Main方法首先建立了一個Exam物件,該物件用於儲存所有的Question物件(題目)。在Exam類中,題目資訊被儲存在一個HashMap<Integer, Question>中,以便根據題號快速訪問和管理題目。

  2. 讀取題目數量

// 讀取題目數量
int numQuestions = Integer.parseInt(scanner.nextLine().trim());
接著,Main方法讀取題目數量,並將其轉換為整數。這個數字用於控制題目的輸入迴圈,確保僅讀取規定數量的題目資訊。

  1. 解析和新增題目
    // 讀取題目資訊並新增到試卷
    for (int i = 0; i < numQuestions; i++) {
    String input = scanner.nextLine().trim();
    Question question = parseQuestion(input);
    if (question != null) {
    exam.addQuestion(question);
    } else {
    System.out.println("題目格式錯誤,跳過該題。");
    }
    }

在這一部分,Main方法迴圈讀取題目資訊。每一行題目資訊透過parseQuestion方法解析為Question物件。parseQuestion方法使用正規表示式來匹配題目字串,若成功匹配並解析,則返回一個Question物件並新增到exam物件中;否則輸出提示資訊“題目格式錯誤,跳過該題”。這個設計既保證了題目資訊的準確性,也避免了不符合格式的輸入導致程式崩潰。

  1. 建立答卷物件

// 建立答卷
AnswerSheet answerSheet = new AnswerSheet(exam);
題目解析和儲存完成後,Main方法建立一個AnswerSheet物件(答卷)。AnswerSheet接受exam物件作為引數,從而能夠根據題號訪問試卷中的題目,並將使用者的答案與標準答案進行對比。

  1. 讀取使用者答案

// 讀取使用者答案
String answersLine = scanner.nextLine().trim();
saveAnswers(answersLine, answerSheet);

接下來,Main方法讀取使用者的答題資訊,並透過saveAnswers方法將答案儲存到answerSheet物件中。saveAnswers方法將使用者輸入的答案行分割成多個答案,並將這些答案逐一儲存到answerSheet物件的HashMap中,其中鍵為題號,值為使用者的答案內容。這樣可以確保答案的管理與題目一一對應。

  1. 判題並輸出結果

檢查所有答案並列印結果
answerSheet.evaluateAllAnswers();
answerSheet.printResults();

最後,Main方法呼叫了answerSheetevaluateAllAnswersprintResults方法,完成對使用者答案的判題及結果輸出。

  • evaluateAllAnswers方法遍歷使用者提交的所有答案,呼叫Question類的isCorrect方法逐題進行判斷,並將結果儲存在AnswerSheet中。
  • printResults方法負責格式化輸出每道題的題目內容、使用者的答案和判題結果。它首先輸出每道題的題目內容及使用者答案,隨後輸出判題結果,符合題目要求的格式。
  1. 關閉輸入流
    scanner.close();
    最後,關閉輸入流資源,防止資源洩漏。這一行程式碼雖然簡單,但在長時間執行的應用程式中,資源管理是一項不可忽視的細節。

程式碼優缺點分析
優點

  1. 結構清晰,模組化程度高:Main方法將各個任務分解為多個類和方法,增強了程式碼的可讀性和可維護性。
  2. 魯棒性:透過parseQuestion方法對題目資訊格式進行校驗,保證輸入的有效性並防止格式錯誤的題目引發程式崩潰。
  3. 封裝性:透過ExamQuestionAnswerSheet等類的封裝,Main方法不需要關注具體的題目儲存和答案對比的細節,只需協調不同類的功能即可。
    缺點
    1.缺少異常處理:對於可能發生的輸入異常(如輸入的題目數量非整數、答案數量與題目數量不匹配等),沒有顯式的異常處理機制。
  4. 靈活性較低:目前程式僅適用於題目數固定的情況,若使用者在輸入答案時答題數量與題目數量不一致,程式可能會產生錯誤結果。
  5. 未考慮邊界情況:例如題目數量為零、輸入包含特殊字元等情況未處理。

答題判題程式1類圖展示:主程式依賴於ExamAnswerSheet,類間依賴關係合理,且透過正規表示式和HashMap,有效提高了解析準確性和資料組織效率。

答題判題程式1時序圖展示

時序圖說明
使用者輸入階段:
使用者輸入題目數量、題目資訊和答案資訊。Main類負責解析這些輸入。

題目解析與儲存階段:
Main類呼叫parseQuestion()解析每一道題目,並使用Exam物件將解析出的Question物件新增到試卷中。

答題資訊儲存階段:
Main類將使用者的答案儲存到AnswerSheet中,儲存時確保按題號順序對應答案。

判題階段:
AnswerSheet的evaluateAllAnswers()方法依次判題,每次呼叫evaluateAnswer()來比對使用者答案,使用Question類的isCorrect()方法判定結果。

結果輸出階段:
AnswerSheet的printResults()方法按要求格式化輸出題目內容、使用者答案和判題結果。
透過該時序圖,程式的流程變得清晰,尤其是輸入、解析、判題、和輸出四個核心步驟的互動。

採坑心得

在原始碼提交和測試過程中,遇到了一些典型問題及相關解決方法:

  1. 輸入格式解析問題

問題:題目和答案的輸入格式較複雜,初次提交時由於正規表示式不夠精準,導致題目解析錯誤。

解決:對正規表示式進行調整,採用非貪婪模式來處理題目內容的多空格問題,並在parseQuestion()中加上容錯邏輯。例如:

Pattern pattern = Pattern.compile("\s#N:\s(\d+)\s#Q:\s(.+?)\s#A:\s(.+)\s*");

  1. 資料儲存與檢索問題

問題:在儲存題目和答案時,題號的次序和題目次序不一致,導致輸出錯位。

解決:在ExamAnswerSheet中統一按題號為鍵儲存,使檢索按題號排序一致,輸出結果準確。

  1. 使用者答案格式化問題

問題:初期程式碼中使用者答案儲存後未處理多餘空格,導致判題結果不準確。

解決:在儲存答案時使用trim(),去除空格以確保判題的準確性。同時,判題結果統一格式化為truefalse,便於輸出一致。

改進建議

  1. 使用資料結構最佳化儲存方式:當前系統採用HashMap儲存題目,儘管查詢效率高,但在輸出時還需額外排序。可以考慮使用TreeMap,自動按題號排序,避免額外處理。

  2. 提高使用者輸入解析的容錯性:當前正規表示式處理多行輸入時較脆弱,建議加入錯誤提示和重新輸入機制,確保使用者輸入符合格式要求。透過捕獲不符合格式的輸入並給出提示,可進一步提高使用者體驗。

  3. 最佳化判題邏輯:AnswerSheet類中所有題目均需判分輸出,但無答題記錄的題目仍會輸出預設判分結果。可以在輸出前篩選已回答的題目,提升判題結果的準確性。

  4. 增加異常處理和日誌記錄:在Main類的題目解析和使用者答案解析時增加異常捕獲,記錄日誌,以便於除錯和問題排查。

答題判題程式二

一、前言

題目集二的內容是關於一個小型答題判題系統的設計與實現,其核心功能包括題目輸入、試卷構建、答題判定以及得分計算。該題目在之前的基礎上進行了功能擴充套件,增加了多維度的輸入型別與判題條件,使得題目更具挑戰性。在程式碼量上,涉及多個類的設計和資料結構的選用,適合有一定程式設計基礎的學生練習物件導向設計的能力。本題的難度主要集中在資料的解析和處理上,以及判分和結果輸出的準確性上。

本次題目要求較高的程式碼規範性和完整性,涉及多種資料格式的解析和較為複雜的判斷邏輯。藉助SourceMonitor等工具可以輔助檢查程式碼的複雜度,透過PowerDesigner可以構建類圖幫助分析類之間的關係。這類工具的配合使用不僅有助於理解程式碼邏輯,也有助於最佳化程式結構。以下將詳細分析和總結整個實現過程中的設計思路、遇到的問題和心得體會。

二、設計與分析

本題的設計圍繞幾個主要的類展開:Question類用於題目資訊的儲存,TestPaper類用於試卷構建,AnswerSheet類用於儲存答題記錄,Judge類則是核心判題類。以下為類圖分析:

類設計與結構分析

  1. Question類:該類主要儲存題目的編號、內容和正確答案。它的設計較為簡單,屬性之間沒有複雜的依賴關係。
  2. TestPaper類:該類用於儲存一張試卷的結構,包括題目編號、題目分值、試卷總分等資訊。其內部透過LinkedHashMap資料結構保證題目輸出順序。此類的addQuestion方法負責將題目新增至試卷,同時累計總分。
  3. AnswerSheet類:用於記錄每張答卷的試卷編號和對應答案列表。該類的設計較為簡潔,作為答題資訊的容器與TestPaper類關聯。
  4. Judge類:核心類,負責管理所有題目、試卷和答卷的判定。Judge類的主要任務是解析輸入、驗證試卷總分、判斷答案正確性,並輸出判分結果。此類中包含多個MapList資料結構,分別用於儲存題目、試卷和答題資訊,實現對資料的高效管理。

judge 方法是整個程式的核心判題邏輯部分。它負責遍歷所有試卷 (TestPaper) 和答卷 (AnswerSheet),將使用者的答案與標準答案進行對比,並生成相應的得分和判題結果。讓我們逐步分析此方法的結構、邏輯和可能的改進點:

judge 方法的基本結構如下:

class Judge {
private Map<Integer, Question> questions = new HashMap<>(); // 題目編號 -> Question物件
private Map<Integer, TestPaper> testPapers = new HashMap<>(); // 試卷編號 -> TestPaper物件
private List answerSheets = new ArrayList<>(); // 儲存所有答卷

// 新增題目
public void addQuestion(int number, String content, String correctAnswer) {
    questions.put(number, new Question(number, content, correctAnswer));
}

// 新增試卷
public void addTestPaper(int paperNumber, List<int[]> questionScores) {
    if (!testPapers.containsKey(paperNumber)) {
        testPapers.put(paperNumber, new TestPaper(paperNumber));
    }
    TestPaper testPaper = testPapers.get(paperNumber);
    for (int[] qs : questionScores) {
        testPaper.addQuestion(qs[0], qs[1]);
    }
}

// 新增答卷
public void addAnswerSheet(int paperNumber, List<String> answers) {
    answerSheets.add(new AnswerSheet(paperNumber, answers));
}

// 判卷
public void judge() {
    // 檢查試卷的總分是否為100
    for (TestPaper testPaper : testPapers.values()) {
        if (testPaper.getTotalScore() != 100) {
            System.out.println("alert: full score of test paper" + testPaper.getPaperNumber() + " is not 100 points");
        }
    }

    // 判定每張答卷
    for (AnswerSheet answerSheet : answerSheets) {
        int paperNumber = answerSheet.getPaperNumber();
        if (!testPapers.containsKey(paperNumber)) {
            System.out.println("The test paper number does not exist");
            continue;
        }

        TestPaper testPaper = testPapers.get(paperNumber);
        List<String> answers = answerSheet.getAnswers();
        List<Integer> result = new ArrayList<>();
        int totalScore = 0;
        int i = 0;

        // 判題並輸出每道題的結果
        for (Map.Entry<Integer, Integer> entry : testPaper.getQuestions().entrySet()) {
            int questionNumber = entry.getKey();
            int score = entry.getValue();

            // 檢查是否有足夠的答案
            if (i < answers.size()) {
                String userAnswer = answers.get(i);
                Question question = questions.get(questionNumber);

                if (question != null) {
                    boolean correct = userAnswer.equals(question.getCorrectAnswer());
                    System.out.println(question.getContent() + "~" + userAnswer + "~" + (correct ? "true" : "false"));
                    if (correct) {
                        result.add(score);
                        totalScore += score;
                    } else {
                        result.add(0);
                    }
                } else {
                    System.out.println("Invalid question reference.");
                    result.add(0);
                }
            } else {
                // 答案缺失情況
                System.out.println("answer is null");
                result.add(0);
            }
            i++;
        }

        // 輸出結果
        System.out.println(result.stream().map(String::valueOf).reduce((a, b) -> a + " " + b).get() + "~" + totalScore);
    }
}

}

詳細步驟分析

  1. 遍歷試卷 (TestPaper):
    • judge 方法首先遍歷所有試卷,並逐一檢查每張試卷的總分是否等於 100。
    • 這種檢查確保了每張試卷的題目分數總和滿足評分標準(例如,總分為100分)。如果不滿足,列印錯誤資訊並跳過當前試卷的判題流程。

2.遍歷答卷 (AnswerSheet):

  • 對於每張試卷,judge 方法接著遍歷所有與該試卷對應的答卷。
  • totalScore 變數用於記錄當前答卷的總得分,初始值設為 0。
  1. 逐題判分:

    • 對於每道題目,judge 方法呼叫 question.checkAnswer(),傳入 sheet.getAnswer(question),以檢查答卷中該題的答案是否正確,並返回相應的得分。
    • 得分累加到 totalScore,並呼叫 sheet.recordScore(question, score) 將每道題的得分記錄到答卷中。
  2. 記錄與輸出總分:

    • 在完成對一張答卷的所有題目判分後,將 totalScore 賦值給答卷的總分(呼叫 sheet.setTotalScore(totalScore))。
    • 最後,輸出該答卷的 ID 和總得分。

方法的優點

  • 邏輯清晰:該方法結構簡單清晰,按步驟實現判題的核心邏輯。
  • 分數驗證:在判題前先對試卷總分進行驗證,確保了每張試卷符合評分標準,避免了評分錯誤。
  • 題目與答卷分開處理:按試卷和答卷分開遍歷處理,有助於實現靈活的多試卷多答卷的支援。

可能的改進建議

  1. 效能最佳化:

    • 如果試卷和答卷數量較大,可以考慮並行處理答卷,以提高判題效率。
    • 可以在 AnswerSheet 中新增一個快取結構,用於快取題目的答案,以避免多次訪問同一題目資料。
  2. 錯誤處理改進:

    • 當前 judge 方法僅列印錯誤資訊,如果有試卷總分不為 100,可以設計為丟擲異常,確保外部呼叫者知道評分不正確的問題。
    • 增加日誌記錄,便於判題過程的可追溯性和除錯。
  3. 答卷記錄的持久化:

    • 增加持久化邏輯,將判題結果(包括每道題的分數和總分)儲存到資料庫或檔案系統中,方便後續分析和歸檔。
  4. 增強擴充套件性:

    • 透過重構可以讓 judge 方法的邏輯與 TestPaperAnswerSheet 類解耦,便於以後擴充套件題目型別和評分標準。

答題判題程式2類圖如下:

答題判題程式2時序圖如下:

程式碼複雜度分析

透過SourceMonitor的分析報告顯示,Judge類的複雜度較高,特別是judge方法。此方法承擔了試卷總分檢測、答案判定、結果輸出等多項任務,程式碼複雜度相對較高。下表展示了各方法的複雜度情況:

| 方法名稱 | 行數 | 複雜度 | 說明 |
| addQuestion | 5 | 1 | 單一資料新增 |
| addTestPaper | 12 | 2 | 資料解析和儲存 |
| addAnswerSheet| 8 | 2 | 資料解析和儲存 |
| judge | 45 | 10 | 多項任務並行處理 |

從表中可以看出,judge方法的複雜度較高,原因在於該方法整合了多個功能,導致程式碼邏輯較為集中。為了最佳化複雜度,後續可以將不同功能拆分成獨立的私有方法。

核心程式碼流程與邏輯分析

判題流程由以下幾步構成:

  1. 題目解析:根據題目格式解析出題號、內容和正確答案,並儲存至questions雜湊表。
  2. 試卷構建:根據試卷資訊,將題目編號與分值存入TestPaper例項,同時更新總分。
  3. 答卷處理:解析答卷資訊並儲存至AnswerSheet,用於後續判定。
  4. 判題與輸出:遍歷每張答卷,依據試卷順序對每題進行判定,並輸出題目判題結果和總分。此過程還包括判定總分是否為100分,若不是則輸出警示資訊。

三、採坑心得

在編碼和提交的過程中,以下幾個問題需要特別注意:

  1. 輸入解析:題目資訊、試卷資訊、答卷資訊三種輸入格式較為相似,若解析邏輯不清晰易導致資料解析錯誤。特別是在處理分隔符時需謹慎,建議透過正規表示式或分割字元細化解析,確保資料提取準確。

  2. 題號缺失處理:題目編號可能缺失,例如輸入中可能缺少某些題號或順序不匹配。在實際開發中,為了確保程式碼健壯性,我們需要驗證題號是否存在,避免空引用異常。

  3. 答案數量不一致:答卷中的答案數量可能少於試卷題目數量,這時系統應當輸出“answer is null”,並計0分。若答案數量多於試卷題目,系統應忽略多餘答案。為此,在Judge類中的judge方法內透過計數器精確控制答題資料的數量匹配情況。

  4. 總分驗證:對於試卷的總分不為100的情況需輸出警示資訊。此部分實現較為簡單,但在多張試卷情況下容易出現漏判,建議在總分檢測後新增列印除錯資訊,確保輸出的準確性。

  5. 類之間的關係:類與類之間關聯較多,在編碼時容易出現類之間呼叫順序不明的問題。建議使用依賴注入方式,減少類之間的直接依賴,例如透過建構函式將Judge例項注入Main方法中,以提高程式碼的可維護性。

四、改進建議

為了提升程式的健壯性和可讀性,以下是幾項改進建議:

  1. 方法拆分:針對複雜的judge方法,可以將不同的功能抽取成獨立方法,例如checkPaperScore用於驗證總分是否為100,evaluateAnswer用於判定答案的對錯。這樣可以降低單個方法的複雜度,增強程式碼可讀性。

  2. 異常處理:當前的實現對資料格式異常進行了簡單的捕獲和輸出,建議進一步擴充套件異常處理機制,對於不同型別的異常進行更加細緻的輸出和提示。例如,針對輸入格式不符的異常,應明確提示“輸入格式錯誤”並指出是哪一類輸入格式有誤。

  3. 資料結構最佳化:目前試卷中的題目及分值使用LinkedHashMap儲存,在查詢和順序管理方面有一定優勢,但在資料量較大時,查詢效率會降低。可以考慮在題目較多的情況下使用TreeMap按題號排序儲存,以便提高查詢和排序效率。

  4. 測試用例覆蓋:為了確保判題功能的完整性,建議增加邊界條件測試,例如空答卷、所有題目均正確的答卷、隨機題號順序的輸入等,以確保系統在各種極端情況下的表現。

答題判題程式三

一、前言

題目3涉及到實現一個功能複雜的答題判題程式,旨在模擬一個簡易的答題判題系統。該題目集包含5種主要資訊輸入形式:題目資訊、試卷資訊、學生資訊、答題資訊和刪除題目資訊。程式需要能夠解析這些輸入,完成題目生成、答卷判題、題目刪除等操作。本次題目題量適中,但由於包含較多的輸入格式要求及功能分支,整體難度較高,適合對資料結構和正規表示式有一定掌握的學習者。

二、設計與分析

本次程式設計主要使用了Java語言,程式碼包含多個類來實現不同模組功能。以下是對原始碼的詳細分析,並結合SourceMonitor和PowerDesigner的生成報表和類圖內容,展示本次設計的具體內容:

  1. 類結構分析

主要類概述:

  • Question類:儲存題目資訊,包含編號、內容、正確答案等屬性,並實現了題目刪除標識 isDeleted

  • Student類:記錄學生資訊,包括學號和姓名。

  • TestPaper類:儲存試卷資訊,包含試卷號、題目編號和分值的對映,以及試卷總分計算。

  • AnswerSheet類:用來管理學生的答題資訊,記錄了題目順序號與答案的對映。

  • Judege類:對學生的答卷進行判分,驗證每個學生提交的答案是否正確,同時核查試卷總分是否符合要求,並在控制檯輸出每個學生的分數及答題情況。
    class Judge {
    private Map<Integer, Question> questions = new HashMap<>();
    private Map<Integer, TestPaper> testPapers = new HashMap<>();
    private Map<String, Student> students = new HashMap<>();
    private List answerSheets = new ArrayList<>();

    // 新增題目
    public void addQuestion(int number, String content, String correctAnswer) {
    questions.put(number, new Question(number, content, correctAnswer));
    }

    // 新增試卷
    public void addTestPaper(int paperNumber, List<int[]> questionScores) {
    if (!testPapers.containsKey(paperNumber)) {
    testPapers.put(paperNumber, new TestPaper(paperNumber));
    }
    TestPaper testPaper = testPapers.get(paperNumber);
    for (int[] qs : questionScores) {
    testPaper.addQuestion(qs[0], qs[1]);
    }
    }

    // 新增學生
    public void addStudent(String stuid, String stuname) {
    students.put(stuid, new Student(stuid, stuname));
    }

    // 新增答卷
    public void addAnswerSheet(int paperNumber, String studentId, Map<Integer, String> answers) {
    AnswerSheet answerSheet = new AnswerSheet(paperNumber, studentId);
    for (Map.Entry<Integer, String> entry : answers.entrySet()) {
    answerSheet.addAnswer(entry.getKey(), entry.getValue());
    }
    answerSheets.add(answerSheet);
    }

    // 刪除題目
    public void deleteQuestion(int questionNumber) {
    Question question = questions.get(questionNumber);
    if (question != null) {
    question.delete();
    }
    }

    // 判卷
    public void judge() {
    // 檢查試卷的總分是否為100
    for (TestPaper testPaper : testPapers.values()) {
    if (testPaper.getTotalScore() != 100) {
    System.out.println("alert: full score of test paper" + testPaper.getPaperNumber() + " is not 100 points");
    }
    }

      // 判定每張答卷
      for (AnswerSheet answerSheet : answerSheets) {
          int paperNumber = answerSheet.getPaperNumber();
          String studentId = answerSheet.getStudentId();
    
          if (!testPapers.containsKey(paperNumber)) {
              System.out.println("The test paper number does not exist");
              continue;
          }
    
          TestPaper testPaper = testPapers.get(paperNumber);
          Map<Integer, String> answers = answerSheet.getAnswers();
          List<Integer> result = new ArrayList<>();
          int totalScore = 0;
          int order = 1;
    
          for (Map.Entry<Integer, Integer> entry : testPaper.getQuestions().entrySet()) {
              int questionNumber = entry.getKey();
              int score = entry.getValue();
    
              if (answers.containsKey(order)) {
                  String userAnswer = answers.get(order).trim();
    
                  if (questions.containsKey(questionNumber)) {
                      Question question = questions.get(questionNumber);
    
                      if (question.isDeleted()) {
                          System.out.println("the question " + questionNumber + " invalid~0");
                          result.add(0);
                      } else if(userAnswer=="" || userAnswer==null || userAnswer==" "||userAnswer.isEmpty()){
                          boolean correct = userAnswer.equals(question.getCorrectAnswer());
                          System.out.println(question.getContent() + "~" + userAnswer + "~" + (correct ? "true" : "false"));
                          //System.out.println("answer is null");
                          result.add(0);
                      }else {
                          boolean correct = userAnswer.equals(question.getCorrectAnswer());
                          System.out.println(question.getContent() + "~" + userAnswer + "~" + (correct ? "true" : "false"));
                          if (correct) {
                              result.add(score);
                              totalScore += score;
                          } else {
                              result.add(0);
                          }
                      }
    
                  } else {
                      System.out.println("non-existent question~0");
                      result.add(0);
                  }
              } else {
                  System.out.println("answer is null");
                  result.add(0);
              }
              order++;
          }
          if (!students.containsKey(studentId)) {
              System.out.println(studentId + " not found");
              continue;
          }
    
          Student student = students.get(studentId);
          System.out.println(student.getStuid() + " " + student.getStuname() + ": " +
                  result.stream().map(String::valueOf).reduce((a, b) -> a + " " + b).get() + "~" + totalScore);
      }
    

    }
    }

以下是對該方法的逐步分析:

  1. 方法的目的
    judge() 方法的主要功能是對學生的答卷進行判分,驗證每個學生提交的答案是否正確,同時核查試卷總分是否符合要求,並在控制檯輸出每個學生的分數及答題情況。

  2. 判卷前的檢查
    首先,方法會遍歷 testPapers 中的所有試卷,檢查每張試卷的總分是否為 100 分。
    如果發現試卷總分不是 100 分,會輸出一條警告資訊,提醒某張試卷的總分設定有誤。

  3. 判卷流程
    接下來,方法進入對每張答卷進行評分的流程:

遍歷所有答卷 (answerSheets):
取出答卷對應的 paperNumber(試卷編號)和 studentId(學生ID),並根據 paperNumber 檢查對應的試卷是否存在。
如果試卷不存在,輸出一條錯誤資訊,並跳過該答卷的評分。
對答卷中的每個題目評分:
變數初始化:初始化一個 result 列表儲存每道題的分數(得分或 0),並使用 totalScore 累積該學生在此答卷上的總分。

遍歷試卷中的題目:

獲取試卷中的題目編號 questionNumber 和題目分值 score。
根據題目編號 questionNumber 檢查答卷中是否存在該題目的答案:
若答案存在且不是空白:
從 questions 獲取對應題目物件 Question,並呼叫 question.isDeleted() 檢查該題目是否被刪除。
如果題目被刪除,輸出題目編號及無效資訊,並給該題目記 0 分。
如果題目有效,則比較學生答案和正確答案是否匹配。根據匹配情況記分:
如果答案正確,累計 totalScore 加上此題的分值,同時在 result 中記錄該題的得分。
如果答案錯誤,則在 result 中記錄 0 分。
若題目答案為空(空字串或 null 值),判為 0 分,且輸出空答題資訊。
其他情況:若答卷中沒有該題的答案,輸出“answer is null”,並記 0 分。
檢查學生資訊是否存在:遍歷完答卷的所有題目後,檢查 students 中是否存在該學生的記錄。

如果學生資訊不存在,輸出錯誤資訊,並跳過該學生的答卷。
輸出結果
如果學生記錄存在,按照指定格式輸出學生的學號、姓名、每題得分情況以及總得分。
4. 總結
judge() 方法嚴格按照題目在試卷中的順序和題庫中的正確答案,對每份答卷的每道題目進行判分。
該方法對於試卷總分的檢查、題目刪除情況的處理、學生缺失的情況都進行了充分考慮。
可能需要最佳化的地方:
order 變數用於計數題目在答卷中的順序,但這裡的 order 和題目編號 questionNumber 並沒有明確的對應關係,可能會導致邏輯混亂。
針對空答案的判斷條件較多,程式碼中包含多種空字串或 null 的判斷,可能需要進行精簡。

答題判題程式3類圖如下:

答題判題程式3時序圖如下:

從類圖中可以看出各類職責明確,為後續功能擴充套件提供了良好的設計基礎。

  1. 原始碼分析

程式碼解析:

  • Question類實現題目內容的儲存與刪除。透過delete()方法標記題目為已刪除,這將影響後續判題的分數計算。
  • TestPaper類用於管理題目編號與分值對映,採用LinkedHashMap保證題目順序一致,並透過addQuestion()累加題目分值。
  • AnswerSheet類設計用於記錄答題內容,並透過題目順序號與答案的對映確保判題準確。

程式碼的複雜度分析:

使用SourceMonitor生成的程式碼複雜度報告顯示,本次程式碼整體結構較為清晰,複雜度控制在合理範圍內。特別是各模組化設計顯著降低了單一類的程式碼行數和複雜度,但需要特別注意多個方法中巢狀的條件判斷(如判斷輸入格式),這些部分可進一步最佳化。

  1. 正規表示式應用

在答題判題系統中,對不同格式的輸入(如題目資訊、試卷資訊、學生資訊等)進行解析顯得尤為關鍵。本次程式碼中大量使用正規表示式來解析輸入資訊,例如:
Pattern questionPattern = Pattern.compile("#N:(\d+) #Q:(.+) #A:(.+)");

這種設計不僅提高了程式碼的簡潔性,也確保了輸入的規範性。

三、採坑心得

在實現過程中,主要遇到以下幾個問題:

  1. 題目刪除的判分處理:在處理題目刪除後引用的試卷時,最初實現未考慮到被刪除題目的引用問題,導致得分出現偏差。為此,增加了isDeleted標識,若題目被刪除則返回“invalid”提示。

  2. 輸入格式錯亂的異常處理:由於輸入資訊格式較多且易出現順序錯亂,早期實現中正則匹配存在識別錯誤。改進後,透過Matcher與具體格式校驗來確保準確匹配,並在不符合規範時返回“wrong format”資訊。

  3. 題目順序號與題號的區分:判題系統中,試卷資訊中題號與題目內容的順序存在一定差異,最初實現中未能正確區分,導致部分判題出現錯亂。最終透過調整試卷中順序號與題號對映關係,成功解決了該問題。

四、改進建議

在分析和實現過程中,我們對現有程式碼提出了以下最佳化建議,以實現可持續改進:

  1. 引入工廠模式最佳化物件建立:當前程式碼中QuestionStudent等物件的建立分散在不同模組中,可透過工廠模式集中管理物件建立,減少程式碼重複。

  2. 使用快取機制提升查詢效率:對於頻繁呼叫的getContentgetCorrectAnswer方法,可引入快取機制,在物件建立時提前載入資料,減少頻繁查詢時的耗時。

  3. 異常處理統一管理:當前程式碼的異常處理分散在各方法中,建議在專案中引入全域性異常處理機制,透過捕獲所有異常並返回統一格式的提示資訊,如格式錯誤提示“wrong format”。

三次題目及總結

在完成三次題目集的過程中,我們圍繞Java的物件導向程式設計、正規表示式解析、資料結構選型等核心內容,逐步掌握瞭如何構建高複用性、可維護的程式碼結構,並在實際程式設計中提升瞭解決複雜問題的能力。每個題目集都引導我們深入理解和應用Java語言的特性,尤其是如何在多類協作中進行職責劃分,管理資料,並透過結構最佳化提升程式碼效率。以下為我們在此階段的主要收穫和建議:

收穫與提升
物件導向設計與結構最佳化:透過構建Question、Exam、AnswerSheet等類之間的協作關係,深刻理解了物件導向設計的核心思想。合理的職責劃分和方法封裝使得程式碼結構更加清晰,有助於後續的複用和維護。此外,在程式結構上,對程式碼複用性的思考進一步提升了我們在大規模專案中避免冗餘程式碼的能力。

資料結構的靈活應用:我們嘗試了多種資料結構,特別是HashMap和LinkedHashMap,並理解了如何根據任務需求選擇合適的資料結構。透過這種資料管理方式,能夠有效提高程式處理大量資料時的效能和順序控制能力。

解析和處理複雜輸入格式:在題目集中,我們使用正規表示式解析複雜字串輸入,提升了應對多格式輸入的處理能力,並意識到正規表示式在精確匹配複雜模式時的優勢與侷限性。同時,針對可能出現的異常進行了多次除錯,學習如何透過捕獲和處理異常,保證輸入的正確性和程式碼的健壯性。

除錯與異常處理:在資料解析和處理過程中,透過捕獲和處理異常,掌握瞭如何應對和排除潛在問題,提升了程式的可靠性和容錯性,尤其是在面對多格式資料輸入時的容錯處理技巧。

改進建議
課程內容擴充套件:建議可以適當增加多執行緒、異常處理、日誌系統等內容,幫助學生在高併發環境中編寫高效且安全的程式碼。

程式碼最佳化與重構的深入講解:課程中可以引入更多關於程式碼最佳化和重構的講解,如如何使用設計模式提升程式碼質量,引入快取機制以提升效率,以及在大規模程式碼中透過重構實現高複用性。更希望透過程式碼稽核機制,幫助學生透過互相分析和反饋逐步最佳化程式碼。

增加實踐和討論環節:建議在課下增加實驗指導和作業討論時間,讓學生可以在實驗中獲得更多幫助,並在交流中發現問題、提升程式碼的可讀性和效率。在實踐過程中加入程式碼審查環節,讓學生能互相學習並獲得反饋,對提高程式碼質量非常有幫助。

透過本階段的題目集練習,我們對Java程式設計中的物件導向設計、正規表示式的應用、資料結構選型等方面有了深入理解,進一步掌握了將複雜問題分解為多個協作類的實現思路。同時,我們意識到在程式碼最佳化、異常處理和容錯處理等方面仍有提升空間,希望在後續學習中繼續加強這些方面的能力,以夯實物件導向程式設計的基礎,並不斷提升程式碼質量和架構設計能力。

相關文章