免责申明
本文和工具截图中涉及题库和题目,均为本人自学使用,并未有商业和传播企图。如有侵害,联系删改。
概述
笔者本人医学专业从业人员,编程只是业余爱好。在自己的专业应考学习过程当中:
- 有时候不太喜欢纸质题库的排版,比如题目与答案分开,甚至是分开在两本书上。
- 想要翻录下来,转存到笔记软件中,作为自己积累的一个题库,而且方便随时查看、修改和扩充笔记。
这项工作,我从2024年头就开始实践,并且已经积累了一定的题目量。作为专业学习和考试的专用复习资料。
翻录操作
我的具体做法是:
- 摊开书,翻页,手机拍照后转存PDF
- 在WPS中进行扫描件识别,获取文本
- 在VSCode中进行批量替换和初步处理
- 复制到笔记软件(我用的是语雀),进行更详细的优化排版
- 学习和补充笔记,手机随时查看记忆
- 选择性打印为册子,方便查看
翻录环节一直是很顺畅和熟练的。但是,后续处理有时候比较麻烦。尤其是很多纸质题库是将题目和参考答案以及解析分开的。手动排版是个海量的工作。因此,需要编程实现题目与答案的合并。
之前的Python版本
2024年11月11日创建的Python版本,其原理是:
- 把题目和答案都预处理为
---
分隔的形式 - 各自拆分为字符串数组,检测题目总数和答案总数一致后,进行题目和答案解析的合并
# 读取文件内容
def read_file(path):
with open(path, 'r',encoding="utf-8") as file:
content = file.read()
return content
# 读取文件内容
def save_file(path,content):
with open(path, 'w',encoding="utf-8") as file:
content = file.write(content)
# 获取题目和答案合并的结果
def get_result(qus_path,ans_path):
result = ""
qus = read_file(qus_path) # 问题
ans = read_file(ans_path) # 答案
# 用 --- 划分
qus_arr = qus.split("---")
ans_arr = ans.split("---")
if len(qus_arr) == len(ans_arr): # 答案和题目数量一致
# 遍历生成合并字符串
for i in range(len(qus_arr)):
q = qus_arr[i]
a = ans_arr[i]
result += f"""{q}
:::tips
{a}
:::"""
return result
save_file("result.md",get_result("qus.txt","ans.txt"))
这个版本的坏处的,必须依靠VSCode和工作目录,并且需要将题目和答案分别存储到固定名称的文件中。
而字符串处理的大部分操作还是需要手动在VSCode中进行。
创建Godot版本
为了更方便快捷的合并,编写了一个简单的Godot应用。
- 用Godot创建合并器,以复制粘贴文本形式,而不是文件形式,处理起来会更灵活。
- 而且代码和处理的灵活性也会进一步上升
初步界面设计
初步实现
extends HSplitContainer
@onready var qus_txt: TextEdit = %qusTxt
@onready var ans_txt: TextEdit = %ansTxt
@onready var result_txt: TextEdit = %resultTxt
# ================================= 按钮点击 =================================
# 整体添加分隔线
func _on_splits_btn_pressed() -> void:
qus_txt.text = add_splits(qus_txt.text)
ans_txt.text = add_splits(ans_txt.text)
# 执行合并
func _on_join_btn_pressed() -> void:
result_txt.text = do_joins_result(qus_txt.text,ans_txt.text)
pass
# ================================= 自定义方法 =================================
# 查找题目或答案序号,并添加 --- 分隔符
func add_splits(old_str:String) -> String:
var regex = RegEx.new()
regex.compile(r"\n\d\.")
for result in regex.search_all(old_str):
var res = result.get_string()
old_str = old_str.replace(res,"\n --- %s" % res)
return old_str
# 执行合并
func do_joins_result(qus_str:String,ans_str:String) -> String:
var result:PackedStringArray = []
var qus_arr = qus_str.split("---",false)
var ans_arr = ans_str.split("---",false)
if qus_arr.size() == ans_arr.size(): # 题目数量与答案解析数量一致
# 遍历生成合并字符串
for i in range(qus_arr.size()):
var qus = qus_arr[i]
var ans = ans_arr[i]
result.append(qus_ans_str(qus,ans))
return "\n".join(result)
# 根据模版字符串生成题目和答案解析的合并字符串
func qus_ans_str(qus:String,ans:String) -> String:
var tmp = """%s
:::tips
%s
:::""" % [qus,ans]
return tmp
已经实现基础的题目与答案解析合并功能,复制到语雀自动识别后,也是可以正常显示:
编写处理类
上面将处理逻辑编写为三个函数,混杂在主场景中,显得并不优秀。
好的办法是进一步编写类,来固定处理相似的逻辑。这样也就让逻辑和UI部分分离了。
新类设想
- 新的类将自动处理输入的普通的题目和答案解析字符串,并维护题目和答案两个数组
- 将可以编写简单的题库系统,顺序查看题目和答案
新类实现
# =========================================================
# 名称:QusAnsList
# 类型:GDSCript类
# 描述:解析和处理普通的题目和答案解析字符串,生成合并字符串的处理类
# Godot版本:v4.3.stable.steam [77dcf97d8]
# 创建时间:2025年2月12日13:08:13
# 最后修改时间:2025年2月12日21:38:46
# =========================================================
class_name QusAnsList
# 切分后的题目与答案集合
var qus_arr:PackedStringArray
var ans_arr:PackedStringArray
# 原始字符串
var qus_str:String
var ans_str:String
# 添加分隔符后的字符串
var split_qus_str:String
var split_ans_str:String
# 创建实例
func _init(qus_str:String,ans_str:String) -> void:
self.qus_str = inline_opts(qus_str)
self.ans_str = ans_str
# 添加分隔线
self.split_qus_str = add_lines(self.qus_str,true)
self.split_ans_str = add_lines(ans_str)
# 分割添加了分隔线的字符串,获得题目与答案集合
splits()
# ================================ 方法 ================================
# 将ABCDE选项放置到一行
func inline_opts(old_str:String) -> String:
var new_str = old_str
for opt in ["B","C","D","E"]:
new_str = new_str.replace("\n%s." % opt,"%s%s." % ["\t".repeat(2),opt])
return new_str
# 查找题目或答案序号,并添加 --- 分隔符
# old_str 要添加分隔符的原始字符串
# ignore_first 是否忽略第一项,用于题目序号不是在第一行的情况
func add_lines(old_str:String,ignore_first:=false) -> String:
# 创建对序号的正则匹配
var regex = RegEx.new()
regex.compile(r"\n\d+\.")
# 遍历所有搜索到的序号,替换为 ---
var results = regex.search_all(old_str)
for i in range(results.size()):
if ignore_first and i==0: # 忽略第一次
continue
var res = results[i].get_string()
old_str = old_str.replace(res,"\n---%s" % res)
return old_str
# 分割添加了分隔线的字符串获得题目与答案集合
func splits() -> void:
qus_arr = split_qus_str.split("\n---\n",false)
ans_arr = split_ans_str.split("\n---\n",false)
# 检测题目与答案集合的数目是否相等
func is_qus_equal_ans_count() -> bool:
return qus_arr.size() == ans_arr.size()
# 获取执行合并的字符串
func get_joins_result() -> String:
var result:PackedStringArray = []
if qus_arr.size() == ans_arr.size(): # 题目数量与答案解析数量一致
# 遍历生成合并字符串
for i in range(qus_arr.size()):
var qus = qus_arr[i]
var ans = ans_arr[i]
result.append(qus_ans_str(qus,ans))
return "\n".join(result)
# 根据模版字符串生成题目和答案解析的合并字符串
func qus_ans_str(qus:String,ans:String) -> String:
var tmp = """%s
:::tips
%s
:::""" % [qus,ans]
return tmp
基于新类,主场景的代码简化如下:
extends HSplitContainer
var QAlist:QusAnsList
var current_index:int = 0
@onready var qus_txt: TextEdit = %qusTxt
@onready var ans_txt: TextEdit = %ansTxt
@onready var result_txt: TextEdit = %resultTxt
@onready var tk_qus_txt: CodeEdit = %tkQusTxt
@onready var tk_ans_txt: CodeEdit = %tkAnsTxt
# ================================= 按钮点击 =================================
# 执行合并
func _on_join_btn_pressed() -> void:
QAlist = QusAnsList.new(qus_txt.text,ans_txt.text)
result_txt.text = QAlist.get_joins_result()
# 题库显示第一题
show_tk_qus_ans(0)
# 题库显示指定的第几题
func show_tk_qus_ans(index:int) -> void:
tk_qus_txt.text = QAlist.qus_arr[index]
tk_ans_txt.text = QAlist.ans_arr[index]
# 显示上一题
func _on_last_btn_pressed() -> void:
current_index = clamp(current_index-1,0,QAlist.qus_arr.size()-1)
show_tk_qus_ans(current_index)
# 显示下一题
func _on_next_btn_pressed() -> void:
current_index = clamp(current_index+1,0,QAlist.qus_arr.size()-1)
show_tk_qus_ans(current_index)
程序效果
一键处理和合并题目与答案
- 生成合并字符串并进行展示,可以复制到语雀中自动识别和排版
生成题库预览
- 因为新类内部维护两个数组,存储题目和答案
- 所以可以搭建简易的题库预览界面,来按顺序查看每一道题目
后续改进
界面分开
- 将界面分为合并器和题库预览两部分,通过顶栏的按钮进行界面的切换
题号列表
- 只通过上一题,下一题查看题库并不方便,可以生成题号按钮,点击快速查看。
修改题库预览界面样式
- 两边留白,让视野宽度略小于屏幕宽度,中心聚焦且看的不是太累。
刷题计时器
- 设计一个简单的刷题计时器,可以重置、开始、暂停,用以记录刷题时间
- 首先是编写一个函数获取累计时间的
HH:MM:SS
形式字符串
# 返回累计时间的时分秒的字符串形式 HH:MM:SS
func get_time_str(time:int) -> String:
var dic = Time.get_time_dict_from_unix_time(time)
return "%02d:%02d:%02d" % [dic["hour"],dic["minute"],dic["second"]]
# 返回累计时间的时分秒的字符串形式 HH小时MM分SS秒
func get_time_str(time:int) -> String:
var dic = Time.get_time_dict_from_unix_time(time)
return "%02d小时%02d分%02d秒" % [dic["hour"],dic["minute"],dic["second"]]
- 接着就是使用和控制
Timer
节点显示累计时间
UI美化
加载和保存题库
- 题库文件是题目和答案用特殊解析字符串
#================;
所连接的纯文本文件
总结
- 至此,意为着利用纯文本形式的题目和答案字符串,快速合并获得适用于语雀显示的格式将变得异常方便
- 同时,可以以非常简便的方式创建刷题系统。只需要维护和保存一堆
.txt
文件即可。