
简介围绕四川大学材料科学与工程基础课程整理的期末复习题库文档面向备考期末的本科生以及需要系统回顾材料学基础的学习者。内容以判断、选择等题型串联核心考点覆盖材料按化学组成的三大分类、四个量子数与电子排布规律、金属键/共价键/离子键及电负性判据、能带理论与导体半导体绝缘体的区分、范德华力与氢键的方向性和饱和性以及晶体结构中的空间点阵、晶系、晶格常数、fcc 堆垛顺序、致密度与配位数等题目多附答案标记便于自测与查漏补缺。压缩包仅含 1 个 doc 文档约 180KB轻量易取适合打印或平板批注。目前已有 108 人学习下载。对需要快速梳理知识框架、集中练习高频判断选择题的读者可按章节顺序过题也可针对薄弱概念回看对应条目用较短时间完成一轮期末冲刺。1. 一份 doc 题库真正值钱的地方是它没被结构化期末周前一晚最容易被低估的不是公式而是那份《四川大学材料科学与工程基础期末复习考试题库完整.doc》本身。它躺在共享盘里很多年用 Word 打开一切正常复制到记事本却是满屏方框Word 里能搜到「密排面」用 grep 一个都搜不到。材料学科的题库还有个特点题干短、选项长、计算题夹着下标和希腊字母σs、α-Fe、{111} 这类符号在纯文本里随时会掉字符。这件事的技术本质不是背书而是把一份非结构化 Word 老文档还原成可检索、可去重、可批次练习的数据资产。适合往下看的人有两类手里攥着 Word 版真题、想自己搭一个刷题库的考生需要批量处理 OLE2 老文档、做教育类语料清洗的工程师。2. 解析 doc 题库从 OLE2 复合文档到可检索纯文本的完整链路2.1 先判断文件到底是 .doc 还是披着 .doc 皮的 .docx很多题库文件后缀写着 .doc实际是 Word 2007 之后的 zip 包甚至是从某个网页另存出来的 HTML。选错解析路径后面全是无用功。先看魔数# 看文件类型描述 file 四川大学材料科学与工程基础期末复习考试题库完整.doc # 看前 8 个字节两种常见结果 # D0 CF 11 E0 A1 B1 1A E1 - OLE2 复合文档真 .doc / .xls / .ppt # 50 4B 03 04 - ZIP 容器其实是 .docx / .odt xxd -l 8 四川大学材料科学与工程基础期末复习考试题库完整.doc没有 xxd 就用od -A x -t x1z -v 文件 | head -1效果一样。判定为 OLE2 的走第 2.2 节的二进制解析路线判定为 ZIP 的直接改后缀成 .docx用 pandoc 就能一步到位。2.2 四条解析路径的选型对比真 OLE2 文档里正文被切碎存放在 WordDocument 流中还带着分片表piece table和字符格式标记。自己用 olefile 逐字节拼是不现实的常见做法是借现成工具。下面这张表是我在不同题库上反复试过之后的取舍方案依赖中文与符号保真表格/图片适用场景antiword单文件二进制中文需指定-m UTF-8.txt映射表格丢失无图片纯文本题干快速 grepcatdoc单文件二进制默认按系统编码易乱码表格退化成空白分隔英文为主的题库LibreOffice headless完整 LibreOffice最好字体符号基本还原表格与图片都能保留正式流程首选Apache TikaJRE jar尚可取决于底层解析器表格保留图片抽取麻烦已有 Java 服务的批处理python-docx纯 Python高好仅适用于 .docx喂 .doc 会直接报错提示python-docx 不认 OLE2 格式遇到 .doc 会抛PackageNotFoundError别在这上面浪费时间。2.3 用 LibreOffice headless 跑通最小转换命令第一步先把 .doc 统一转成 .docx保留结构# --headless 无界面运行--convert-to 指定目标过滤器--outdir 指定输出目录 soffice --headless --convert-to docx:MS Word 2007 XML \ --outdir ./converted \ 四川大学材料科学与工程基础期末复习考试题库完整.doc参数说明docx:MS Word 2007 XML里的过滤器名必须带引号否则 shell 会把括号当通配符--outdir不存在时会报错而不是自动创建先mkdir -p converted。Windows 上把soffice换成C:\Program Files\LibreOffice\program\soffice.exemacOS 上换成/Applications/LibreOffice.app/Contents/MacOS/soffice。注意LibreOffice 同一时刻只允许一个实例运行。批处理脚本里连续调用多次第二次开始会直接把任务丢给已存在的进程然后返回脚本以为成功了实际文件没生成。稳妥做法是每次转换前加-env:UserInstallationfile:///tmp/lo_$$指定独立配置目录。第二步把 .docx 转成 Markdown顺带把相图、晶胞截图抽出来# -f 输入格式-t gfm 输出 GitHub 风格 Markdown # --wrapnone 禁止硬换行保证一道题占一行方便后端正则 # --extract-media 把内嵌图片导出到 ./media pandoc -f docx -t gfm --wrapnone --extract-media./media \ -o ./题库.md ./converted/题库.docx如果只想要纯文本、不在乎表格可以直接一步到底省掉 pandocsoffice --headless --convert-to txt:Text (encoded):UTF8 \ --outdir ./txt 四川大学材料科学与工程基础期末复习考试题库完整.doc2.4 转换之后的三个必查项转换完不要急着写解析脚本先做三项体检。第一项题号计数grep -cE ^[0-9]{1,3}[\.、] 题库.md拿这个数和 Word 里最后一题的编号对一下差值超过 5% 就说明有题号被并进了上一段。第二项字符体检grep -n 题库.md | head出现替换字符说明编码环节丢了东西通常是 GBK 题库走了 UTF-8 通道。第三项符号回填检查Word 里 A/B/C/D 选项前面的符号常常是 Wingdings 字体的字符转出来会变成空白或者方框需要按位置重新编号。这一步做完你会拿到一份每题一行、图片外链、可以 grep 的 Markdown。它已经比原始 .doc 好用得多但还只是文本下一步要把它拆成字段。3. 把题干、选项、答案拆成结构化字段正则切块加状态机兜底3.1 材料类题库的三种典型排版翻过几份材料科学与工程基础的题库之后排版基本逃不出三种第一种是题号、题干、四个选项各占一行答案另起一行最规整第二种是题干一行四个选项挤在同一行用空格分隔第三种最麻烦题干跨两行中间夹着公式或者「如图 3-2 所示」。这三种要分别处理用一条大正则硬啃会写出一坨没人敢改的东西。先把题号的形态穷举出来1.、1、、(1)、1全角句点以及带章节前缀的3-12.。建议统一用一条正则匹配后面所有逻辑都基于它。3.2 正则先切块题号、选项、答案各管一段import re # 题号支持 1. / 1、/ 1/ (1) / 3-12. 五种写法 QNUM re.compile(r^\s*(?:\((\d{1,3})\)|(\d{1,3}(?:-\d{1,3})?))\s*[\.、]\s*) # 选项行首 A. / A、/ A) / A 冒号与全角括号都兼容 OPT re.compile(r^\s*([A-Da-d])\s*[\.、\)]\s*(.?)\s*$) # 答案答案C / 答 案:C / 参考答案ABD ANS re.compile(r^\s*(?:参考)?答\s*案\s*[:]\s*([A-Da-d\s]{1,8})\s*$) def split_blocks(text): 按题号把整份文档切成块返回 [(题号, [行...]), ...] blocks, cur_no, cur_lines [], None, [] for line in text.splitlines(): m QNUM.match(line) if m: if cur_no is not None: blocks.append((cur_no, cur_lines)) cur_no m.group(1) or m.group(2) cur_lines [QNUM.sub(, line, count1)] elif cur_no is not None: cur_lines.append(line) if cur_no is not None: blocks.append((cur_no, cur_lines)) return blocks逻辑说明split_blocks只做一件事就是按题号切片不做任何字段识别。这样切块逻辑和字段逻辑解耦后面调选项正则时不会把切块搞坏。QNUM用非捕获组加两个捕获组group(1)是(1)这种带括号的group(2)是1.和3-12.取的时候用or兜底。QNUM.sub(, line, count1)把题号从题干里摘掉count1很关键否则题干里出现「1.」这种编号也会被误删。3.3 状态机兜底跨行题干与同行多选项切完块之后逐块解析。这里必须用状态机而不是逐行正则原因是一道题的答案行可能隔了几十行空行也可能是下一题的前一行。def parse_block(no, lines): stem_parts, options, answer [], {}, None for raw in lines: line raw.strip() if not line: continue ma ANS.match(line) if ma: answer ma.group(1).replace( , ).upper() continue # 答案行不再往题干里塞 mo OPT.match(line) if mo: options[mo.group(1).upper()] mo.group(2).strip() continue if options: # 已经进入选项区又出现非选项行说明是选项的续行 last sorted(options)[-1] options[last] line else: stem_parts.append(line) # 还在题干区 return { no: no, stem: .join(stem_parts).strip(), option_a: options.get(A, ), option_b: options.get(B, ), option_c: options.get(C, ), option_d: options.get(D, ), answer: answer or , }对于「四个选项挤在一行」的排版OPT.match只会命中第一个 A剩下的会被当成续行拼进 A 里。兜底办法是在进入解析前做一次预展开用re.split(r(?[A-D][\.、\)]\s), line)把同一行切开再走上面的状态机。注意这个切分正则要加前瞻断言否则切分会把选项字母本身吃掉。3.4 字段落库前的四道校验拆完不能直接写库有些题本来就是坏的。校验规则如下表任一条不通过就打上suspect标记而不是丢弃人工复核还有机会捞回来校验项判断规则常见成因题干非空len(stem) 6题号被误切题干并入了上一题选项齐全选择题至少 A、B 两个非空选项用图片表达转换后丢失答案合法答案字符集是 ABCD 的子集答案是「略」或在解析区答案落在选项内答案字母都能在 options 里找到选项符号是 Wingdings回填错位判断题和填空题的答案常常写成「正确」「错误」或者一整句这时ANS正则要吃两个分支把[A-Da-d\s]{1,8}换成.{1,40}再在落库时按题型分流。这一步做完一份 Word 题库就变成了可以写 SQL 的结构化数据。4. 用 SQLite FTS5 给题库做中文全文检索与近重复题去重4.1 表结构与批量导入CREATE TABLE questions ( id INTEGER PRIMARY KEY, no TEXT, stem TEXT NOT NULL, option_a TEXT, option_b TEXT, option_c TEXT, option_d TEXT, answer TEXT, suspect INTEGER DEFAULT 0 ); CREATE UNIQUE INDEX idx_q_hash ON questions(stem);import sqlite3, hashlib, unicodedata, re def norm(s: str) - str: 归一化全角转半角、去空白与标点、统一小写 s unicodedata.normalize(NFKC, s).lower() return re.sub(r[\s\u3000。、()《》\[\]【】], , s) conn sqlite3.connect(mse.db) cur conn.cursor() for it in items: # items 来自第 3 章的解析结果 if not it[stem]: continue cur.execute( INSERT OR IGNORE INTO questions (no, stem, option_a, option_b, option_c, option_d, answer, suspect) VALUES (?,?,?,?,?,?,?,?), (it[no], it[stem], it[option_a], it[option_b], it[option_c], it[option_d], it[answer], it[suspect])) conn.commit()norm里用 NFKC 做兼容分解能把全角数字和罗马数字统一掉INSERT OR IGNORE配合idx_q_hash其实只做了「题干完全一致」这一层去重真正的近重复还在下面。注意唯一索引建在原始 stem 上而不是 norm 之后的值因为归一化要去标点会误伤「α-Fe」和「αFe」这类本来就不同的题干。4.2 FTS5 建索引中文场景一定要用 trigram默认的 unicode61 分词器按空白和标点切词中文没有空格一整句会被当成一个 token搜「密排面」什么都搜不到。FTS5 从 3.34 开始提供 trigram 分词器按三字符滑窗建索引正好适配中文子串检索CREATE VIRTUAL TABLE q_fts USING fts5( stem, option_a, option_b, option_c, option_d, contentquestions, -- 外部内容表不重复存正文 content_rowidid, tokenizetrigram ); INSERT INTO q_fts(q_fts) VALUES(rebuild);参数说明contentquestions让 FTS 表只存索引不存原文省一半空间tokenizetrigram是中文能被搜到的关键rebuild命令在每次批量导入之后跑一次把外部表的变动同步进索引。查询语句长这样SELECT q.id, q.stem, bm25(q_fts) AS score FROM q_fts JOIN questions q ON q.id q_fts.rowid WHERE q_fts MATCH ? ORDER BY score LIMIT 20;注意trigram 的最小匹配长度是 3 个字符。搜「铁」或者「晶格」这种一两个字的词索引会退化成扫描性能断崖下跌。常见做法是查询前判断长度短查询改走LIKE %词%别硬塞给 MATCH。bm25()返回的是负值越小越相关所以ORDER BY score升序就行不需要加 DESC——这是很多人第一次用会写反的地方。4.3 近重复题的判定先精确后模糊题库合并常见的问题是一道题出现三遍只是标点和空格不同。精确层由唯一索引解决模糊层用字符 3-gram 集合的 Jaccard 相似度def shingles(s, k3): s norm(s) return {s[i:ik] for i in range(max(len(s) - k 1, 1))} def jaccard(a, b): sa, sb shingles(a), shingles(b) return len(sa sb) / len(sa | sb) if sa | sb else 0.0实践上 0.85 是一条比较稳的阈值题干长度普遍在 15 到 60 字之间时0.85 能抓住「把以下哪个选项正确」和「下列选项正确的是」这种改写又不会把「面心立方」和「体心立方」两道正经题误判成重复。相似度计算是 O(n²)题库量在五千题以内可以直接两两比超过这个量先按题干长度分桶只在相邻桶之间比能砍掉大部分比较次数。4.4 查询参数怎么写进实际使用日常用得最多的三种查询按关键词找题、按章节找题、找出所有做错过的题。关键词查询的参数绑法要注意MATCH 的值不能直接用用户输入拼接否则双引号会破坏语法def search(cur, kw, limit20): kw kw.strip().replace(, ) # 转义双引号 if len(kw) 3: cur.execute(SELECT id, stem FROM questions WHERE stem LIKE ? LIMIT ?, (f%{kw}%, limit)) else: cur.execute(SELECT q.id, q.stem FROM q_fts JOIN questions q ON q.id q_fts.rowid WHERE q_fts MATCH ? ORDER BY bm25(q_fts) LIMIT ?, (f{kw}, limit)) return cur.fetchall()f{kw}外面套双引号是把整个短语当成一个 phrase 查询避免 trigram 把「面心立方」拆成「面心」和「立方」两个独立条件。如果确实想要分词效果去掉外面的引号即可两种行为的差异在调试时用EXPLAIN QUERY PLAN看得很清楚。5. 进阶把结构化的材料题库接进每日自测与错题追踪5.1 用 due 字段实现简化版间隔重复建一张练习记录表把上次练习时间和熟练度存下来每天只抽到期该复习的题CREATE TABLE review ( qid INTEGER PRIMARY KEY REFERENCES questions(id), level INTEGER DEFAULT 0, -- 熟练度 0-5 due_date TEXT, -- 下次到期日 YYYY-MM-DD wrong_cnt INTEGER DEFAULT 0 );抽题 SQL按到期日升序、错题优先SELECT q.id, q.stem, q.option_a, q.option_b, q.option_c, q.option_d, q.answer FROM questions q JOIN review r ON r.qid q.id WHERE r.due_date date(now) ORDER BY r.wrong_cnt DESC, r.due_date ASC LIMIT 20;5.2 答对答错的间隔推进规则答题结果level 变化下次间隔答对且 level 317 天答对且 level 1-213 天答对且 level 011 天答错归零当天import datetime DELTA {0: 1, 1: 3, 2: 3, 3: 7, 4: 14, 5: 30} def grade(cur, qid, correct): cur.execute(SELECT level FROM review WHERE qid?, (qid,)) row cur.fetchone() lv row[0] if row else 0 if correct: lv min(lv 1, 5) due datetime.date.today() datetime.timedelta(daysDELTA[lv]) cur.execute(UPDATE review SET level?, due_date?, wrong_cnt0 WHERE qid?, (lv, due.isoformat(), qid)) else: # 答错当天必须重做错题计数加一用于排序时插队 cur.execute(UPDATE review SET level0, due_date?, wrong_cntwrong_cnt1 WHERE qid?, (datetime.date.today().isoformat(), qid)) cur.connection.commit()5.3 每天跑一次的落地方式把抽题脚本包成一个函数用 cron 或 Windows 任务计划在固定时间跑输出当天要练的题到一个 Markdown 文件直接在编辑器里作答# 每早 7:30 生成当日题单 30 7 * * * cd /path/to/mse /usr/bin/python3 daily.py /var/log/mse_daily.log 21日志里至少要记录三样东西本次抽到多少题、有多少题处于suspect状态被自动跳过、哪些题的wrong_cnt已经超过 3。第三项是真正有用的信号——同一道题错了三遍以上说明不是记忆问题而是知识点本身没搞懂该回去翻教材对应章节而不是继续在题库里刷。本文还有配套的精品资源点击获取