Java实现选择题选项乱序算法

前言

在开发在线考试或问答系统时,经常会遇到一个痛点:考生容易通过背诵选项字母(例如“第一题选C,第二题选A”)来作弊或应付考试。

为了解决这个问题,我们需要在系统渲染试卷时,将题目的选项顺序随机打乱。

这里的核心难点在于:选项顺序打乱后,系统必须依然能准确识别出哪一个是正确答案。

这就好比给房间里的物品换门牌号:物品本身没动,只是门上的字母标签换了,但我们需要时刻追踪那个存放“正确答案”的房间现在挂着什么字母。


一、 核心思路

原标签 (originalKey) 新标签 (newKey) 选项内容 (value) 是否为正确答案
A C JAVA2 EE
B A JAVA2 Card
C D JAVA2 ME
D B JAVA2 HE ✓ → 触发记录 keyNew = "B"

内容没有动,只是换了门牌号(字母标签)。正确答案的内容跟着走到了 B,所以正确的选项 Key 也同步更新为 B。


二、核心代码实现

为了实现这个功能,我们首先需要一个数据结构来承载选项和答案。

2.1 定义数据承载类 Topic

这个类非常简单,主要用于封装选项的 Map(选项字母 -> 选项内容)和正确的 key

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import java.util.Map;

public class Topic {
private Map<String, String> option; // 选项映射,例如 {"A": "JAVA2 EE", ...}
private String key; // 正确答案的选项字母

public Topic() {}

public Topic(Map<String, String> option, String key) {
this.option = option;
this.key = key;
}

public Map<String, String> getOption() { return option; }
public void setOption(Map<String, String> option) { this.option = option; }
public String getKey() { return key; }
public void setKey(String key) { this.key = key; }
}

2.2 乱序工具类 TopicRandomUtil

这是整个乱序逻辑的引擎。利用 Collections.shuffle() 来实现“摇匀门牌号”的操作。

在 Java 中,HashMapkeySet() 是无序的,它既不保证插入顺序,也不保证字母顺序。它仅仅是根据 Key 的哈希值(Hash Code)计算出的数组下标来存放的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import java.util.*;

public class TopicRandomUtil {

/**
* 乱序Map元素,记录对应答案key
* @param option 原题目选项
* @param key 原正确答案
* @return Topic 乱序后的选项和新的正确答案
*/
static public Topic random(Map<String, String> option, String key) {
// 1. 获取原有的门牌号集合 (A, B, C, D)
Set<String> keySet = option.keySet();
ArrayList<String> keyList = new ArrayList<>(keySet);

// 2. 将门牌号列表乱序 (例如变成了 C, A, D, B)
Collections.shuffle(keyList);

// 3. 准备一个新的Map来存放换好门牌号的选项
HashMap<String, String> optionNew = new HashMap<>();

int idx = 0;
String answerNew = ""; // 用于记录新的正确答案门牌号

// 4. 遍历原有的门牌号,依次贴上新的门牌号
for (String originKey : keySet) {
String newKey = keyList.get(idx++);

// 5. 判断当前被替换门牌号的原选项,是不是正确答案
if (key.equals(originKey)) {
answerNew = newKey; // 如果是,记录它贴上的新门牌号
}

// 6. 将新门牌号和原内容存入新Map
optionNew.put(newKey, option.get(originKey));
}

return new Topic(optionNew, answerNew);
}
}

算法可视化流程:

image-20260425150613153


3. 实际业务场景模拟验证

为了直观看到效果,我们编写一个简单的 main 方法来模拟这个过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import java.util.HashMap;
import java.util.Map;

public class ExamSimulation {

public static void main(String[] args) {
// 1. 模拟从数据库中读取了一道题
Map<String, String> originalOptions = new HashMap<>();
originalOptions.put("A", "JAVA2 EE");
originalOptions.put("B", "JAVA2 Card");
originalOptions.put("C", "JAVA2 ME");
originalOptions.put("D", "JAVA2 HE");

String originalKey = "D"; // 原正确答案是 D: JAVA2 HE

System.out.println("====== 打乱前 ======");
System.out.println("题目选项: " + originalOptions);
System.out.println("正确答案: " + originalKey + " -> " + originalOptions.get(originalKey));

// 2. 使用乱序器打乱选项
Topic randomizedTopic = TopicRandomUtil.random(originalOptions, originalKey);

// 3. 打印打乱后的结果
System.out.println("\n====== 打乱后 ======");
System.out.println("题目选项: " + randomizedTopic.getOption());
System.out.println("正确答案: " + randomizedTopic.getKey() + " -> " + randomizedTopic.getOption().get(randomizedTopic.getKey()));

// 验证:内容是否依然对应
System.out.println("\n验证结果:答案内容是否改变? " +
(!originalOptions.get(originalKey).equals(randomizedTopic.getOption().get(randomizedTopic.getKey())) ? "改变了(错误)" : "没改变(正确)"));
}
}

模拟运行输出结果示例:

1
2
3
4
5
6
7
8
9
====== 打乱前 ======
题目选项: {A=JAVA2 EE, B=JAVA2 Card, C=JAVA2 ME, D=JAVA2 HE}
正确答案: D -> JAVA2 HE

====== 打乱后 ======
题目选项: {A=JAVA2 EE, B=JAVA2 ME, C=JAVA2 HE, D=JAVA2 Card}
正确答案: C -> JAVA2 HE

验证结果:答案内容是否改变? 没改变(正确)

总结

通过分离“标签(Key)”和“内容(Value)”,利用 Collections.shuffle() 打乱标签集合,并在重新绑定的过程中通过内容比对找回正确答案的新标签。