由“交卷”功能引發的思考——對比兩個字符串數組的差異

最近在做一個答題系統,在交卷的時候需要判斷客觀題的答題情況

客觀題的題型有單選題、多選題、判斷題

其中判斷題可以當做單選題處理,而單選題也可以當做標準答案長度爲一的多選題

所以最終只需要實現多選題的判定即可

 

一、需求分析

將標準答案和考生回答分別記爲字符串數組 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;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章