简介:PTA理论考部分.docx是一份面向PTA平台C语言理论考整理的复习资料,主要收录HW02、HW03等章节的判断题与少量选择题,每题均附正确答案,并对涉及的知识点进行展开说明,而不是简单给出对错。解析涵盖编译预处理命令的作用、main函数唯一性、程序执行起点、分号结束规则、变量先声明后使用、单目运算符只需一个操作数、整数除法取整、赋值与比较运算符的差异、scanf和printf的格式符匹配、浮点数取模非法、自增运算与普通加法的区别、for循环三表达式的执行顺序、复合语句与空语句的语法地位等高频考点。压缩包内仅含1个docx文件,大小约345KB,Word格式便于离线查看、关键词检索以及打印成纸质练习。已有1895人学习浏览,适合正在刷PTA题目或备考C语言期中期末理论考的学生,可用于快速检验C语言基础掌握情况,边做题边对照详解,及时查漏补缺。
1. PTA理论考部分.docx 能解决什么:把 Word 题库变成能刷、能考、能查错的数据
拿到一份 PTA理论考部分.docx,大多数人的第一反应是双击打开、从头往下翻。几百道题挤在段落和表格里,判断题、单选题、多选题混着排,答案要么在题尾括号里,要么藏在文末列表,翻到后面早就忘了前面。我处理这类文档的习惯是:先别急着看题,先把它当成一份需要解析的数据源。PTA理论考部分.docx 本质上是一个打包的 XML 文档,题面、选项、答案都以段落或表格形式存在,只要把结构抽出来,就能转成可刷题、可随机抽卷、可统计错题的本地题库。这篇文章就讲怎么用脚本把这份 Word 文档变成真正能用的复习系统,适合考前需要快速把题库盘活的考生,也适合帮同事做培训资料整理的工程师。
2. 先拆开 PTA理论考部分.docx:docx 的内部结构与最小提取脚本
2.1 docx 不是"一个文件":zip 包、document.xml 与样式树
很多人把 docx 当成和 txt 一样的纯文本,这是后面所有解析问题的根源。docx 用的是 Office Open XML 格式,本质是一个 zip 压缩包,里面装着若干 XML 文件。正文存放在 word/document.xml 里,样式定义在 word/styles.xml,图片、页眉页脚各自有独立的 XML。这意味着你不能用 open() 直接读文本,也不能用简单的字符串查找去定位题目,必须先经过一层解包和 XML 解析。
我一般不会手动去翻 XML,而是直接用 python-docx 这个库,它把 document.xml 封装成了段落(paragraph)和表格(table)两个对象模型。段落有样式名、文本内容和对齐方式,表格有行和列,每个单元格里又包含段落。这个模型和 Word 里看到的结构基本一致,处理理论考文档时够用了。另一个不能忽视的点是:Word 里的自动编号列表在 XML 里并不以文本形式存在,题号可能是 numPr 属性控制的,直接读 paragraph.text 可能什么都读不到,这一点在第五章会展开讲。
2.2 最小提取脚本:读段落、读表格、读页眉页脚
先把环境准备好。python-docx 是纯 Python 库,pip 安装即可,解析 docx 不需要本机装 Word。
pip install python-docx然后写一个最小脚本,把文档里所有段落和表格的文本都拉出来,先看看内容长什么样。
from docx import Document doc = Document("PTA理论考部分.docx") print("===== 段落 =====") for i, para in enumerate(doc.paragraphs): if para.text.strip(): print(f"[{i}] {para.text}") print("===== 表格 =====") for t_idx, table in enumerate(doc.tables): print(f"--- 表 {t_idx} ---") for row in table.rows: cells = [cell.text.strip() for cell in row.cells] print(" | ".join(cells))这份代码的作用是把文档里的非空段落按顺序打印出来,再把每个表格逐行打印。doc.paragraphs 拿到的是文档主体里的顶层段落,不包含表格单元格里的段落;doc.tables 拿到的才是所有表格。第一次跑这个脚本,重点是确认两件事:题目到底是放在段落里还是表格里,题目和答案的位置关系是什么样的。
2.3 为什么不能直接按纯文本处理:分隔符、自动编号与修订痕迹
直接对 paragraph.text 做字符串切片,看起来省事,但会踩三个坑。第一个是自动编号:如果题号是 Word 的列表自动生成的,text 里根本没有"1、"这个前缀,切分题目时锚点会全部丢失。第二个是分隔符:Word 里的换行符(软回车)在 python-docx 里表现为 run 内部的 break,text 属性会把它丢掉,导致一个题目被拆成多段但粘在一起,无法定位。第三个是修订痕迹:如果文档开启过修订模式并接受了部分更改,XML 里会残留 w:ins、w:del 标签,text 读出来的内容可能和肉眼看到的不一致。
我处理这类文档的第一步永远是先复制一份原始文件,然后在不改变原文件的前提下做只读解析。遇到段落文本为空但样式名带 "ListParagraph" 的情况,不要直接跳过,这些段落很可能是自动编号包裹的题号或选项行。
3. 把题干变成数据:PTA理论考部分.docx 的题型识别与 JSON 导出
3.1 理论考文档的常见版式:判断、单选、多选与填空
拿到一份理论考 docx,不管是什么科目,版式通常逃不出几种固定套路。判断题的题干后面紧跟括号,括号里是"√"或"×",也有写成"正确/错误"的。单选题是题干下面缩进排列的 A/B/C/D 选项,答案在题尾或集中在文末。多选题选项数量不固定,可能会有 E 甚至 F 选项。填空题则是题干中留空,用下划线或括号占位。
我的做法是先把文档按题型分块,而不是逐题处理。先扫描全文,找出题型标题行(比如"一、判断题""二、单选题"),把文档切成几个区间,再在每个区间内做题目切分。这样做的好处是:每种题型的正则规则可以分别定制,不会出现判断题为匹配到编号而把选项行吞掉的情况。
import re section_pattern = re.compile(r"^[一二三四五六七八九十]+、(.+?)[、\s]*(判断题|单选题|多选题|填空题)")这个正则用来识别题型标题行,第一个分组捕获具体章节名,第二个分组捕获题型。匹配成功时记录当前题型的起始段落下标,下一次匹配成功时,上一个题型区间就闭合了。如果文档里没有明显的题型标题,就用题号编号的连续性来推断题型切换点。
3.2 用正则做题目切分的边界设定:编号锚点与选项续行
题目切分是整个流程里最容易出错的环节。常见的错误做法是匹配"数字+"就切一刀,结果把所有含数字的正文都切成了新题。我的经验是先建立锚点规则,锚点优先级从高到低:
第一优先级是题号后跟顿号或英文点,且行首位置,比如^\d+[.、]\s*。第二优先级是题型标题行本身。第三优先级是题干中的固定提示词,比如"()"或"___"。切分时还要处理选项续行的问题——一个选项可能因为排版被拆成两行,如果按段落切题,第二行的内容就会被当成独立的无编号文本丢弃。
def split_questions(paragraphs, q_start, q_end): questions = [] current = None for para in paragraphs[q_start:q_end]: text = para.strip() if not text: continue m = re.match(r"^(\d+)[.、]\s*(.*)$", text) if m: if current: questions.append(current) current = {"number": int(m.group(1)), "body": m.group(2)} elif current: current["body"] += "\n" + text else: current = {"number": None, "body": text} if current: questions.append(current) return questions这段代码的思路是:遇到行首题号就收拢上一题、开启新题;没有题号的行一律当作上一题的续行拼到 body 里,防止选项被拆散。参数说明:paragraphs 是已经按题型切好区间的段落列表,q_start 和 q_end 是区间起止下标;续行拼接用 \n 保留换行结构,方便后续再做选项拆分。正则里的[.、]同时兼容中文顿号和英文点,\s*处理题号和题干之间的空格或全角空格。
3.3 导出 JSON:字段设计、答案归一化与双选校验
题目切分完成后,下一步是把它导出成结构化的 JSON。字段设计上我坚持两个原则:一是原始文本至少保留一份,方便后续人工核查;二是答案用统一枚举,不直接用"√""对""T"这种写法。
def normalize_answer(raw): mapping = { "√": "true", "正确": "true", "对": "true", "T": "true", "×": "false", "错误": "false", "错": "false", "F": "false", "A": 0, "B": 1, "C": 2, "D": 3, "E": 4, "F": 5 } return mapping.get(raw.strip(), raw.strip()) def build_question_json(number, body, qtype): return { "id": f"{qtype}-{number:03d}", "type": qtype, "body": body, "options": [], "answer": None, "analysis": "" }normalize_answer 的作用是把判断题的各种答案写法统一成 true/false,把单选题的 A/B/C/D 映射成下标数字。需要注意:多选题的答案是"ABD"这种组合串,不能直接逐字符映射,否则会丢掉选项顺序信息。我的做法是把答案串按字符拆分后逐个映射,再拼回列表。导出 JSON 时用 ensure_ascii=False,否则中文会变成 \uXXXX,后续排错非常痛苦。写出的文件用 utf-8 编码,同时保留一个精简版便于快速浏览。
4. 从 docx 到刷题闭环:抽卷、错题本与 SQLite 存储
4.1 转 CSV/SQLite:给手机端刷题 App 喂数据
JSON 适合程序间交换,但手机刷题 App 或自建网页题库更喜欢 CSV 或 SQLite。转 CSV 时最需要注意的是选项列表的序列化:不能直接把 Python 列表写进 CSV,要先用分号或竖线拼接。我一般用 sqlite3 直接建表,字段和 JSON 保持一致,同时加上 update_time 字段方便后续同步。
sqlite3 theory_bank.db <<'EOF' CREATE TABLE questions ( id TEXT PRIMARY KEY, type TEXT NOT NULL, body TEXT NOT NULL, options TEXT, answer TEXT, analysis TEXT, chapter TEXT, wrong_count INTEGER DEFAULT 0, last_wrong_at DATETIME ); EOF这张表的重点是 wrong_count 和 last_wrong_at 两个字段,它们支撑后面的错题本和复习计划。选项字段用 TEXT 存储分隔后的字符串,读取时再 split,避免在 SQLite 里用难维护的嵌套结构。题目正文里如果包含单引号,写入 SQL 时要做转义,更稳妥的做法是参数化插入而不是拼 SQL 字符串。
4.2 随机抽题脚本:按章节权重抽一套模拟卷
有了题库数据,下一步就是随机抽题。很多人直接 random.shuffle 然后取前 N 道,这样抽出来的卷子题型比例完全不可控。模拟卷应该按题型和章节权重分层抽样:判断题抽多少道、单选题抽多少道、多选题抽多少道,各章节占比按考试大纲分配。
import random def generate_paper(db_path, rules): conn = sqlite3.connect(db_path) cur = conn.cursor() paper = [] for rule in rules: qtype = rule["type"] chapter = rule.get("chapter") count = rule["count"] if chapter: cur.execute( "SELECT id, body, options, answer FROM questions WHERE type=? AND chapter=? ORDER BY RANDOM() LIMIT ?", (qtype, chapter, count) ) else: cur.execute( "SELECT id, body, options, answer FROM questions WHERE type=? ORDER BY RANDOM() LIMIT ?", (qtype, count) ) rows = cur.fetchall() if len(rows) < count: print(f"警告:{qtype} 在 {chapter or '全部章节'} 的题库不足 {count} 道") paper.extend(rows) conn.close() return paperORDER BY RANDOM() 是 SQLite 内置的随机排序,配合 LIMIT 直接取出指定数量的题目。rules 是抽题规则列表,每个元素定义题型、章节和抽取数量。如果某个章节题库数量不够,脚本会打印警告而不是静默跳过,这是防止模拟卷出现题型缺失的关键。
4.3 错题标记与导出错题本:把复习闭环起来
刷题过程中最核心的数据是错题记录。我建议每做一次题就更新一次题库里的 wrong_count 和 last_wrong_at,而不是在本地临时文件里记。这样复习时可以直接按最后一次答错时间排序,优先处理最近出错和反复出错的题目。
def record_wrong(conn, qid): conn.execute( "UPDATE questions SET wrong_count = wrong_count + 1, last_wrong_at = datetime('now') WHERE id = ?", (qid,) ) conn.commit()导出错题本时,我一般不导出全部错题,而是加两个过滤条件:wrong_count 大于等于 2 的优先导出;last_wrong_at 在最近 7 天内的其次导出。前者的逻辑是反复错说明没掌握,后者的逻辑是趁记忆没消退赶紧巩固。导出格式直接生成 Markdown 文件,每题带题号、题型、正确答案和你的作答,方便打印或在手机上看。
5. PTA理论考部分.docx 处理的 5 个避坑记录:现象、原因与解决
5.1 现象:题号读不出来,正则怎么匹配都是空
我第一次处理类似文档时,打印出来的段落文本只有题干没有题号,但原文档里明明显示"1.判断题题干"这样的前缀。原因是这些题号是 Word 的自动编号列表生成的,在 XML 里只是 numPr 属性,并不存在实际的数字文本。python-docx 的 paragraph.text 无法读取这种虚拟编号。
解决办法是改用样式名识别。自动编号的段落样式通常是 "List Paragraph" 或 "ListNumber",先按样式把这类段落筛出来,再通过 numPr 的引用找到具体的编号格式。更省事的做法是让同事在 Word 里把自动编号转换成静态文本,具体操作是选中列表,右键菜单里选择"转换为纯文本"。如果无法改源文件,就放弃正则匹配题号,改用段落样式 + 上下文规则切题。
5.2 现象:表格里的选项读出来顺序是乱的
有些文档把选项放在两列表格里,左边是"A. 选项内容",右边是"B. 选项内容"。python-docx 读 row.cells 时,如果表格本身有合并单元格或嵌套表格,cells 返回的元素数量和视觉上的行列数不一致,导致选项拼接顺序错乱。
排查方法:先打印 table 的行列数,再用 docx 的 table.rows[i].cells 逐个检查单元格内容,确认哪些单元格是空的、哪些是重复引用。解决思路是不要依赖 cells 的下标,而是按单元格文本内容里的"A.""B."前缀重新排序,前缀丢失的单元格按表格坐标就近补位。如果表格结构太乱,直接退回用段落流式解析,放弃表格专用逻辑。
5.3 现象:特殊符号变成乱码或直接丢失
理论考里经常出现"≥""≤""±""×"这些符号,以及化学式下标。docx 里这些符号可能是普通 Unicode 文本,也可能是公式对象(OMML)或符号字体。python-docx 对公式对象的支持很弱,paragraph.text 可能只返回空字符串,导致题干缺失关键信息。
解决分两层:如果只是个别符号丢失,用正则把缺失位置标记成占位符(比如[公式]),人工补充后再入库;如果公式数量多,就先在 Word 里把公式转成图片,再通过 extract_media 导出图片,按图片在文档中的位置插回题面。我的经验是:理论考文档里公式占比高的科目,不要指望纯文本解析一步到位,预留人工核对环节是必须的。
5.4 现象:同一道题出现两份,内容几乎一样但答案不同
这是修订痕迹和文档合并造成的。Word 开启修订后,XML 里会同时保留原始内容和修订后的内容,python-docx 默认读取的是修订后的文本,但如果你用某些转换工具先转了格式再解析,可能把删除线和插入文本都当成正文。还有一种情况是文档曾经被多人分段编辑后合并,同一题在文首和文末各出现一次。
排查方法:先检查 docx 里是否有 w:ins、w:del 标签,用 python-docx 的 element 树直接搜索。去重逻辑上,我对题目正文做归一化后计算哈希,完全相同的只保留第一条;正文相同但答案不同的,打印出来人工判断,不自动覆盖。自动覆盖的后果是把正确答案改错,这个风险不值得冒。
5.5 现象:批注和脚注混进正文,题目被截断
文档里的批注内容在 document.xml 里是独立的元素,python-docx 的 paragraph.text 默认不包含批注,但脚注内容在某些转换场景下会嵌入正文段落的 run 里,导致题干后面跟着一段与题目无关的补充说明,干扰选项识别。
解决方法是先把段落里的 run 逐个过滤,只保留 page number 之前的内容,或者把脚注引用标记替换成空格。更稳妥的做法是解析前先用现成工具把 docx 转成纯文本的中间格式,再在中间格式上做题目切分,这样至少能保证正文提取逻辑的输入是干净的。
6. 进阶:给题目打标签,做按知识点的防遗忘复习
刷题只是第一步,真正提高通过率的是按知识点有节奏地复习。我习惯在题库表里加一个 tag 字段,用关键词匹配的方式给每道题打上章节和知识点标签。比如题干里出现"设备""验收"就标记为"设备验收",出现"应急预案""演练"就标记为"应急管理"。这个规则不需要很精确,能粗粒度分组就够了,后续复习时可以按标签组随机抽题。
复习节奏我参考的是间隔重复的思路:新学题目当天刷一遍,第 2 天、第 4 天、第 7 天再各出现一次。脚本每天启动时扫描题库,把 last_wrong_at 在对应时间窗口内的题抽出来,生成当天的复习清单。反复答对的题目自动降低出现频率,答错的则把 last_wrong_at 更新为今天,重新进入复习周期。这个过程不需要复杂算法,几条 SQL 加一个定时任务就能跑起来。
最后检查一遍数据质量:题目数量、题型分布、答案是否越界、是否有重复题。我一般会写一个校验脚本,把所有 answer 字段和 options 数量比对,越界的直接标红。这个步骤虽然不起眼,但能避免考前突然发现某道多选题答案少了一个选项。
这套流程我做过不止一次,每次翻车几乎都发生在前 30 分钟——不是题目切错,而是没先看清楚文档的版式和修订痕迹。现在我的习惯是拿到 docx 先跑一遍最小提取脚本,把段落和表格的预览打出来,确认结构以后再写切题规则。希望帮到你。
本文还有配套的精品资源,点击获取