由“交卷”功能引发的思考——对比两个字符串数组的差异

最近在做一个答题系统,在交卷的时候需要判断客观题的答题情况

客观题的题型有单选题、多选题、判断题

其中判断题可以当做单选题处理,而单选题也可以当做标准答案长度为一的多选题

所以最终只需要实现多选题的判定即可

 

一、需求分析

将标准答案和考生回答分别记为字符串数组 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;
}

 

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