最近在做一個答題系統,在交卷的時候需要判斷客觀題的答題情況
客觀題的題型有單選題、多選題、判斷題
其中判斷題可以當做單選題處理,而單選題也可以當做標準答案長度爲一的多選題
所以最終只需要實現多選題的判定即可
一、需求分析
將標準答案和考生回答分別記爲字符串數組 standard 和 answer
經過分析,多選題可能出現的情況有:
1. 完全正確
answer 中的所有元素都能在 standard 中找到,且 answer 和 standard 長度相等
2. 部分正確
answer 中的所有元素都能在 standard 中找到,但 answer 的長度小於 standard
3. 回答錯誤
answer 中至少有一個元素不在 standard 中
爲了處理以上情況,可以設計一個排除 standard 和 answer 中重複項的函數,並基於各自剩下的元素來判斷結果
/**
* 去除兩個數組的重複項, 並返回處理後的兩個數組
* ```ts
* 輸入: [ ['A', 'B'], ['A', 'C'] ]
* 輸出: [ ['B'], ['C'] ]
* ```
* @param data [string[], string[]]
* @returns [string[], string[]]
*/
function compareArray(data: [string[], string[]]): [string[], string[]] {
const [standard, answer] = data || [];
// ...
}
二、函數實現
這個 compareArray 函數內部邏輯和數組去重的過程很類似
// 關於數組去重的方案可以參考《JavaScript 高性能數組去重》
需要遍歷數組 A, 拿每個元素去和數組 B 對比, 如果重複則在兩個數組中都刪除該元素
1. 基礎版:
使用 indexOf 在 B 數組中查找重複項
若存在重複項,則將元素置空,最後使用 filter 過濾
不建議使用 splice 刪除元素,因爲它會修改原數組,不易維護遍歷的過程
function compareArray(data: [string[], string[]]): [string[], string[]] {
const [standard, answer] = data || [];
// 拷貝原數組
const _standard = [...standard];
const _answer = [...answer];
for (let i = 0; i < _standard.length; i++) {
const k = _standard[i];
const answerIndex = _answer.indexOf(k);
if (answerIndex < 0) continue;
// 如果存在重複元素, 則將對應的元素標記爲空
_standard[i] = '';
_answer[answerIndex] = '';
}
// 過濾空元素
return [
_standard.filter((x) => !!x),
_answer.filter((x) => !!x),
];
}
2. 進階版
上面的基礎版使用了 indexOf 來查找重複項,會存在重複遍歷的情況
可採用空間換時間的思路,改用對象來判斷重複項
另外還可以使用增量存儲的方式,記錄 standard 中未重複的元素,這樣就可以省略最後一步的 filter
function compareArray(data: [string[], string[]]): [string[], string[]] {
const [standard, answer] = data || [];
// 用於記錄 standard 數組的處理結果(增量存儲)
const _standard: string[] = [];
// 將 answer 轉爲對象便於查詢, 需要手動刪除重複值
const _answerMap: Record<string, true> = answer.reduce(
(res, x) => ({ ...res, [x]: true }),
{},
);
for (let i = 0; i < standard.length; i++) {
const k = standard[i];
// 如果存在重複值, 則刪除 answerMap 中的鍵
if (_answerMap[k]) {
Reflect.deleteProperty(_answerMap, k);
} else {
// 如果存在不重複, 則記錄到結果數組中
_standard.push(k);
}
}
return [_standard, Object.keys(_answerMap)];
}
三、結果判斷
文章的開頭已經提到,多選題的結果存在這三種情況
/** 答題結果 */
export enum ANSWER_RESULT_ENUM {
/** 完全正確 */
CORRECT = '1',
/** 部分正確 */
NOTBAD = '2',
/** 回答錯誤 */
WRONG = '3',
}
結合上面的 compareArray 函數,可以這麼來判斷答題結果:
type AnswerItem = string[];
/** 檢查單個問題的答題情況 */
function markingPapers(
answer: AnswerItem,
standard: AnswerItem,
): ANSWER_RESULT_ENUM {
// 未作答
if (!answer?.length) {
return ANSWER_RESULT_ENUM.WRONG;
}
const [_answer, _standard] = compareArray([answer, standard]);
// 完全正確
if (!_standard.length && !_answer.length) {
return ANSWER_RESULT_ENUM.CORRECT;
}
// 部分正確
if (!_answer.length && _standard.length > 0) {
return ANSWER_RESULT_ENUM.NOTBAD;
}
// 回答錯誤
return ANSWER_RESULT_ENUM.WRONG;
}