前言
PTA第四次作業
設計與分析
題目分析
本題在答題判題程式3基礎上新增了選擇題和填空題內容,程式輸入資訊分五種,
輸入格式:
1、題目資訊格式:"#N:"+題目編號+" "+"#Q:"+題目內容+" "#A:"+標準答案
2、試卷資訊格式:"#T:"+試卷號+" "+題目編號+"-"+題目分值+" "+題目編號+"-"+題目分值+...
3、學生資訊格式:"#X:"+學號+" "+姓名+"-"+學號+" "+姓名....+"-"+學號+" "+姓名
4、答卷資訊格式:"#S:"+試卷號+" "+學號+" "+"#A:"+試卷題目的順序號+"-"+答案內容+...
5、刪除題目資訊格式:"#D:N-"+題目號
輸出格式:
1、試卷總分警示
2、答卷資訊
3、判分資訊
4、被刪除的題目提示資訊
5、題目引用錯誤提示資訊
6、格式錯誤提示資訊
7、試卷號引用錯誤提示輸出
8、學號引用錯誤提示資訊
分析:在輸入方面的改進,新增題目型別,有選擇題題目資訊,填空題題目資訊。在輸入順序上也有變化,作業4明確只要是正確格式的資訊,可以以任意的先後順序輸入各類不同的資訊,比如試卷可以出現在題目之前,刪除題目的資訊可以出現在題目之前等。
輸出方面的改進有多選題和填空題的判題結果及給分方式細化。填空題和多選題增加了 “partially correct” 表示部分正確。給分方式上新增了部分正確答案且不含錯誤答案給一半分。
還要考慮多張試卷及多學生答卷情況:考慮了多個同學有多張不同試卷的答卷的情況,輸出順序優先順序為學號、試卷號,按從小到大的順序先按學號排序,再按試卷號。
所以,透過題目分析,程式碼方面需要增加
1、題目型別判斷及處理邏輯:需要增加程式碼來識別輸入的題目是選擇題還是填空題,然後根據各自不同的判題和給分規則進行處理。對於選擇題,要能夠處理標準答案中多個正確答案的情況,比如分割正確答案字串,在判題時準確判斷答案是否完全正確、部分正確或錯誤。對於填空題,要實現精確的字元匹配判斷,以及按照填空題的給分規則準確計分。
2、資訊輸入順序處理邏輯:由於允許各類資訊以任意先後順序輸入,程式碼中需要增加邏輯來靈活處理這種無序輸入的情況。需要先將所有輸入資訊收集起來,然後根據不同的型別標識(如 “#N:”“#T:”“#X:”“#S:”“#D:” 等)進行分類整理,再按照處理流程依次處理各類資訊。
3、多張試卷及多學生答卷排序邏輯:為了實現按照學號、試卷號從小到大的順序輸出結果,需要在程式碼中增加相應的排序演算法。
知識點解析
繼承:使用了繼承機制,如MultipleChoiceQuestion和FillInBlankQuestion類繼承自Question類。
連結串列(LinkedList):使用了LinkedList來儲存學生答案列表(answerList)和學生資訊列表(studentList)。
雜湊表(LinkedHashMap):運用了LinkedHashMap來儲存題目資訊(questionMap)和試卷資訊(testpaperMap)。
正規表示式:大量使用正規表示式來匹配和解析輸入的各種格式的字串。
集合的遍歷:對LinkedList和LinkedHashMap等集合型別進行遍歷操作。
集合的排序:對answerList使用了Collections.sort()方法並傳入自定義的比較器(透過Answer類實現Comparable介面定義的compareTo方法)來按照學號和試卷號的順序對學生答案進行排序。
字串處理:涉及到大量的字串擷取、拼接、比較等操作。透過equals()方法比較學生答案和標準答案是否相等,透過contains()方法判斷字串是否包含另一個字串等操作來實現部分正確等情況的判斷。
異常處理:部分透過格式檢查實現。
除錯過程
1.類的定義分析
Question類
作用:該類是所有題目型別的基類,用於表示一個通用的試題資訊。它包含了題目的編號(number),題目的具體內容(content)題目的標準答案(standardAnswer)題目的有效形式(isValid),為後續不同型別題目(如選擇題、填空題等)的類提供了基礎結構。提供了一個預設建構函式public Question()和帶引數的建構函式public Question(int number, String content, String standardAnswer, boolean isValid)。
MultipleChoiceQuestion類
繼承自Question類,專門用於表示選擇題型別的題目。建構函式public MultipleChoiceQuestion透過super()關鍵字呼叫了父類Question的帶引數建構函式,將傳入的引數傳遞給父類來初始化從父類繼承的屬性,確保選擇題物件能正確初始化其通用的題目屬性。
FillInBlankQuestion類
同樣繼承自Question類,用於表示填空題型別的題目。建構函式public FillInBlankQuestion透過super()呼叫父類建構函式來初始化繼承的屬性,保證填空題物件能正確設定通用的題目資訊。
TestPaper類
用於表示一份試卷的相關資訊,屬性包括試卷中的題目及其對應的分值,以及計算試卷的總分等。同時使用LinkedHashMap來儲存試卷中的題目編號(鍵)及其對應的分值(值)。定義了預設建構函式。
public void addQuestion(int questionID, int score):用於向試卷中新增一道新題,該方法將傳入的題目編號和分值新增到questions對映中,並同時更新試卷的總分(透過累加分值)以及題目數量(自增)。
public LinkedHashMap<Integer, Integer> getQuestions():返回儲存試卷題目編號和分值的LinkedHashMap,以便在其他地方獲取試卷的題目資訊。
public int getFullScore():透過遍歷questions對映中的所有分值並累加,計算並返回試卷的總分。
Answer類
用於表示學生對試卷的答案資訊,包括答案對應的題目、學生的學號、試卷編號等,同時實現了Comparable介面以便對答案進行排序。
LinkedHashMap儲存學生答案中題目編號(鍵)及其對應的答案內容(值),answerCount記錄學生答案的數量,paperID表示該答案所對應的試卷編號,studentID儲存學生的唯一識別符號(學號)。
提供了一系列的get和set方法和建構函式:public Answer(LinkedHashMap<Integer, String> answers, int answerCount, int paperID, int studentID),同時
@Override public int compareTo(Answer other):實現了Comparable介面的compareTo方法,用於比較兩個Answer物件的大小,使Answer物件可以按照學號和試卷編號的順序進行排序。
Student類
作用:用於表示學生的基本資訊,主要包含學生的學號studentID和姓名name兩個屬性。
ArrayChecker類
該類提供了一個方法用於檢查兩個字串陣列之間的關係,主要用於處理選擇題答案的比較情況(判斷學生答案與標準答案的匹配程度)。
public String checkArrays(String[] array1, String[] array2)透過比較兩個集合的內容來判斷它們之間的關係。分別返回"true","partially correct","false"。用於判斷選擇題學生答案與標準答案的匹配情況,是核心功能。
NChecher類("#N:")
用於處理普通題目資訊的輸入,提供一個靜態方法public static void processEntryForNormalQuestion(String entry, LinkedHashMap<Integer, Question> questionMap),根據特定的輸入格式解析普通題目資訊,並將其儲存到LinkedHashMap型別的題目對映中。
ZChecher類("#Z:")
專門用於處理選擇題題目資訊的輸入,與NChecher類類似,針對選擇題的輸入格式進行解析和處理。
KChecher類("#K:")
用於處理填空題題目資訊的輸入,按照填空題的輸入格式要求解析輸入字串,並將解析出的資訊儲存到題目對映中。
TChecher類("#T:")
負責處理試卷資訊的輸入,根據特定的試卷資訊輸入格式解析輸入字串,並建立相應的TestPaper物件,將其儲存到LinkedHashMap型別的試卷對映中。
SChecher類("#S:")
用於處理學生答案資訊的輸入,按照學生答案的輸入格式解析輸入字串,並建立相應的Answer物件,將其新增到LinkedList型別的答案列表中。
XChecher類("#X:")
用於處理學生資訊的輸入,根據特定的學生資訊輸入格式解析輸入字串,並建立相應的Student物件,將其新增到LinkedList型別的學生列表中。
DNChecher類("#DN:")
用於處理將題目標記為無效的輸入操作,根據特定的輸入格式解析輸入字串,找到對應的題目並將其有效性設定為false。
OutputHandler類
提供了一系列靜態方法用於輸出各種錯誤資訊和提示資訊。
CheckQuestion類(核心方法)
提供了一個靜態方法用於檢查學生答案與題目標準答案的匹配情況,根據題目型別(選擇題、填空題或其他型別)採用不同的判斷邏輯來確定答案是否正確,並返回相應的判斷結果。
public static String checkAnswer(Question question, String studentAnswer, String[] studentAnswersArray1):該方法首先判斷題目型別,如果是MultipleChoiceQuestion型別,透過ArrayChecker類的checkArrays方法來比較學生答案陣列與標準答案陣列的關係,得出判斷結果。如果是FillInBlankQuestion型別,則根據學生答案與標準答案的具體內容進行比較,考慮完全匹配、部分匹配等情況得出判斷結果。對於其他型別的題目,直接比較學生答案與標準答案是否相等來確定判斷結果。最後返回相應的判斷結果字串("true"、"partially correct"或"false")。
2.重點程式碼程式碼具體分析
不同型別輸入處理部分:
處理普通題目資訊(NChecher):如果輸入以"#N:"開頭,會呼叫NChecher類的processEntryForNormalQuestion方法。該方法首先透過正規表示式檢查輸入格式是否符合"#N:(\s\d+\s)#Q:(.)#A:(.)"的模式。若格式正確,會從輸入字串中提取出題目編號、內容和標準答案等資訊,建立一個Question物件(並設定為有效),最後將該物件存入questionMap中,以題目編號作為鍵。若格式不正確,則輸出錯誤提示資訊"wrong format:" + entry。
class NChecher {
public static void processEntryForNormalQuestion(String entry, LinkedHashMap<Integer, Question> questionMap) {
if (entry.startsWith("#N:")) {
if (entry.matches("#N:(\\s*\\d+\\s*)#Q:(.*)#A:(.*)")) {
String num = entry.substring(entry.indexOf("#N:") + 3, entry.indexOf("#Q:")).trim();
String content = entry.substring(entry.indexOf("#Q:") + 3, entry.indexOf("#A:")).trim();
String standardAnswer = entry.substring(entry.indexOf("#A:") + 3, entry.length()).trim();
int number = Integer.parseInt(num);
questionMap.put(number, new Question(number, content, standardAnswer, true));
} else {
System.out.println("wrong format:" + entry);
}
}
}
}
處理選擇題題目資訊(ZChecher):當輸入以"#Z:"開頭時,ZChecher類的processEntryForMultipleChoiceQuestion方法會被呼叫。它先檢查格式是否符合特定正規表示式模式,若符合,提取相關資訊建立MultipleChoiceQuestion物件(設定為有效),並存入questionMap中;否則輸出錯誤提示。
class ZChecher {
public static void processEntryForMultipleChoiceQuestion(String entry, LinkedHashMap<Integer, Question> questionMap) {
if (entry.startsWith("#Z:")) {
if (entry.matches("#Z:(\\s*\\d+\\s*)#Q:(.*)#A:(.*)")) {
String num = entry.substring(entry.indexOf("#Z:") + 3, entry.indexOf("#Q:")).trim();
String content = entry.substring(entry.indexOf("#Q:") + 3, entry.indexOf("#A:")).trim();
String standardAnswer = entry.substring(entry.indexOf("#A:") + 3, entry.length()).trim();
int number = Integer.parseInt(num);
questionMap.put(number, new MultipleChoiceQuestion(number, content, standardAnswer, true));
} else {
System.out.println("wrong format:" + entry);
}
}
}
}
處理填空題題目資訊(KChecher):輸入以"#K:"開頭的情況,由KChecher類的processEntryForFillInBlankQuestion方法處理。先驗證格式,格式正確則提取資訊建立FillInBlankQuestion物件(設定為有效)放入questionMap中,格式不對就輸出錯誤資訊。程式碼邏輯和class ZChecher相似。
處理試卷資訊(TChecher):若輸入以"#T:"開頭,TChecher類的processEntryForTestPaper方法負責處理。它先透過正規表示式判斷格式是否符合要求,若符合,先提取試卷編號,建立TestPaper物件,再從輸入中解析出題目編號和分值資訊新增到該試卷物件中,最後將試卷物件存入testpaperMap中;若格式不符,輸出錯誤提示。
class TChecher {
public static void processEntryForTestPaper(String entry, LinkedHashMap<Integer, TestPaper> testpaperMap) {
if (entry.startsWith("#T:")) {
if (entry.matches("#T:\\s*(\\d*)\\s*(\\s*\\d+-\\d+\\s*)*")) {
Pattern pattern1 = Pattern.compile("#T:\\s*(\\d+)\\s*(.*)");
Matcher matcher1 = pattern1.matcher(entry);
if (matcher1.find()) {
int pid = Integer.parseInt(matcher1.group(1).trim());
TestPaper paper = new TestPaper();
Pattern pattern2 = Pattern.compile("(\\d+)-(\\d+)");
Matcher matcher2 = pattern2.matcher(entry);
while (matcher2.find()) {
int qid = Integer.parseInt(matcher2.group(1));
int score = Integer.parseInt(matcher2.group(2));
paper.addQuestion(qid, score);
}
testpaperMap.put(pid, paper);
} else {
System.out.println("wrong format:" + entry);
}
}
}
}
}
處理學生答案資訊(SChecher):對於以"#S:"開頭的輸入,SChecher類的processEntryForStudentAnswer方法會進行處理。該方法首先檢查格式是否匹配"#S:\s(\d+)\s+(\w)\s(#A:\s(\d+-?[^#]))"的正規表示式模式。若格式正確,會提取出學生的學號、試卷編號、答案數量以及每個答案的題目編號和內容等資訊,建立Answer物件並新增到answerList中;若格式錯誤,輸出錯誤提示。程式碼邏輯和class TChecher相似。
處理學生資訊(XChecher):當輸入以"#X:"開頭時,XChecher類的processEntryForStudentInfo方法被呼叫。它透過正規表示式檢查格式,若符合格式,會從輸入中提取每個學生的學號和姓名資訊,建立Student物件並新增到studentList中;若格式不正確,輸出錯誤提示。程式碼邏輯和class TChecher相似。
處理題目標記為無效的操作(DNChecher):若輸入以"#D:N-"開頭,DNChecher類的processEntryForInvalidateQuestion方法會處理。它先檢查格式是否符合"#D:N-\s\d+\s"的正規表示式模式,若符合,提取要標記為無效的題目編號,在questionMap中找到該題目並將其有效性設定為false;若格式不對,輸出錯誤提示。程式碼邏輯和class TChecher相似。
對答案列表排序部分:
在完成所有輸入的讀取和處理後,透過Collections.sort(answerList, Collections.reverseOrder());語句對answerList中的Answer物件按照由Answer類實現的Comparable介面中的compareTo方法進行排序。
該方法的比較邏輯是先基於學生的學號(studentID)進行比較,如果兩個Answer物件的學號不同,那麼就根據學號的大小關係確定整個Answer物件的大小關係並返回相應結果。只有當學號相同時,才會進一步基於試卷編號(paperID)進行比較,同樣根據試卷編號的大小關係確定Answer物件的大小關係並返回結果。
首先透過int sidComparison = Integer.compare(other.studentID, this.studentID)語句來比較當前Answer物件和傳入的另一個Answer物件的學生學號,這個返回的整數值sidComparison就代表了基於學生學號的比較結果。(Integer.compare()方法是 Java 提供的用於比較兩個整數大小的靜態方法,它會根據傳入的兩個整數引數的大小關係返回一個整數值)
接著透過if (sidComparison!= 0)條件判斷來檢查學號比較結果是否不等於0。如果不等於0,說明兩個Answer物件的學生學號不同,此時直接返回sidComparison的值。這樣,在排序操作中,學號較大的Answer物件就會排在前面。
如果前面基於學生學號的比較結果sidComparison等於0,這意味著兩個Answer物件的學生學號相同。需要進一步透過return Integer.compare(other.paperID, this.paperID)語句來比較兩個Answer物件的試卷編號。
@Override
public int compareTo(Answer other) {
int sidComparison = Integer.compare(other.studentID, this.studentID);
if (sidComparison!= 0) {
return sidComparison;
}
return Integer.compare(other.paperID, this.paperID);
}
}
學生答案與題目標準答案處理部分:
ArrayChecker類中checkArrays方法:(處理答案比較)
checkArrays方法的主要目的是比較兩個字串陣列array1和array2之間的關係,根據它們元素的包含情況來確定一種匹配程度,並返回一個表示匹配結果的字串,可能的結果為"true"(完全匹配)、"partially correct"(部分匹配)或"false"(不匹配)。
在方法內部,首先將傳入的兩個字串陣列array1和array2分別轉換為HashSet集合
Set<String> set11 = new HashSet<>(Arrays.asList(array1));
Set<String> set22 = new HashSet<>(Arrays.asList(array2));
然後再將這個List集合作為引數傳遞給HashSet的建構函式,從而建立出對應的HashSet集合。
接下來,透過一系列的條件判斷來確定兩個集合之間的關係,並根據不同的關係返回相應的結果字串
if (set11.equals(set22)) {
return "true";
} else if ((set11.containsAll(set22) || set22.containsAll(set11))&&set22.size() < set11.size()) {
return "partially correct";
} else {
return "false";
}
完全匹配情況(set11.equals(set22)):
兩個HashSet集合包含的元素完全相同,方法直接返回"true",表示兩個陣列是完全匹配的關係。答案完全正確。
部分匹配情況((set11.containsAll(set22) || set22.containsAll(set11))&&set22.size() < set11.size()):
我是這樣思考的,首先,(set11.containsAll(set22) || set22.containsAll(set11))這部分判斷的是兩個集合是否存在包含關係,即要麼set11包含set22中的所有元素,要麼set22包含set11中的所有元素。
然後,&&set22.size() < set11.size()這部分進一步限制了條件。在前面判斷出存在包含關係的基礎上,還要求被包含的集合的大小要小於包含它的集合的大小。表示一種部分正確的匹配關係,例如在選擇題場景下,正確答案:ABD,學生答案:AD,答案相互包含,但是學生答案少於正確答案,就可以認為是部分正確的答案。當滿足這個部分匹配的條件時,方法返回"partially correct"。
不匹配情況(上述條件都不滿足):
如果前面兩種情況的條件都不滿足,說明兩個陣列之間的關係是不匹配的。在這種情況下,方法返回"false",表示兩個陣列的元素之間不存在符合預期的匹配關係。答案完全錯誤。
CheckQuestion類中checkAnswer方法(處理問題型別)
checkAnswer方法的主要功能是根據傳入的題目物件(Question及其子類)、學生給出的答案(字串形式或字串陣列形式,取決於題目型別),來判斷學生答案與題目標準答案的匹配情況,並返回一個表示匹配結果的字串,可能的值為"true"(完全正確)、"partially correct"(部分正確)或"false"(錯誤)。
首先定義了一個空字串變數FLAG,用於儲存最終的答案匹配結果。然後根據題目型別判斷答案匹配情況。
如果是選擇題型別(MultipleChoiceQuestion):
if (question instanceof MultipleChoiceQuestion) {
ArrayChecker checker1 = new ArrayChecker();
String[] MstandardAnswersArray = question.getStandardAnswer().split(" ");
FLAG = checker1.checkArrays(MstandardAnswersArray, studentAnswersArray1);
}
建立ArrayChecker物件:透過ArrayChecker checker1 = new ArrayChecker();建立了一個ArrayChecker類的物件,使用其中的checkArrays方法。
使用String[] MstandardAnswersArray = question.getStandardAnswer().split(" ");將題目物件的標準答案字串按照空格進行拆分,得到一個字串陣列MstandardAnswersArray,每個元素代表選擇題的一個正確選項。
然後比較答案陣列,呼叫ArrayChecker物件的checkArrays方法,即FLAG =checker1.checkArrays(MstandardAnswersArray, studentAnswersArray1);,將標準答案陣列和學生答案陣列作為引數傳入。使用checkArrays方法處理答案比較,回"true"(完全匹配)、"partially correct"(部分匹配)或"false"(不匹配)。然後將checkArrays方法的返回值賦給FLAG變數,作為選擇題答案匹配的最終結果。
如果是填空題型別(FillInBlankQuestion):
else if (question instanceof FillInBlankQuestion) {
if (question.getStandardAnswer().equals(studentAnswer)) {
FLAG = "true";
} else if ((studentAnswer.contains(question.getStandardAnswer()) || question.getStandardAnswer().contains(studentAnswer)) && studentAnswer.length() < question.getStandardAnswer().length()) {
FLAG = "partially correct";
} else {
FLAG = "false";
}
}
直接比較答案:首先透過if (question.getStandardAnswer().equals(studentAnswer))判斷學生給出的答案的標準答案完全相等。如果相等,將FLAG變數設定為"true",表示答案完全正確。
部分正確判斷:如果答案不完全相等,接著透過else if ((studentAnswer.contains(question.getStandardAnswer()) || question.getStandardAnswer().contains(studentAnswer)) && studentAnswer.length() <question.getStandardAnswer().length())進行部分正確的判斷。邏輯是如果學生答案包含標準答案或者標準答案包含學生答案,並且學生答案的長度小於標準答案的長度,那麼就認為答案是部分正確的,此時將FLAG變數設定為"partially correct"。
錯誤情況:如果上述兩個條件都不滿足,即學生答案既不與標準答案完全相等,也不符合部分正確的條件,那麼將FLAG變數設定為"false",表示答案錯誤。
如果是其他型別題目(既不是選擇題也不是填空題):
else {
if (question.getStandardAnswer().equals(studentAnswer)) {
FLAG = "true";
} else {
FLAG = "false";
}
}
簡單比較答案:透過if (question.getStandardAnswer().equals(studentAnswer))直接比較學生答案(studentAnswer)與題目物件的標準答案是否相等。如果相等,將FLAG變數設定為"true";如果不相等,將FLAG變數設定為"false",以此來確定答案的正確性。
最後,透過return FLAG;語句將儲存了答案匹配結果的FLAG變數返回給呼叫該方法的地方,以便根據這個結果進行輸出答案的正確與否資訊、計算得分等操作的處理。
3.類圖時序圖
改進建議
1、MultipleChoiceQuestion 和 FillInBlankQuestion 目前只是簡單繼承自 Question,可以考慮在子類中新增一些特定於題型的方法或屬性,以更好地體現不同題型的特點和處理邏輯。
2、正規表示式的複用與最佳化:程式碼中多處使用了正規表示式來驗證輸入格式,如在各個 Checher 類(NChecher、ZChecher 等)中。可以考慮將一些常用的正規表示式提取成常量,以便在多個地方複用,並且在編寫正規表示式時,可以進一步最佳化其準確性和簡潔性,避免過於複雜和難以理解的表示式,同時新增更詳細的註釋來解釋其用途和匹配規則。
3、在 CheckQuestion 類的 checkAnswer 方法中,對於不同題型答案的檢查邏輯可以進一步最佳化。目前程式碼中有一些重複的邏輯判斷,比如對於多選題和填空題部分邏輯相似但又分別實現。可以考慮提取公共的邏輯部分,透過多型或者其他設計模式來更優雅地處理不同題型答案的檢查,減少程式碼的冗餘。
4、在處理學生答案與問題匹配以及計算得分等邏輯中,存在一些相似的程式碼片段在不同的條件分支下重複出現,比如多次獲取學生答案、拆分答案字串、檢查問題是否存在及有效等操作。可以將這些重複的邏輯封裝成可複用的方法。
PTA第五次作業
設計與分析
題目分析
輸入格式:
1、裝置資訊:分別用裝置識別符號K、F、L、B、R、D分別表示開關、分檔調速器、連續調速器、白熾燈、日光燈、吊扇。引腳格式:裝置標識-引腳編號。
2、連線資訊一條連線資訊佔一行,用[]表示一組連線在一起的裝置引腳,引腳與引腳之間用英文空格" "分隔。
3、控制裝置調節資訊
開關調節資訊格式:#+裝置標識K+裝置編號
分檔調速器的調節資訊格式:#+裝置標識F+裝置編號+"+" 代表加一檔
連續調速器的調節資訊格式:#+裝置標識L+裝置編號+":" +數值
4、電源接地標識:VCC,電壓220V,GND,電壓0V。輸入資訊以end為結束標誌,
輸出格式:
按開關、分檔調速器、連續調速器、白熾燈、日光燈、吊扇的順序依次輸出所有裝置的狀態或引數。每個裝置一行。同類裝置按編號順序從小到大輸出。
輸出格式:@裝置標識+裝置編號+":" +裝置引數值(控制開關的檔位或狀態、燈的亮度、風扇的轉速,只輸出值,不輸出單位)
連續調速器的檔位資訊保留兩位小數,即使小數為0,依然顯示兩位小數.00。
開關狀態為0(開啟)時顯示turned on,狀態為1(合上)時顯示closed
分析:首先定義了一系列裝置相關的類,以裝置基類為基礎,派生出控制裝置類及其子類(開關、分檔調速器、連續調速器)和受控裝置類及其子類(燈、風扇的具體型別)。每個子類都根據自身裝置特性定義了相應的屬性和方法,比如控制裝置類用於調節狀態或檔位以及計算輸出電位的方法,受控裝置類用於根據引腳電壓差計算亮度或轉速的方法。在電路連線邏輯上,透過特定的資料結構來儲存裝置間的連線關係,依據輸入的連線資訊格式進行解析並記錄。輸入處理部分設定了讀取輸入、解析連線資訊、解析控制裝置調節資訊等函式,分別負責處理不同型別的輸入內容,確保準確識別並執行相應操作。輸出處理部分則有專門的函式按照規定順序和格式輸出各裝置的最終狀態或引數。最後在主函式中完成整體流程的排程,先進行輸入處理,再輸出所有裝置的狀態資訊,從而完整地模擬出智慧家居強電電路系統的執行情況。
知識點解析
1、類與繼承:程式碼大量運用了類來對不同的電氣裝置進行抽象建模,如BaseElectricalDevice作為基類,定義了裝置的一些公共屬性(裝置編號、裝置型別)和方法(showStatus)。其他各類具體裝置(如SwitchingDevice、StepSpeedRegulator等)都繼承自BaseElectricalDevice,繼承機制使得子類能夠複用基類的屬性和方法,並根據自身特性進行擴充套件和重寫,體現了物件導向程式設計中程式碼複用和多型性的特點。
除錯過程
2、連結串列(LinkedList):使用了LinkedList來儲存多種資料,如deviceIdList用於儲存裝置編號列表,uniqueDeviceIdList用於儲存唯一裝置編號列表,connectionList用於儲存連線列表,controlCommandList用於儲存控制命令列表,以及deviceList用於儲存裝置物件列表。
3、雜湊對映(LinkedHashMap):LinkedHashMap用於儲存裝置對映,其中鍵為裝置編號,值為對應的電氣裝置物件(BaseElectricalDevice及其子類物件)。
4、正規表示式,運用正規表示式來匹配和解析特定格式的輸入資料。
5、異常處理NumberFormatException:在處理需要將字串轉換為數字型別的操作時,考慮到可能出現的格式錯誤,透過try-catch語句捕獲NumberFormatException異常。
6、Comparator 介面:實現了Comparator介面來定義裝置比較器(DeviceComparator類),用於對裝置列表進行排序。
1.類的定義分析
BaseElectricalDevice類
作為所有電氣裝置類的基類,定義了電氣裝置的一些基本屬性和通用方法,為子類提供了一個基礎框架,便於實現多型性和程式碼複用。deviceId用於儲存裝置的編號,deviceType表示裝置的型別
public BaseElectricalDevice(String id, String type):用於初始化deviceId和deviceType屬性。
public String showStatus(BaseElectricalDevice device):為子類提供一個可重寫的模板方法,子類可以根據自身裝置的具體狀態或引數情況重寫該方法,實現多型性的輸出展示。
SwitchingDevice類
繼承自BaseElectricalDevice類,專門用於模擬開關裝置的行為和特性。
屬性有state布林型別變數,表示開關的狀態,true表示關閉狀態,false表示開啟狀態,初始化為false,即預設開關為開啟狀態。
public SwitchingDevice(String id):建構函式,呼叫父類的建構函式初始化裝置編號和裝置型別(“K”),同時設定開關的初始狀態為false。
public void flipState():用於切換開關的狀態,透過對state變數取反操作來實現,簡單有效地改變開關的開合狀態。
public double getOutputVoltage(double inputVolt):根據開關的當前狀態返回輸出電壓,準確模擬了開關對電壓傳輸的控制作用。
@Override public String showStatus(BaseElectricalDevice device):重寫了父類的showStatus方法,按照特定格式“@裝置編號:狀態(closed或turned on)”返回開關的狀態資訊。
StepSpeedRegulator類
繼承自BaseElectricalDevice類,用於模擬分檔調速器裝置的功能,如調節輸出電壓的檔位。
level整型變數,表示調速器的檔位,初始化為0,代表初始檔位為最低檔。
public StepSpeedRegulator(String id):建構函式,呼叫父類建構函式初始化裝置編號和裝置型別(“F”),並將檔位level初始化為0。
public double getOutputVoltage(double inputVolt):根據當前檔位level返回相應的輸出電壓。
public void adjustLevel(boolean increase):用於調整調速器的檔位。如果increase引數為true,則將檔位增加一檔;如果為false,則將檔位降低一檔。同時,透過條件判斷確保檔位值在有效範圍內(0到3之間),防止出現超出預期的檔位設定。
@Override public String showStatus(BaseElectricalDevice device):重寫父類的showStatus方法,按照格式“@裝置編號:檔位值”返回撥速器的當前檔位資訊。
ContinuouslyAdjustableDevice類
繼承自BaseElectricalDevice類,用於模擬連續調速器裝置的特性,能夠根據設定的引數值在一定範圍內連續調節輸出電壓。
value雙精度浮點型變數,表示連續調速器的引數值,初始化為0,用於確定輸出電壓與輸入電壓的比例關係。
public ContinuouslyAdjustableDevice(String id):建構函式,呼叫父類建構函式初始化裝置編號和裝置型別(“L”),並將引數值value初始化為0。
public void setParameterValue(double param):用於設定連續調速器的引數值,將傳入的引數param賦值給value變數,從而改變輸出電壓的比例關係,實現對輸出電壓的連續調節功能。
public double getOutputVoltage(double inputVolt):根據當前的引數值value和輸入電壓inputVolt計算並返回輸出電壓。
@Override public String showStatus(BaseElectricalDevice device):重寫父類的showStatus方法。
IncandescentLightDevice類
繼承自BaseElectricalDevice類,專門用於模擬白熾燈裝置的特性,根據輸入電壓設定並返回brightness整型變數,表示白熾燈的亮度,初始化為0,用於儲存根據輸入電壓計算得到的亮度值。
public IncandescentLightDevice(String id):建構函式,呼叫父類建構函式初始化裝置編號和裝置型別(“B”),並將亮度brightness初始化為0。
public void setBrightness(double inputVolt):根據輸入電壓inputVolt設定白熾燈的亮度。透過一系列條件判斷,按照不同的輸入電壓範圍採用不同的計算公式來確定亮度值。@Override public String showStatus(BaseElectricalDevice device):重寫父類的showStatus方法,按照格式“@裝置編號:亮度值”返回白熾燈的當前亮度資訊。
FluorescentLightDevice類
繼承自BaseElectricalDevice類,用於模擬日光燈裝置的特性,根據輸入電壓確定日光燈的亮度。brightness整型變數,表示日光燈的亮度,初始化為0,用於儲存根據輸入電壓計算得到的亮度值。
public FluorescentLightDevice(String id):建構函式,呼叫父類建構函式初始化裝置編號和裝置型別(“R”),並將亮度brightness初始化為0。
public void setBrightness(double inputVolt):根據輸入電壓inputVolt設定日光燈的亮度。@Override public String showStatus(BaseElectricalDevice device):重寫父類的showStatus方法,按照格式“@裝置編號:亮度值”返回日光燈的當前亮度資訊。
FanDevice類
繼承自BaseElectricalDevice類,用於模擬風扇裝置的特性,根據輸入電壓設定並返回風扇的轉速。
speed:整型變數,表示風扇的轉速,初始化為0,用於儲存根據輸入電壓計算得到的轉速值。
public FanDevice(String id):建構函式,呼叫父類建構函式初始化裝置編號和裝置型別(“D”),並將轉速speed初始化為0。
public void setRotationSpeed(double inputVolt):根據輸入電壓inputVolt設定風扇的轉速。
@Override public String showStatus(BaseElectricalDevice device):重寫父類的showStatus方法,按照格式“@裝置編號:轉速值”返回風扇的當前轉速資訊。
DeviceComparator類
實現了Comparator介面,用於定義裝置之間的比較規則,以便對裝置列表按照特定順序進行排序。
private static final String[] DEVICE_TYPE_ORDER = {"K", "F", "L", "B", "R", "D"};:定義了一個私有靜態常量陣列,用於儲存裝置型別的優先順序順序,按照這個順序來確定不同型別裝置之間的先後排序關係。
@Override public int compare(BaseElectricalDevice d1, BaseElectricalDevice d2):重寫了Comparator介面的compare方法,用於比較兩個BaseElectricalDevice型別的裝置。
private int getTypePriority(BaseElectricalDevice device):私有方法,用於獲取給定裝置在DEVICE_TYPE_ORDER陣列中的優先順序索引。
private int getDeviceIdNumber(BaseElectricalDevice device):私有方法,用於獲取裝置編號的數字部分。
2.重點程式碼程式碼具體分析
首先設計類:BaseElectricalDevice類,SwitchingDevice類,StepSpeedRegulator類,ContinuouslyAdjustableDevice類,IncandescentLightDevice類,FluorescentLightDevice類,FanDevice類,DeviceComparator類。
建立資料結構:
在main方法中,首先建立了多個LinkedList和一個LinkedHashMap用於儲存不同型別的輸入資訊。
// 建立一個 LinkedList 用於儲存裝置編號列表
LinkedList<String> deviceIdList = new LinkedList<>();
// 建立一個 LinkedList 用於儲存唯一裝置編號列表
LinkedList<String> uniqueDeviceIdList = new LinkedList<>();
// 建立一個 LinkedList 用於儲存連線列表
LinkedList<String> connectionList = new LinkedList<>();
// 建立一個 LinkedHashMap 用於儲存裝置對映,鍵為裝置編號,值為電氣裝置物件
LinkedHashMap<String, BaseElectricalDevice> deviceMap = new LinkedHashMap<>();
// 建立一個 LinkedList 用於儲存控制命令列表
LinkedList<String> controlCommandList = new LinkedList<>();
讀取使用者輸入並分類處理:
透過Scanner物件從控制檯迴圈讀取使用者輸入,直到輸入為“end”結束迴圈。
對於讀取到的每一行輸入,根據其格式進行分類處理:
如果輸入以“#”開頭,則將其作為控制命令新增到controlCommandList中。
如果輸入以“[”開頭,則透過正規表示式"(\w+)-(\d+)"匹配連線資訊中的裝置編號格式,提取出裝置編號新增到deviceIdList中,並將完整的連線資訊新增到connectionList中。
如果輸入不符合上述兩種格式,則輸出“Wrong Format”錯誤提示。
處理裝置編號去重及裝置物件建立:
在處理完所有輸入後,對deviceIdList進行去重操作,將不重複的裝置編號新增到uniqueDeviceIdList中。這裡透過兩層迴圈遍歷deviceIdList和uniqueDeviceIdList,比較每個元素是否相等,若不相等則新增到uniqueDeviceIdList中。
然後遍歷uniqueDeviceIdList,根據裝置編號的首字元判斷裝置型別,並建立相應型別的電氣裝置物件,新增到deviceMap中。
for (String str : deviceIdList) {
isDuplicate = false;
for (String uniqueStr : uniqueDeviceIdList) {
if (str.equals(uniqueStr)) {
isDuplicate = true;
break;
}
}
if (!isDuplicate) {
uniqueDeviceIdList.add(str);
}
}
根據控制命令操作裝置:
遍歷controlCommandList中的控制命令,根據命令的字首判斷命令型別,並對相應裝置進行操作:
如果命令以“#L”開頭,則解析出連續調速器的裝置編號和要設定的引數值,若裝置對映deviceMap中包含該裝置編號,則透過((ContinuouslyAdjustableDevice) deviceMap.get(deviceId)).setParameterValue(parameterValue)設定連續調速器的引數值,否則輸出“Wrong Format”錯誤提示。
如果命令以“#F”開頭,則解析出分檔調速器的裝置編號以及是升檔還是降檔操作,若裝置對映中包含該裝置編號,則透過((StepSpeedRegulator) deviceMap.get(fId)).adjustLevel(isIncrease)調整分檔調速器的檔位,否則輸出“Wrong Format”錯誤提示。
如果命令以“#K”開頭,則解析出開關裝置的編號,若裝置對映中包含該裝置編號,則透過((SwitchingDevice) deviceMap.get(switchId)).flipState()切換開關狀態,否則輸出“Wrong Format”錯誤提示。
模擬電壓傳遞:
建立一個LinkedList型別的deviceList,將deviceMap中的所有裝置物件新增到其中。
設定輸入電壓為220,然後遍歷deviceList,根據裝置型別呼叫相應裝置的getOutputVoltage方法模擬電壓的傳遞,更新輸入電壓的值。<baseelectricaldevice>
for (BaseElectricalDevice device : deviceList) {
switch (device.deviceType) {
case "K":inputVoltage = ((SwitchingDevice) device).getOutputVoltage(inputVoltage);break;
case "F":inputVoltage = ((StepSpeedRegulator) device).getOutputVoltage(inputVoltage);break;
case "L":inputVoltage = ((ContinuouslyAdjustableDevice) device).getOutputVoltage(inputVoltage);break;
}
}
設定裝置狀態(亮度、轉速):再次遍歷deviceList,根據裝置型別呼叫相應裝置的setBrightness(對於燈裝置)或setRotationSpeed(對於風扇裝置)方法,根據當前的輸入電壓設定裝置的狀態(如白熾燈的亮度、風扇的轉速等)。
for (BaseElectricalDevice device : deviceList) {
switch (device.deviceType) {
case "B":((IncandescentLightDevice) device).setBrightness(inputVoltage);break;
case "R":((FluorescentLightDevice) device).setBrightness(inputVoltage);break;
case "D":((FanDevice) device).setRotationSpeed(inputVoltage);break;
}
}
對裝置列表排序:
使用DeviceComparator對deviceList進行排序,使得裝置按照先按裝置型別優先順序,型別相同再按裝置編號大小的順序排列。
輸出各裝置狀態或引數:遍歷排序後的deviceList,呼叫每個裝置的showStatus方法,按照各自重寫的格式輸出每個裝置的狀態或引數資訊。
Collections.sort(deviceList, new DeviceComparator());
for (BaseElectricalDevice device : deviceList) {
System.out.println(device.showStatus(device));
}
3.類圖時序圖
改進建議
裝置操作邏輯最佳化:在處理不同型別裝置的操作(如設定引數、調整檔位、切換開關等)時,目前是透過多個if-else或switch語句進行判斷。可以考慮使用策略模式或工廠模式等設計模式來最佳化這部分邏輯,使得程式碼的擴充套件性更好,當需要新增新的裝置型別或操作時,更容易進行維護。
考慮使用集合框架的其他特性:在處理裝置編號去重時,目前使用了雙層迴圈來判斷是否重複並新增到唯一裝置編號列表。可以考慮使用HashSet等資料結構來自動去重,這樣程式碼會更加簡潔高效。
正規表示式複用:在解析連線資訊和處理控制命令時,都使用了正規表示式。可以考慮將常用的正規表示式提取成常量,以便複用,同時也方便後續如果需要修改正規表示式的模式,只需要在一處修改即可。
方法抽取:在Main類的main方法中,部分邏輯可以抽取成獨立的方法,例如處理使用者輸入並分類新增到不同列表的邏輯、根據裝置編號建立對應裝置物件並新增到對映的邏輯等。
PTA第六次作業
設計與分析
題目分析
迭代內容:
- 裝置型別增加:在受控裝置方面,題目 2 在原有基礎上新增了 “落地扇” 這一裝置型別,需要在程式碼中對其進行相應的模擬和處理,包括其工作電壓與轉速的對應關係、電阻等特性的設定。
- 電路連線方式擴充:題目 1 只考慮串聯形式的電路連線,而題目 2 不僅考慮串聯,還明確提到要考慮各類裝置的並聯接入情況,這使得電路連線的複雜性大大增加,需要在程式碼中實現對並聯電路以及其包含的串聯電路的解析、處理和模擬電流在並聯電路中的分配等邏輯。
3、輸入資訊格式改變:題目 2 的連線資訊不再單獨輸入,而是包含在串聯電路資訊和並聯電路資訊中。串聯電路資訊需按從靠電源端到接地端順序依次輸入連線資訊,且有特定的格式規範;並聯電路資訊則由其包含的幾條串聯電路組成,也有相應格式。 - 裝置屬性增加:題目 2 新增了各裝置電阻屬性,白熾燈的電阻為 10,日光燈的電阻為 5,吊扇的電阻為 20,落地扇的電阻為 20。在類比電路工作過程中,需要根據這些電阻值來計算電流、電壓分配等情況,這在程式碼實現上需要增加相應的屬性設定和相關計算邏輯。
程式碼方面分析:
1、新增裝置類:需要建立一個新的 “落地扇” 裝置類,類似於已有的吊扇裝置類,定義其工作電壓與轉速的對應關係、電阻屬性以及根據輸入電壓計算轉速等方法
2、裝置基類屬性新增:在裝置基類(如BaseElectricalDevice)中需要新增一個表示電阻的屬性
3、輸入邏輯:需要更新程式碼實現串聯電路資訊的解析和並聯電路資訊的解析
4、處理並聯電路邏輯:在類比電路工作過程中,當涉及到並聯電路時,需要根據並聯電路的特性來更新程式碼邏輯
5、考慮裝置電阻的計算:由於新增了裝置電阻屬性,在類比電路工作時,需要根據歐姆定律等電學原理,結合裝置的電阻、輸入電壓等資訊來計算電流等引數。
6、輸出部分的更新:在輸出所有裝置的狀態或引數時,需要按照題目 2 的要求,新增對落地扇裝置狀態的輸出,確保輸出順序為開關、分檔調速器、連續調速器、白熾燈、日光燈、吊扇、落地扇,並遵循相應的輸出格式規範。
知識點解析
多型性:透過在子類中重寫基類的方法(showmethod、regulate等方法在不同子類中有不同的實現)來體現多型性。
ArrayList:廣泛使用了ArrayList來儲存各種資料,如connectionList、parallelList、controlList等。
HashMap 與 LinkedHashMap:使用HashMap(如parallelCircuits、mainCircuit、finalCircuit等)和LinkedHashMap儲存鍵值對形式的資料。
自定義比較器(Comparator 介面):定義了內部類DeviceComparator實現Comparator介面,用於比較兩個BasEledevice物件的優先順序順序。<baseledevice>
除錯過程
1. 類的定義分析
BasEledevice類:作為所有電氣裝置類的基類,定義了一些通用的屬性和方法,這些屬性和方法會被繼承它的子類所共享或重寫。
屬性:
type:表示裝置的型別。
id:裝置的唯一識別符號。
voltage:裝置的電壓,預設值為 220。
ifopen:表示裝置是否開啟,預設值為"turned on"。
speed:裝置的速度,預設值為 0,部分子類會根據具體情況使用該屬性。
value:裝置的某個數值,預設值為 0.00,部分子類會根據具體情況使用該屬性。
resistance:裝置的電阻值,在建構函式中初始化。
方法:
showmethod():在基類中為空實現,子類會根據自身需求重寫該方法以展示裝置的相關資訊。
regulate(String vs):在基類中為空實現,子類會根據自身需求重寫該方法來實現對裝置的調節功能。
setVoltage(double voltage):用於設定裝置的電壓值。
getReturnTypeValue():根據裝置的type屬性,返回一個對應的值,用於確定裝置型別在優先順序順序中的索引。
SwitchDevice類:表示開關裝置,繼承自BasEledevice類。
建構函式:呼叫父類建構函式,將type設定為"K"。
重寫的方法:
showmethod():重寫父類方法,用於輸出開關裝置的開啟狀態資訊。
setVoltage(double voltage):根據裝置的開啟狀態來設定電壓。
StepSpeedRegulator類:表示步進速度調節器裝置,繼承自BasEledevice類。
建構函式:呼叫父類建構函式,將type設定為"F"。
重寫的方法:
showmethod():重寫父類方法,用於輸出速度調節器的當前速度資訊。
regulate(String vs):根據傳入的調節指令vs("+"或"-")來增加或減少裝置的速度,速度範圍限制在 0 到 3 之間。
setVoltage(double voltage):根據裝置的速度來計算並設定電壓。
ConAdjustDevice類:表示連續調節裝置,繼承自BasEledevice類。
建構函式:呼叫父類建構函式,將type設定為"L"。
重寫的方法:
showmethod():重寫父類方法,用於輸出連續調節裝置的當前調節值資訊。
regulate(String vs):將傳入的字串vs轉換為雙精度浮點數,並賦值給裝置的value屬性,實現對裝置的調節。
setVoltage(double voltage):根據裝置的value屬性來計算並設定電壓。
WhiteLightDevice類:表示白熾燈裝置,繼承自BasEledevice類。
建構函式:呼叫父類建構函式,將type設定為"B",並設定裝置的電阻值為 10。
重寫的方法:
showmethod():根據裝置的電壓值計算並輸出白光燈的亮度資訊。
setVoltage(double voltage):呼叫父類的setVoltage方法設定電壓。
SunLightDevice類:表示日光燈裝置,繼承自BasEledevice類。
建構函式:呼叫父類建構函式,將type設定為"R",並設定裝置的電阻值為 5。
重寫的方法:
showmethod():根據裝置的電壓值計算並輸出太陽光模擬器的亮度資訊。
setVoltage(double voltage):呼叫父類的setVoltage方法設定電壓。
FanDevice類表示風扇裝置,繼承自BasEledevice類。
建構函式:呼叫父類建構函式,將type設定為"D",並設定裝置的電阻值為 20。
重寫的方法:
showmethod():根據裝置的電壓值計算並輸出風扇的速度資訊。
setVoltage(double voltage):呼叫父類的setVoltage方法設定電壓。
GroundFanDevice類表示落地扇裝置,繼承自BasEledevice類。
建構函式:呼叫父類建構函式,將type設定為"A",並設定裝置的電阻值為 20。
重寫的方法:
showmethod():根據裝置的電壓值計算並輸出落地扇的速度資訊。
setVoltage(double voltage):呼叫父類的setVoltage方法設定電壓。
BINGconnect類:表示並聯,繼承自BasEledevice類,建構函式將type設定為"M"。
CHUANconnect類:表示串聯,繼承自BasEledevice類,建構函式將type設定為"T"。
2. 重點程式碼程式碼具體分析
設定BasEledevice類和各類裝置子類。
輸入處理部分
首先建立了幾個ArrayList用於儲存不同型別的輸入資訊,分別是connectionList(用於儲存電路連線相關資訊)、parallelList(用於儲存並行電路相關資訊)和controlList(用於儲存控制裝置的指令資訊)。
透過Scanner類從控制檯讀取使用者輸入的字串,每次讀取一行並去除首尾空格後進行處理。根據輸入內容是否符合特定的格式(透過isEndInput、isTInput、isMInput、isKInput、isFInput、isLInput方法判斷),將其分別交給handleTInput、handleMInput、handleKInput、handleFInput、handleLInput處理方法進行處理。
while (true) {
input = in.nextLine().trim();
if (isEndInput(input)) {break;}
if (isLInput(input)) {handleLInput(input, controlList);}
if (isFInput(input)) {handleFInput(input, controlList);}
if (isKInput(input)) {handleKInput(input, controlList);}
if (isMInput(input)) {handleMInput(input, parallelList);}
if (isTInput(input)) {handleTInput(input, connectionList);}
}
電路構建部分
處理電路連線資訊(connectionList):
遍歷connectionList中的每個元素,先透過正規表示式Pattern.compile("(.)😦.)")解析出電路部分和對應的值部分。
然後進一步處理值部分,根據其格式判斷是輸入型別還是電源型別並根據具體情況將對應的裝置新增到circuitMap中(透過addElectricToMap方法,該方法根據裝置編號的首字母建立相應型別的裝置物件並新增到map中)。
Pattern pattern = Pattern.compile("(.*):(.*)");
Matcher matcher = pattern.matcher(circuitPart);
如果處理的是輸入型別且滿足特定條件(如包含OUT)則停止處理當前元素;如果是電源型別且滿足特定條件(如包含GND)則停止處理當前元素。
處理完一個元素後,如果是輸入型別則將構建好的circuitMap新增到parallelCircuits中(以電路部分作為鍵);如果是電源型別則將circuitMap賦值給mainCircuit。
if (connectionType.length() >= 3 && connectionType.charAt(0) == '[' && connectionType.charAt(1) == 'I' && connectionType.charAt(2) == 'N')
if (connectionType.length() >= 4 && connectionType.charAt(0) == '[' && connectionType.charAt(1) == 'V' && connectionType.charAt(2) == 'C' && connectionType.charAt(3) == 'C')
boolean isOutSubPart = subParts.length > 1 && subParts[1].equals("OUT")
if (subParts.length > 1 && subParts[1].equals("GND"))
處理並行電路資訊(parallelList):
遍歷parallelList中的每個元素,先提取出前兩個字元作為keyPrefix。然後透過正規表示式Pattern.compile("(.)\s\[(.)]")解析出值部分,並進一步將值部分按空格分割成多個子部分。
Matcher matcher = pattern.matcher(parallelList.get(parallelIndex));
boolean matchFound = false;
while (matcher.find()) {partValue = String.valueOf(matcher.group(2));matchFound = true;}
遍歷這些子部分,對於每個子部分,在parallelCircuits中查詢與之匹配的鍵(即電路部分),如果找到匹配的鍵,則將對應的circuitMap新增到allParallelCircuits中,鍵為keyPrefix + index(index是遍歷子部分的索引)。
if (entryKey.equals(parts[index])) {keysEqual = true;}
if (keysEqual) {allParallelCircuits.put(keyPrefix + index, entry.getValue());}
裝置控制與狀態更新部分
遍歷controlList中的每個控制指令,對於每個指令:
首先判斷指令的型別(根據首字母是否為K、F、L等),然後在allParallelCircuits和mainCircuit中查詢對應的裝置物件。
如果是K型別(開關裝置)的指令,找到對應的裝置後,根據其當前開關狀態(ifopen屬性)進行狀態切換。
if (isKStart) {
String key = "";
if (controlId.length() >= 2) {
key = controlId.substring(0, 2);
}
BasEledevice electric = entry.getValue().get(key);
if (electric!= null) {
boolean isTurnedOn = false;
if (electric.ifopen!= null && electric.ifopen.equals("turned on")) {
isTurnedOn = true;
}
if (isTurnedOn) {
electric.ifopen = "closed";
} else {
electric.ifopen = "turned on";
}
break;
}
}
如果是F型別(調速裝置)的指令,找到對應的裝置後,根據指令中攜帶的+或-符號呼叫裝置的regulate方法來調節速度,並根據調節情況更新一個flag變數。
如果是L型別(引數調節裝置)的指令,找到對應的裝置後,呼叫裝置的regulate方法根據指令中的值來設定裝置的引數(value屬性)。
電阻與電壓計算部分
計算並聯電阻相關資訊:
建立parallelResistances陣列用於儲存各並行電路的電阻資訊。遍歷allParallelCircuits中的每個元素,對於每個並行電路中的裝置:
double[] parallelResistances = new double[allParallelCircuits.size()];
for (Entry<String, Map<String, BasEledevice>> entry : allParallelCircuits.entrySet()) {
flag = 0;
parallelResistances[i] = 0;
for (Entry<String, BasEledevice> entry1 : entry.getValue().entrySet()) {
if (entry1.getKey().startsWith("K")) {
if (entry1.getValue().ifopen.equals("turned on"))
flag = 1;
if (entry1.getValue().ifopen.equals("closed"))
allClosedCount++;
}
}
if (allClosedCount == entry.getValue().size())
parallelResistances[i] = 0;
if (flag == 1)
parallelResistances[i] = -1;
else {
for (Entry<String, BasEledevice> entry1 : entry.getValue().entrySet()) {
parallelResistances[i] += entry1.getValue().resistance;
}
}
i++;
}
如果裝置的鍵以K開頭(即開關裝置),根據其開關狀態(ifopen屬性)來更新一些標誌變數(如flag和allClosedCount)。
根據這些標誌變數的值來確定當前並行電路的電阻值,如果所有裝置都關閉(allClosedCount等於裝置數量)則電阻為0;如果有裝置開啟(flag為1)則電阻設為-1;否則將各裝置的電阻相加得到當前並行電路的電阻值,並儲存到parallelResistances陣列中。
接著計算parallelReciprocalSum,即各並行電路電阻倒數之和。
計算主電路電阻相關資訊:
建立mainCircuitResistance陣列(只有一個元素)用於儲存主電路的電阻值。遍歷mainCircuit中的每個裝置,透過processMainCircuitEntry方法處理每個裝置,該方法主要處理開關裝置(如果開關開啟則將主電路電阻設為0並返回,否則將裝置電阻累加到主電路電阻值上)。
計算串聯和並聯電壓值:
根據前面計算得到的主電路電阻值(透過calculateSeriesResistance方法獲取)和並聯電阻相關資訊(透過calculateParallelResistance方法根據parallelReciprocalSum計算並聯電阻),利用歐姆定律相關公式分別計算串聯電壓(calculateSeriesVoltage方法)和並聯電壓(calculateParallelVoltage方法)。
double seriesResistance = calculateSeriesResistance(mainCircuitResistance);
double parallelResistance = calculateParallelResistance(parallelReciprocalSum);
double seriesVoltage = calculateSeriesVoltage(supplyVoltage, seriesResistance, parallelResistance);
double parallelVoltage = calculateParallelVoltage(supplyVoltage, parallelResistance, seriesResistance);
裝置電壓設定與最終輸出部分
遍歷mainCircuit中的每個裝置(透過裝置編號partId):
for (Entry<String, BasEledevice> entry : mainCircuit.entrySet())
根據裝置編號的首字母或是否以特定字元開頭(如K、L、F、A、D、R、B、M等)來確定裝置型別,並根據前面計算得到的串聯、並聯電壓以及裝置自身電阻等資訊,呼叫裝置的setVoltage方法來設定裝置的實際工作電壓。
如果裝置編號以M開頭,還需要遍歷allParallelCircuits中的每個元素,根據parallelReciprocalSum的值以及各並行電路中的裝置情況,分別呼叫setDeviceVoltageAndHandle、setDeviceVoltage或handleDeviceVoltageSetting等方法來設定裝置電壓,並將裝置新增到finalCircuit中。
else if (partId.startsWith("M"))
for (Entry<String, Map<String, BasEledevice>> entry6 : allParallelCircuits.entrySet())
建立finalList,將finalCircuit中的所有裝置新增到finalList中。
Map<String, BasEledevice> finalCircuit = new HashMap<>();
if (partId.startsWith("K")) {
BasEledevice elc = mainCircuit.get(partId);
elc.setVoltage(supplyVoltage);
supplyVoltage = elc.voltage;
seriesVoltage = calculateSeriesVoltage(supplyVoltage, seriesResistance, parallelResistance);
parallelVoltage = calculateParallelVoltage(supplyVoltage, parallelResistance, seriesResistance);
finalCircuit.put(partId, elc);
}
建立一個自定義的比較器DeviceComparator,該比較器實現了Comparator介面,用於比較兩個BasEledevice物件的優先順序順序。
使用Collections.sort方法根據自定義的比較器對finalList進行排序,最後遍歷排序後的finalList,呼叫每個裝置的showmethod方法來輸出裝置的狀態資訊。
Collections.sort(finalList, new DeviceComparator());
<baseledevice>
for (BasEledevice electric : finalList) {
electric.showmethod();
}
輔助方法功能
addElectricToMap方法:根據傳入的連線編號(connectionId)的首字母建立相應型別的裝置物件,並將其新增到傳入的map中,返回更新後的map。
handleTInput、handleMInput、handleKInput、handleFInput、handleLInput方法:這些方法分別用於處理以#T、#M、#K、#F、#L開頭的輸入字串,透過正規表示式解析輸入字串,並將相關部分新增到對應的ArrayList(如connectionList、parallelList、controlList)中。
processMainCircuitEntry方法:用於處理主電路中的每個裝置,主要處理開關裝置對主電路電阻的影響,如果開關開啟則將主電路電阻設為0並返回,否則將裝置電阻累加到主電路電阻值上。
checkSwitchStatus方法:目前該方法內只有一個空的條件判斷(如果裝置的ifopen屬性等於turned on),可能是預留用於後續進一步處理開關裝置狀態相關的邏輯,但在當前程式碼中沒有實際的完整功能。
calculateSeriesResistance、calculateParallelResistance、calculateSeriesVoltage、calculateParallelVoltage方法:分別用於計算串聯電阻值、並聯電阻值、串聯電壓值和並聯電壓值,這些計算基於歐姆定律相關公式以及前面步驟中計算得到的電阻、電壓等相關引數。
setDeviceVoltageAndHandle、setDeviceVoltage、handleDeviceVoltageSetting方法:這些方法用於根據不同的條件(如裝置型別、電路連線情況等)設定裝置的電壓,並將裝置新增到finalCircuit中,以便後續輸出裝置的狀態資訊。
3. 類圖時序圖
改進建議
1、模組化處理:當前程式碼的 main 方法較為冗長,包含了多種不同功能的邏輯,如輸入處理、電路構建、裝置控制、引數計算以及結果展示等。可以考慮將這些不同功能的程式碼提取成獨立的方法。
踩坑心得
第四次作業
1、正規表示式匹配問題:
處理普通問題的 NChecher 類中的正規表示式 #N:(\s\d+\s)#Q:(.)#A:(.),使用者輸入的問題編號前後的空白格式不符合預期,影響了正規表示式的匹配邏輯,就導致無法正確提取資訊並建立對應的 Question 物件。
在使用正規表示式時,要充分考慮到各種可能的輸入情況,包括不同的空白格式、特殊字元等。在編寫正規表示式後,應該使用多種邊界情況的測試資料進行反覆測試,確保其能準確匹配預期的所有有效輸入格式。
2、對於多選題判斷問題:
假設標準答案是 ["A", "B"],學生答案是 ["A", "C"]。在程式碼中處理多選題答案檢查的邏輯因為對 ArrayChecker 類的使用不當或者對部分條件判斷的遺漏(沒有考慮到學生答案陣列為空的情況),導致錯誤地判斷答案的正確性,輸出不符合實際情況的結果。
第五次作業
1、輸入格式處理相關問題:
對於控制命令的解析,像 #L 命令用於設定連續調速器裝置的引數值,在最初的實現中(如註釋掉的舊程式碼部分)可能存在對引數值提取和轉換不夠健壯的問題。
在處理使用者輸入時,要充分考慮到各種可能的輸入情況,尤其是涉及到資料型別轉換的部分(如從字串轉換為數字型別)。對於輸入格式的驗證應該更加嚴格和全面,不僅要判斷格式是否符合預期的模式,還要對提取出來的資料進行合法性檢查。
2、控制命令執行邏輯問題:
用於控制分檔調速器裝置的檔位調整,在最初的實現的程式碼沒有準確解析,改進後的程式碼中,準確解析命令中的資訊來確定是增加還是降低檔位以及對應的裝置編號。
第六次作業
1、電阻計算與電壓分配邏輯問題:
在計算並聯電阻值時,透過 calculateParallelResistance 方法根據 parallelReciprocalSum 來計算。在計算 parallelReciprocalSum 的過程中,對各個支路電阻的處理出現錯誤,導致並聯電阻值計算錯誤,進而影響到後續的電壓分配計算,使輸出的裝置電壓等引數不符合實際電路情況。比如在計算 parallelReciprocalSum 時,錯誤地只考慮了支路 1 的電阻,遺漏了支路 2 的電阻,計算出的並聯電阻值就會錯誤。
透過手動計算一些簡單的電路場景示例,並與程式計算結果進行對比,來驗證程式碼的準確性。
總結
透過這次三次PTA大作業,首先,我學到了合理使用瞭如LinkedHashMap、LinkedList等資料結構來儲存和管理相關資料。還有類的繼承與多型,關於電氣裝置的實現部分,透過定義基類以及多個子類(如各種具體的電氣裝置類),體現了類的繼承關係。子類繼承基類的屬性和方法,並可以根據自身特點重寫某些方法,展示了多型性的應用。在對試卷總分的計算、學生答案與標準答案的比對及評分等邏輯處理,根據不同裝置的特性(開關裝置的狀態切換、調速器的檔位調整、各種裝置根據輸入電壓計算輸出電壓或設定自身狀態)處理中,加深了資料處理與計算,同時,掌握了自定義的比較器(DeviceComparator)的就技巧。
雖然目前的程式碼在一定程度上實現了功能的模組化和分層,但在面對更復雜的業務需求變化時,我需要進一步引入設計模式來提高程式碼的可擴充套件性。同時,還要最佳化資料結構選擇、異常處理機制、多執行緒與併發處理等。
在程式碼的除錯過程中,一步一步的解決問題,紅色的字不斷閃現,“山重水複疑無路,柳暗花明又一村”的感覺令我放鬆,我愛java!