-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathexam_system_cli.py
More file actions
348 lines (301 loc) · 14.1 KB
/
exam_system_cli.py
File metadata and controls
348 lines (301 loc) · 14.1 KB
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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
# 基于命令行的试卷系统 - KeetCode
import json
import argparse
import random
from rich.console import Console
from rich.panel import Panel
from rich.text import Text
from rich.markdown import Markdown
from rich.table import Table
from rich.style import Style
# 创建控制台实例
console = Console()
class Question:
def __init__(self, q_type, content, score, options=None, answer=None, analyze=None):
self.q_type = q_type # 'choice', 'judge', 'fill', 'answer'
self.content = content
self.score = score
self.options = options or []
self.answer = answer or ''
self.analyze = analyze or ''
self.user_answer = ''
class ExamSystem:
def __init__(self, questions_files=['exam_questions.json'], mode='a'):
self.questions_files = questions_files
self.mode = mode
self.questions = self.generate_sample_questions()
self.total_score = sum(q.score for q in self.questions)
self.wrong_questions = []
def generate_sample_questions(self):
"""从多个JSON文件读取题目并根据模式生成试卷"""
all_questions = []
# 读取所有文件的题目
for file_path in self.questions_files:
try:
with open(file_path, 'r', encoding='utf-8') as f:
data = json.load(f)
# 处理选择题
for q_id, q_data in data.get('choice', {}).items():
all_questions.append(Question(
'choice',
q_data['ask'],
int(q_data['value']),
q_data['options'],
q_data['answer'],
q_data.get('analyze', '')
))
# 处理判断题
for q_id, q_data in data.get('judge', {}).items():
all_questions.append(Question(
'judge',
q_data['ask'],
int(q_data['value']),
[],
q_data['answer'],
q_data.get('analyze', '')
))
# 处理填空题
for q_id, q_data in data.get('fill', {}).items():
all_questions.append(Question(
'fill',
q_data['ask'],
int(q_data['value']),
[],
q_data['answer'],
q_data.get('analyze', '')
))
# 处理解答题(JSON中的problem对应代码中的answer)
for q_id, q_data in data.get('problem', {}).items():
all_questions.append(Question(
'answer',
q_data['ask'],
int(q_data['value']),
[],
q_data['answer'],
q_data.get('analyze', '')
))
except FileNotFoundError:
print(f"错误:{file_path} 文件未找到")
except json.JSONDecodeError:
print(f"错误:{file_path} 文件格式错误")
except KeyError as e:
print(f"错误:{file_path} 文件缺少必要字段 {e}")
# 根据模式处理题目
if self.mode == 'a':
# 模式a:包含所有题目
return all_questions
elif self.mode.startswith('b'):
# 模式b:随机抽取指定比例的题目
try:
# 提取抽取比例
ratio = float(self.mode[1:])
if 0 < ratio <= 1:
# 计算需要抽取的题目数量
num_to_select = int(len(all_questions) * ratio)
# 随机抽取题目
return random.sample(all_questions, num_to_select)
else:
print("错误:抽取比例必须在0-1之间")
return all_questions
except ValueError:
print(f"错误:无效的模式参数 {self.mode},格式应为b0.8")
return all_questions
else:
print(f"错误:无效的模式 {self.mode},支持的模式为a或b0.8")
return all_questions
def run_exam(self):
"""运行考试"""
# 使用rich美化标题
title_text = Text("KeetCode 试卷系统", style="bold magenta")
info_text = Text(f"试卷共有 {len(self.questions)} 道题,总分 {self.total_score} 分", style="cyan")
console.print(Panel(title_text, expand=False, border_style="bold green"))
console.print(info_text, justify="center")
console.print("=" * 50, style="green")
try:
for idx, question in enumerate(self.questions):
# 美化题目编号和分值
question_title = Text(f"\n第 {idx+1} 题 ({question.score} 分)", style="bold yellow")
console.print(question_title)
# 使用Markdown风格显示题目内容
console.print(Markdown(question.content))
if question.q_type == 'choice':
# 选择题
self.handle_choice_question(idx, question)
elif question.q_type == 'judge':
# 判断题
self.handle_judge_question(idx, question)
elif question.q_type == 'fill':
# 填空题
self.handle_fill_question(idx, question)
elif question.q_type == 'answer':
# 解答题
self.handle_answer_question(idx, question)
except EOFError:
console.print("\n\n考试中断,仅计算已答题目的分数", style="bold red")
# 计算分数
self.calculate_score()
# 显示结果
self.show_result()
def handle_choice_question(self, idx, question):
"""处理选择题"""
console.print("选项:", style="bold blue")
# 生成字母选项(A, B, C, D...)
letters = [chr(65 + i) for i in range(len(question.options))] # A, B, C, D...
for i, option in enumerate(question.options):
option_text = Text(f"{letters[i]}. {option}", style="white")
console.print(option_text)
while True:
user_input = console.input("请输入你选择的选项字母(多个选项用逗号分隔,如A,B,C):").upper()
try:
# 分割用户输入并去除空格
selected_letters = [x.strip() for x in user_input.split(',')]
# 验证输入是否有效
valid = True
for letter in selected_letters:
if letter not in letters:
valid = False
break
if valid:
# 直接保存用户选择的字母
question.user_answer = selected_letters
break
else:
console.print(f"输入无效,请输入有效的选项字母(如:{','.join(letters)})", style="red")
except ValueError:
console.print("输入格式错误,请重新输入", style="red")
def handle_judge_question(self, idx, question):
"""处理判断题"""
console.print("选项:", style="bold blue")
console.print("1. ✓ 正确", style="white")
console.print("0. ✗ 错误", style="white")
while True:
user_input = console.input("请输入你的选择(1-正确,0-错误):")
if user_input in ['1', '0']:
question.user_answer = bool(int(user_input))
break
else:
console.print("输入无效,请重新输入", style="red")
def handle_fill_question(self, idx, question):
"""处理填空题"""
user_input = console.input("请输入你的答案:")
question.user_answer = user_input.strip()
def handle_answer_question(self, idx, question):
"""处理解答题"""
console.print("请输入你的答案(输入'###'结束):", style="bold blue")
answer_lines = []
while True:
line = console.input()
if line.strip() == '###':
break
answer_lines.append(line)
question.user_answer = '\n'.join(answer_lines)
def calculate_score(self):
"""计算分数"""
total_obtained = 0
self.wrong_questions.clear()
for question in self.questions:
# 检查题目是否已回答
if question.q_type == 'choice':
# 选择题评分 - 已回答至少一个选项
if hasattr(question, 'user_answer') and len(question.user_answer) > 0:
if sorted(question.user_answer) == sorted(question.answer):
total_obtained += question.score
else:
self.wrong_questions.append(question)
elif question.q_type == 'judge':
# 判断题评分 - user_answer是布尔值(已回答)
if hasattr(question, 'user_answer') and isinstance(question.user_answer, bool):
if question.user_answer == question.answer:
total_obtained += question.score
else:
self.wrong_questions.append(question)
elif question.q_type == 'fill':
# 填空题评分 - user_answer不是空字符串
if hasattr(question, 'user_answer') and question.user_answer != '':
if question.user_answer == question.answer:
total_obtained += question.score
else:
self.wrong_questions.append(question)
elif question.q_type == 'answer':
# 解答题评分 - 已回答
if hasattr(question, 'user_answer') and question.user_answer != '':
# 示例中直接给一半分数
total_obtained += question.score // 2
self.obtained_score = total_obtained
def show_result(self):
"""显示考试结果"""
# 美化考试结果
result_title = Text("考试结果", style="bold magenta")
score_text = Text(f"你的得分是:{self.obtained_score}/{self.total_score} 分", style="bold yellow")
console.print("\n" + "=" * 50, style="green")
console.print(Panel(score_text, expand=False, border_style="bold cyan"))
console.print("=" * 50, style="green")
if self.wrong_questions:
console.print("\n错题列表:", style="bold red")
for i, question in enumerate(self.wrong_questions, 1):
console.print(f"\n{i}. {question.content}", style="white")
console.print(f" 正确答案: {question.answer}", style="green")
console.print(f" 你的答案: {question.user_answer}", style="red")
if question.analyze:
console.print(f" 试题解析: {question.analyze}", style="blue")
else:
console.print("\n恭喜!你没有错题。", style="bold green")
# 保存错题
self.save_wrong_questions()
def save_wrong_questions(self):
"""保存错题到JSON文件"""
if not self.wrong_questions:
return
# 构建错题JSON数据结构
wrong_data = {
"choice": {},
"judge": {},
"fill": {},
"problem": {}
}
# 统计各题型错题数量
type_counts = {
"choice": 0,
"judge": 0,
"fill": 0,
"answer": 0
}
# 填充错题数据
for question in self.wrong_questions:
# 计算对应JSON中的题型关键字
json_type = "problem" if question.q_type == "answer" else question.q_type
# 生成题目标识
type_counts[question.q_type] += 1
q_id = str(type_counts[question.q_type])
# 构建题目数据
q_data = {
"ask": question.content,
"answer": question.answer,
"analyze": question.analyze,
"value": question.score
}
# 添加选择题特有的选项字段
if question.q_type == "choice":
q_data["options"] = question.options
# 添加用户答案
q_data["user_answer"] = question.user_answer
# 添加到对应题型的错题列表
wrong_data[json_type][q_id] = q_data
# 保存到JSON文件, 追加模式
with open('wrong_questions.json', 'a', encoding='utf-8') as f:
json.dump(wrong_data, f, ensure_ascii=False, indent=2)
# 美化保存结果提示
save_text = Text("错题已追加保存到 wrong_questions.json 文件中", style="bold green")
console.print(save_text, justify="center")
if __name__ == "__main__":
# 创建参数解析器
parser = argparse.ArgumentParser(description='试卷系统 - 从JSON文件读取试题')
parser.add_argument('-f', '--file', type=str, nargs='+', default=['exam_questions.json'],
help='试题JSON文件路径 (支持多个文件,默认: exam_questions.json)')
parser.add_argument('-m', '--mode', type=str, default='a',
help='出题模式: a表示包含所有题目,b表示随机抽取 (如b0.8表示抽取80%)')
# 解析命令行参数
args = parser.parse_args()
# 创建考试系统实例并运行
exam = ExamSystem(questions_files=args.file, mode=args.mode)
exam.run_exam()