﻿---
title: "知识树的主结构：图书馆分类法够不够用"
description: "想把博客文章里的知识点做成游戏那样能一格一格点亮的知识树，第一个想到的是图书馆分类法。顺着这个念头往下追：图书馆分类放进这些文章会遇到什么，游戏里的树到底是什么结构，学科本体、叙词表、刻面分类各补上了哪一块，“点亮”背后的知识空间理论，点亮的深浅怎么分，最后这几样东西各自该放在树的哪一层。"
pubDate: 2026-10-24
tags: ["知识组织", "分类法", "知识空间理论", "学习", "方法论"]
hero: "./hero.svg"
refs:
  - id: clc
    title: "中国图书馆分类法（第五版）"
    url: http://clc.nlc.cn/
    author: 《中国图书馆分类法》编辑委员会
    site: 国家图书馆出版社
    date: "2010"
    note: "五个基本部类、22 个大类，类号由字母和数字组成，如 TP 自动化技术、计算机技术"
  - id: ddc
    title: "Dewey Decimal Classification"
    url: https://www.oclc.org/en/dewey.html
    site: OCLC
    note: "十个大类，三位数字加小数逐级细分，如 005.8 数据安全、612.1 血液与循环生理"
  - id: udc
    title: "Universal Decimal Classification"
    url: https://udcc.org/
    site: UDC Consortium
    note: "在杜威基础上发展而来，可以用冒号 `:` 把两个类号组配成一个"
  - id: ranganathan
    title: "Colon Classification"
    url: https://en.wikipedia.org/wiki/Colon_classification
    author: S. R. Ranganathan
    date: "1933"
    note: "第一个系统的刻面分类法；五个基本范畴 PMEST：本体（Personality）、物质（Matter）、动作（Energy）、空间（Space）、时间（Time）"
  - id: acm-ccs
    title: "The 2012 ACM Computing Classification System"
    url: https://www.acm.org/publications/class-2012
    site: ACM
    note: "多重上位的概念体系，以 SKOS 格式发布；论文作者为每篇论文选多个概念"
  - id: acmart
    title: "acmart: Class for typesetting publications of ACM"
    url: https://ctan.org/pkg/acmart
    site: CTAN
    note: "`\\ccsdesc[500]{...}` 给概念标权重，500 / 300 / 100 对应 High / Medium / Low"
  - id: msc2020
    title: "Mathematics Subject Classification 2020 (MSC2020)"
    url: https://msc2020.org/
    site: Mathematical Reviews & zbMATH
    note: "三级：63 个两位类、529 个三位类、6022 个五位类，如 11G15；论文标一个主分类加若干次分类"
  - id: skos
    title: "SKOS Simple Knowledge Organization System Reference"
    url: https://www.w3.org/TR/skos-reference/
    site: W3C Recommendation
    date: 2009-08-18
    note: "`skos:broader` / `skos:narrower` / `skos:related`、`skos:prefLabel` / `skos:altLabel`；`skos:related` 是对称关系，`skos:broader` 本身不传递，另有 `skos:broaderTransitive`"
  - id: doignon1985
    title: "Spaces for the assessment of knowledge"
    url: https://doi.org/10.1016/S0020-7373(85)80031-6
    author: Jean-Paul Doignon, Jean-Claude Falmagne
    site: International Journal of Man-Machine Studies 23(2)
    date: "1985"
    note: "知识状态是一个人能解出的题目的集合；知识空间是可能的知识状态组成的族，对并运算封闭"
  - id: falmagne2015
    title: "Knowledge Spaces and Learning Spaces"
    url: https://arxiv.org/abs/1511.06757
    author: Jean-Claude Falmagne, Jean-Paul Doignon 等
    date: "2015"
    note: "外缘（outer fringe）：再加上就仍是合法状态的项，即下一步准备好学的；内缘（inner fringe）：去掉后仍合法的项；学习空间里一个状态由它的两个缘唯一确定。ALEKS 据此推荐下一步"
  - id: krathwohl2002
    title: "A Revision of Bloom's Taxonomy: An Overview"
    url: https://doi.org/10.1207/s15430421tip4104_2
    author: David R. Krathwohl
    site: Theory Into Practice 41(4)
    date: "2002"
    note: "修订版六级：记忆、理解、应用、分析、评价、创造；完整版见 Anderson & Krathwohl (2001) A Taxonomy for Learning, Teaching, and Assessing"
---

这个博客写到现在有 43 篇文章，每篇在 frontmatter 里带几个标签。把所有标签拉出来数一数，一共 123 个，其中 87 个只用过一次。用得最多的是“AI”（8 篇）和“源码分析”（7 篇），后面跟着“Git”“区块链”“成本测算”这些。

我想把这些文章里的知识点整理成游戏里那种知识树：读完一篇，树上亮起几格；亮了的格子旁边，又露出几格可以接着点的。第一个想到的现成办法是图书馆的分类法，几百万本书都分得下，几十篇文章应该不在话下。

可是图书馆的分类，真的就是那种能点亮的树吗？如果不是，差在哪里？除了图书馆的办法，还有没有更合适的？本文就从把文章放进图书馆分类开始，一步步往下看。

## 一、图书馆分类法

### 它解决的是什么问题

图书馆分类法要解决的是一件很实际的事：书架上每本书放在哪。国内的图书馆用《中国图书馆分类法》（中图法），分 22 个大类，每类一个字母，往下用数字细分，比如 T 是工业技术，TP 是其中的自动化技术、计算机技术，TP3 是计算技术、计算机技术[^clc]。国外多用杜威十进分类法，十个大类，三位数字加小数逐级往下，005 是计算机编程与软件，005.8 是数据安全[^ddc]。

每本书拿到一个分类号，加上作者号就成了书脊上的索书号。书只能放在一个书架上，所以**一本书只有一个主分类号**，这是图书馆分类最根本的约束。

### 现象：放进博客文章以后

我们拿几篇文章试着归类。下表的类号是我对照类目表归的，只到二三级，不是查馆藏得来。

<figure class="dd dd-figure dt-figure">
<div class="dt-panel">
<div class="dt-title" id="kt-lib-title">几篇文章放进中图法和杜威</div>
<div class="dt-scroll">
<table class="dt" aria-labelledby="kt-lib-title">
<colgroup><col style="width:24%"><col style="width:26%"><col style="width:16%"><col></colgroup>
<thead>
<tr><th scope="col">文章</th><th scope="col">中图法</th><th scope="col">杜威</th><th scope="col">文章里的知识点</th></tr>
</thead>
<tbody>
<tr><th scope="row">TOTP 与 Google 身份验证器</th><td class="warn">TP309 计算机安全，或 TN918 保密通信</td><td class="mono">005.8</td><td>HMAC、HOTP、TOTP、数字证书、钓鱼</td></tr>
<tr><th scope="row">Git 服务器是怎么工作的</th><td>TP311 软件</td><td class="mono">005.1</td><td>Git 对象模型、传输协议、GitLab、Gerrit、walgit</td></tr>
<tr><th scope="row">Hyperliquid 为什么快</th><td class="warn">F83 金融，或 TP311 软件</td><td class="mono warn">332.4 或 005</td><td>区块链、共识算法、订单簿</td></tr>
<tr><th scope="row">两个对不上的 HRV 数字</th><td>R33 人体生理学</td><td class="mono">612.1</td><td>RR 间期、SDNN、RMSSD、自主神经</td></tr>
<tr><th scope="row">注意力有弹性吗</th><td class="warn">B84 心理学，或 R33</td><td class="mono">153.7</td><td>注意资源、HRV、反应时变异</td></tr>
<tr><th scope="row">谁定方向，谁来干活</th><td class="warn">C93 管理学，或 B84</td><td class="mono warn">658 或 158</td><td>形式权威与实际权威、失败预设</td></tr>
</tbody>
</table>
</div>
<p class="dt-note">黄色的格子都是“两处都说得通”，只能挑一个。</p>
</div>
<figcaption>图 1 · 一篇文章常跨两个大类，而分类号只到文章，进不到文章里的知识点</figcaption>
</figure>

归完一遍，能看到三个问题。

**一篇文章常常跨类。** TOTP 那篇讲的是认证，放在计算机安全下面说得通，放在密码通信下面也说得通。图书馆有一套规则帮馆员挑一个，比如按“主要论述的方面”、按“使用者最可能去哪找”，但挑完之后，另一边的读者就找不到它了。中图法和杜威都允许加“参见”，可那只是一行注释，不是结构。

**粒度停在文章这一级。** 分类号给的是整篇文章的位置，而我们要点亮的是文章里的知识点。TOTP 那一篇里就有 HMAC、HOTP、TOTP、数字证书好几个，它们分别属于不同的小类，一个分类号装不下。

**树枝的含义是“属于”，不是“先学”。** TP311 下面挂着 TP311.1、TP311.5，意思是“后者是前者的一部分”。点亮了 TP311，能说明什么？说明不了我接下来能学什么。

前两个问题，后面会看到别的分类体系能补上。第三个问题更根本：**游戏里那棵树，树枝的意思到底是什么？**

## 二、游戏里的知识树

### 现象：点亮一格，旁边才露出几格

玩过《文明》或者《流放之路》的都熟悉这种界面。《文明》的科技树里，一项科技通常要先研究完一两项前置科技才能开始；《流放之路》的天赋盘上，只能点和已点亮格子相邻的格子。树上的位置只是摆放，真正起作用的是格子之间的那条线：**这条线说的是“先有 A，才能有 B”。**

这和分类树里的线不是一回事。分类树里，一个节点只有一个父类，线的意思是“B 是 A 的一种”；科技树里，一个节点可以有好几个前置，也可以是好几条线的共同前置，线的意思是“B 要求 A”。数学上，前者是一棵树，后者是一张有向无环图。

### 分析：同一个知识点在两种结构里

拿“哈希函数”这个知识点看最清楚。Git 用它给每个对象起名字，TOTP 用它算 HMAC，区块链用它把区块串起来，三篇文章都要先懂它。

<figure class="dd dd-figure">
<div class="dd-scroll">
<svg class="dd-svg" viewBox="0 0 800 400" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="kt-tree-title kt-tree-desc">
<title id="kt-tree-title">同一个“哈希函数”在分类树和依赖图里</title>
<desc id="kt-tree-desc">左边是分类树：TP 下面分出 TP309 安全和 TP311 软件，F 下面是 F83 金融，再往下分别是 totp、git-server、hyperliquid 三篇文章。哈希函数只能实线挂在 TOTP 那一支下，另外两支只能用虚线框写“参见”。右边是依赖图：哈希函数在最上面，用强调色分出三条线，分别通往 HMAC、Git 对象模型、区块链，再往下是 HOTP 与 TOTP、Git 传输协议与 Git 服务器、共识算法与知识共识链。</desc>
<defs>
<marker id="kt-tree-arrow" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto"><path class="dd-head" d="M1 1 L7 4 L1 7"/></marker>
<marker id="kt-tree-arrow-acc" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto"><path class="dd-head-acc" d="M1 1 L7 4 L1 7"/></marker>
</defs>
<rect class="dd-frame" width="800" height="400" rx="10"/>
<rect class="dd-zone" x="16" y="16" width="376" height="368" rx="8"/>
<rect class="dd-zone" x="408" y="16" width="376" height="368" rx="8"/>
<text class="t-zone" x="32" y="44">分类树 · 每个节点一个父类</text>
<text class="t-zone" x="424" y="44">依赖图 · 一个节点可以有多个后继</text>
<line class="dd-edge" x1="86" y1="100" x2="86" y2="136"/>
<line class="dd-edge" x1="206" y1="100" x2="206" y2="136"/>
<line class="dd-edge" x1="324" y1="100" x2="324" y2="136"/>
<line class="dd-edge" x1="86" y1="172" x2="86" y2="208"/>
<line class="dd-edge" x1="206" y1="172" x2="206" y2="208"/>
<line class="dd-edge" x1="324" y1="172" x2="324" y2="208"/>
<line class="dd-edge-acc" x1="86" y1="244" x2="86" y2="280"/>
<line class="dd-edge dd-dash" x1="206" y1="244" x2="206" y2="280"/>
<line class="dd-edge dd-dash" x1="324" y1="244" x2="324" y2="280"/>
<rect class="dd-card" x="32" y="64" width="228" height="36" rx="6"/>
<text class="t-name" x="48" y="87">TP <tspan class="t-note">计算机技术</tspan></text>
<rect class="dd-card" x="272" y="64" width="104" height="36" rx="6"/>
<text class="t-name" x="288" y="87">F <tspan class="t-note">经济</tspan></text>
<rect class="dd-card" x="32" y="136" width="108" height="36" rx="6"/>
<text class="t-name" x="44" y="159">TP309 <tspan class="t-note">安全</tspan></text>
<rect class="dd-card" x="152" y="136" width="108" height="36" rx="6"/>
<text class="t-name" x="164" y="159">TP311 <tspan class="t-note">软件</tspan></text>
<rect class="dd-card" x="272" y="136" width="104" height="36" rx="6"/>
<text class="t-name" x="284" y="159">F83 <tspan class="t-note">金融</tspan></text>
<rect class="dd-card" x="32" y="208" width="108" height="36" rx="6"/>
<text class="t-mono" x="48" y="231">totp</text>
<rect class="dd-card" x="152" y="208" width="108" height="36" rx="6"/>
<text class="t-mono" x="168" y="231">git-server</text>
<rect class="dd-card" x="272" y="208" width="104" height="36" rx="6"/>
<text class="t-mono" x="284" y="231">hyperliquid</text>
<rect class="dd-card" x="32" y="280" width="108" height="36" rx="6"/>
<text class="t-name t-acc" x="48" y="303">哈希函数</text>
<rect class="dd-derived" x="152" y="280" width="108" height="36" rx="6"/>
<text class="t-note" x="168" y="303">参见 哈希函数</text>
<rect class="dd-derived" x="272" y="280" width="104" height="36" rx="6"/>
<text class="t-note" x="284" y="303">参见 哈希函数</text>
<text class="t-say" x="32" y="348">虚线：同一个知识点在别的分支下</text>
<text class="t-say" x="32" y="368">只能写一行“参见”。</text>
<path class="dd-edge-acc" d="M598 100 V134" marker-end="url(#kt-tree-arrow-acc)"/>
<path class="dd-edge-acc" d="M598 118 H486 Q478 118 478 126 V134" marker-end="url(#kt-tree-arrow-acc)"/>
<path class="dd-edge-acc" d="M598 118 H710 Q718 118 718 126 V134" marker-end="url(#kt-tree-arrow-acc)"/>
<path class="dd-edge" d="M478 172 V206" marker-end="url(#kt-tree-arrow)"/>
<path class="dd-edge" d="M598 172 V206" marker-end="url(#kt-tree-arrow)"/>
<path class="dd-edge" d="M718 172 V206" marker-end="url(#kt-tree-arrow)"/>
<path class="dd-edge" d="M478 244 V278" marker-end="url(#kt-tree-arrow)"/>
<path class="dd-edge" d="M598 244 V278" marker-end="url(#kt-tree-arrow)"/>
<path class="dd-edge" d="M718 244 V278" marker-end="url(#kt-tree-arrow)"/>
<rect class="dd-card" x="544" y="64" width="108" height="36" rx="6"/>
<text class="t-name t-acc" x="598" y="87" text-anchor="middle">哈希函数</text>
<rect class="dd-card" x="424" y="136" width="108" height="36" rx="6"/>
<text class="t-name" x="478" y="159" text-anchor="middle">HMAC</text>
<rect class="dd-card" x="544" y="136" width="108" height="36" rx="6"/>
<text class="t-name" x="598" y="159" text-anchor="middle">Git 对象模型</text>
<rect class="dd-card" x="664" y="136" width="108" height="36" rx="6"/>
<text class="t-name" x="718" y="159" text-anchor="middle">区块链</text>
<rect class="dd-card" x="424" y="208" width="108" height="36" rx="6"/>
<text class="t-name" x="478" y="231" text-anchor="middle">HOTP</text>
<rect class="dd-card" x="544" y="208" width="108" height="36" rx="6"/>
<text class="t-name" x="598" y="231" text-anchor="middle">Git 传输协议</text>
<rect class="dd-card" x="664" y="208" width="108" height="36" rx="6"/>
<text class="t-name" x="718" y="231" text-anchor="middle">共识算法</text>
<rect class="dd-card" x="424" y="280" width="108" height="36" rx="6"/>
<text class="t-name" x="478" y="303" text-anchor="middle">TOTP</text>
<rect class="dd-card" x="544" y="280" width="108" height="36" rx="6"/>
<text class="t-name" x="598" y="303" text-anchor="middle">Git 服务器</text>
<rect class="dd-card" x="664" y="280" width="108" height="36" rx="6"/>
<text class="t-name" x="718" y="303" text-anchor="middle">知识共识链</text>
<text class="t-say" x="424" y="348">点亮哈希函数，三条线都能往前走一步。</text>
</svg>
</div>
<figcaption>图 2 · 分类树里“哈希函数”只能挂在一处；在依赖图里，它同时是三条线的起点</figcaption>
</figure>

左边的分类树里，哈希函数只能实打实挂在一个分支下，另两处只剩“参见”。右边的依赖图里，它在最上面，三条线都从它出发。点亮它，对三个方向都有意义；这正是游戏里那种“点了这格，好几处同时解锁”的感觉。

**所以主结构里必须有一条“先学 A 才能学 B”的边，而图书馆分类法里没有这条边。** 那么，那些比图书馆更细、更新的分类体系，有没有这条边？

## 三、学科本体与叙词表

### 学科本体：一篇标多个概念

学术界的分类体系，最早遇到的就是“一篇跨好几类”的问题，因为论文比书更专。ACM 的计算机分类体系 ACM CCS 2012 做了两个改变：一是允许一个概念有多个上位，比如同一个概念既挂在“安全”下，也挂在“网络”下；二是一篇论文可以选好几个概念，每个标上权重[^acm-ccs]。在 ACM 的 LaTeX 模板里，作者这样写：

```latex
\ccsdesc[500]{Security and privacy~Authentication}
\ccsdesc[300]{Security and privacy~Hash functions and message authentication codes}
```

500、300、100 分别是高、中、低三档相关度[^acmart]。数学界的 MSC2020 也类似：分三级，63 个两位大类、529 个三位类、6022 个五位类，每篇论文标一个主分类，再加若干次分类[^msc2020]。

这样一来，图 1 里“两处都说得通”的问题解决了：TOTP 那篇可以同时标认证和哈希两个概念，一高一中。五位一级的类目也细到了知识点的粒度。

但这两套体系都只管自己的学科：ACM CCS 里没有 HRV，MSC 里没有 Git。而且它们的边还是“属于”，没有“先学”。

### 叙词表：存储格式和同义词

把这类概念体系写成机器能读的格式，W3C 有一个标准叫 SKOS，ACM CCS 就是用它发布的。SKOS 里每个概念有一个首选名、若干别名，概念之间有三种关系：更宽（`broader`）、更窄（`narrower`）、相关（`related`）[^skos]。

<figure class="dd dd-figure">
<div class="dd-scroll">
<svg class="dd-svg" viewBox="0 0 800 336" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="kt-skos-title kt-skos-desc">
<title id="kt-skos-title">用 SKOS 写下 TOTP 这个知识点</title>
<desc id="kt-skos-desc">一张记录卡，类型是 skos:Concept，标识 ex:totp。字段有：首选名 TOTP，两个别名“基于时间的一次性密码”和“动态口令”，上位概念 ex:two-factor-auth，相关概念 ex:hotp，所属概念体系 ex:blog-knowledge。卡片下方一个虚线框写着 ex:requires ex:hotp，注明这是自定义属性，SKOS 标准里没有。右侧说明：related 没有方向，A related B 等于 B related A；先学谁只能自己加一条边。</desc>
<rect class="dd-frame" width="800" height="336" rx="10"/>
<rect class="dd-zone" x="16" y="16" width="768" height="304" rx="8"/>
<text class="t-zone" x="32" y="44">SKOS 概念 · 写成 Turtle 的样子</text>
<rect class="dd-card" x="32" y="64" width="500" height="188" rx="6"/>
<path class="dd-card-head" d="M38 64 H526 Q532 64 532 70 V96 H32 V70 Q32 64 38 64 Z"/>
<text class="t-name" x="48" y="85">skos:Concept</text>
<text class="t-mono" x="516" y="85" text-anchor="end">ex:totp</text>
<text class="t-mono" x="48" y="113">skos:prefLabel</text>
<text class="t-val" x="200" y="113">"TOTP"</text>
<line class="dd-hair" x1="32" y1="122" x2="532" y2="122"/>
<text class="t-mono" x="48" y="139">skos:altLabel</text>
<text class="t-val" x="200" y="139">"基于时间的一次性密码"@zh</text>
<line class="dd-hair" x1="32" y1="148" x2="532" y2="148"/>
<text class="t-mono" x="48" y="165">skos:altLabel</text>
<text class="t-val" x="200" y="165">"动态口令"@zh</text>
<line class="dd-hair" x1="32" y1="174" x2="532" y2="174"/>
<text class="t-mono" x="48" y="191">skos:broader</text>
<text class="t-val" x="200" y="191">ex:two-factor-auth</text>
<line class="dd-hair" x1="32" y1="200" x2="532" y2="200"/>
<text class="t-mono" x="48" y="217">skos:related</text>
<text class="t-val t-acc" x="200" y="217">ex:hotp</text>
<text class="t-note" x="516" y="217" text-anchor="end">对称</text>
<line class="dd-hair" x1="32" y1="226" x2="532" y2="226"/>
<text class="t-mono" x="48" y="243">skos:inScheme</text>
<text class="t-val" x="200" y="243">ex:blog-knowledge</text>
<rect class="dd-derived" x="32" y="268" width="500" height="36" rx="6"/>
<text class="t-mono" x="48" y="291">ex:requires</text>
<text class="t-val t-acc" x="200" y="291">ex:hotp</text>
<text class="t-note" x="516" y="291" text-anchor="end">自定义属性</text>
<text class="t-say" x="556" y="208">related 没有方向：</text>
<text class="t-say" x="556" y="228">A related B，也就是 B related A。</text>
<text class="t-say" x="556" y="284">虚线：SKOS 里没有“先学”，</text>
<text class="t-say" x="556" y="304">只能自己加一条边。</text>
</svg>
</div>
<figcaption>图 3 · SKOS 能记下名字、别名、上下位和相关，唯独记不下“先学谁”</figcaption>
</figure>

SKOS 有两处正好用得上。别名解决了同义词：“TOTP”“动态口令”“基于时间的一次性密码”是同一个节点，读者搜哪个都能落到这里。多重上位也允许一个概念挂在几个上位下面。

可是三种关系里，`related` 是对称的：写了 TOTP 相关 HOTP，就等于也写了 HOTP 相关 TOTP，看不出谁先谁后。`broader` 是“属于”，也不是“先学”。要表达“先学 HOTP 才能学 TOTP”，只能像图 3 下面那样，自己加一个 `ex:requires` 属性。**SKOS 适合做存储格式，不适合当主结构。**

学科本体和叙词表都还是在同一个方向上把分类做细。还有一种思路换了方向：**与其给一篇文章挑一个位置，不如承认它同时站在好几个维度上。**

## 四、刻面分类

### 现象：标签里混着几种东西

回头看开头那 123 个标签，里面其实混着几种性质不同的词。“Git”“Prometheus”“HRV”说的是文章讲的**对象**；“源码分析”“成本测算”“回测”说的是文章的**做法**；“入门”说的是读者的**起点**。它们被放在同一个平铺的列表里，所以才会长到 123 个，而且大部分只用一次。

### 原因与解决：把维度分开

印度图书馆学家阮冈纳赞在 1933 年提出冒号分类法，第一次把这件事系统化：一本书不再放进预先排好的格子，而是在几个基本范畴上各取一个值，再组配起来。他的五个范畴是本体、物质、动作、空间、时间，简称 PMEST[^ranganathan]。后来的 UDC 也允许用冒号把两个类号组配成一个[^udc]。

把博客标签按这个思路分开，就是下表。

<figure class="dd dd-figure dt-figure">
<div class="dt-panel">
<div class="dt-title" id="kt-facet-title">按刻面拆开的几篇文章</div>
<div class="dt-scroll">
<table class="dt" aria-labelledby="kt-facet-title">
<colgroup><col style="width:30%"><col><col class="hl"><col></colgroup>
<thead>
<tr><th scope="col">文章</th><th scope="col">对象</th><th scope="col" class="acc">做法</th><th scope="col">中图法大类</th></tr>
</thead>
<tbody>
<tr><th scope="row">Git 服务器是怎么工作的</th><td>Git、GitLab、Gerrit</td><td>源码分析</td><td class="mono">TP</td></tr>
<tr><th scope="row">E2B 和 Docker 是什么关系</th><td>E2B、Docker</td><td>源码分析</td><td class="mono">TP</td></tr>
<tr><th scope="row">租云 GPU 转卖大模型 API</th><td>GPU、大模型 API</td><td class="acc">成本测算</td><td class="mono">F</td></tr>
<tr><th scope="row">几十美元的小盒子跑大模型</th><td>边缘设备、内存带宽</td><td class="acc">成本测算</td><td class="mono">TP</td></tr>
<tr><th scope="row">去中心化推理</th><td>llama.cpp RPC</td><td class="acc">成本测算</td><td class="mono">TP</td></tr>
<tr><th scope="row">纳指 ETF 的时差利差</th><td>513100、溢价</td><td>回测</td><td class="mono">F</td></tr>
</tbody>
</table>
</div>
<p class="dt-note">按“做法 = 成本测算”筛，会同时拿到 TP 和 F 两个大类里的文章。</p>
</div>
<figcaption>图 4 · 刻面把“讲什么”和“怎么做”分成两条轴，按一条轴筛选能横穿图书馆的大类</figcaption>
</figure>

刻面让“一篇跨几类”不再是问题：它本来就该在几条轴上各有一个值。想看所有成本测算类的文章，按做法筛一下就行，不必管它们在中图法里分属 TP 和 F。

但刻面之间是正交的，没有先后。“对象 = Git”和“做法 = 源码分析”是两个并列的维度，谁也不是谁的前置。**刻面是很好的筛选器，可它同样不产生“点亮这格才能点那格”。**

到这里，几种分类方式都试过了，它们回答的都是“这个东西在哪、属于什么”。要找“先学什么”这条边，得去看研究学习本身的理论。

## 五、前置依赖：知识空间理论

### 它在回答什么问题

1985 年，Jean-Paul Doignon 和 Jean-Claude Falmagne 提出了知识空间理论[^doignon1985]。他们关心的问题是：怎样用尽量少的测验题，弄清一个学生到底会什么？

他们的出发点很简单。把一个领域拆成一组知识项（比如一道道代数题），一个人会的那些项组成一个集合，叫他的**知识状态**。关键在于，并不是任意一个集合都可能是某人的状态：一个会解二次方程、却不会解一次方程的学生，几乎不存在。项和项之间有前置关系，只有“前置都在、自己才在”的集合才是合法的状态。

在这个框架里，有两个概念正好对上游戏里的点亮[^falmagne2015]：

- **外缘**（outer fringe）：还没掌握、但前置都已掌握的项。加上任何一个，仍然是合法状态。这就是“下一步能点亮的格子”。
- **内缘**（inner fringe）：已掌握、而且没有别的已掌握项依赖它的项。去掉它，状态仍然合法。这大致是“最近点亮的末梢”。

商业化的自适应学习系统 ALEKS 就建立在这个理论上：先用一轮测验估出学生的知识状态，再从外缘里挑下一道题。

### 用博客文章试一试

我从十几篇文章里抽了 20 个知识点，分到系统、安全、Git、区块链、健康五个区域，再给每个点写上前置。假设一个读者读完了 tmux 那篇、HRV 那篇的前一半、Git 服务器那篇的第一节、TOTP 那篇的 HMAC 部分，点亮的就是图 5 里实心的七格。

<figure class="dd dd-figure">
<div class="dd-scroll">
<svg class="dd-svg" viewBox="0 0 800 460" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="kt-fringe-title kt-fringe-desc">
<title id="kt-fringe-title">点亮七个知识点之后的外缘</title>
<desc id="kt-fringe-desc">五个区域的依赖图。已点亮（实心并带圆点）的有：进程与信号、tmux、哈希函数、HMAC、Git 对象模型、RR 间期、SDNN 与 RMSSD。用强调色标出的外缘是：容器隔离、HOTP、Git 传输协议、区块链、HRV，它们的前置都已点亮。其余虚线框的节点还不能点：微虚拟机、Agent 沙箱、TOTP、Git 服务器、共识算法、知识共识链、注意力弹性、瞳孔测量。哈希函数在顶部的基础区，同时连向 HMAC、Git 对象模型和区块链。</desc>
<defs>
<marker id="kt-fringe-arrow" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto"><path class="dd-head" d="M1 1 L7 4 L1 7"/></marker>
<marker id="kt-fringe-arrow-acc" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto"><path class="dd-head-acc" d="M1 1 L7 4 L1 7"/></marker>
</defs>
<rect class="dd-frame" width="800" height="460" rx="10"/>
<rect class="dd-zone" x="16" y="16" width="144" height="384" rx="8"/>
<rect class="dd-zone" x="168" y="16" width="448" height="88" rx="8"/>
<rect class="dd-zone" x="168" y="112" width="144" height="288" rx="8"/>
<rect class="dd-zone" x="320" y="112" width="144" height="288" rx="8"/>
<rect class="dd-zone" x="472" y="112" width="144" height="288" rx="8"/>
<rect class="dd-zone" x="624" y="16" width="160" height="384" rx="8"/>
<text class="t-zone" x="28" y="44">系统</text>
<text class="t-zone" x="184" y="44">基础</text>
<text class="t-zone" x="180" y="140">安全</text>
<text class="t-zone" x="332" y="140">Git</text>
<text class="t-zone" x="484" y="140">区块链</text>
<text class="t-zone" x="636" y="44">健康</text>
<path class="dd-edge" d="M88 100 V134" marker-end="url(#kt-fringe-arrow)"/>
<path class="dd-edge-acc" d="M148 82 Q156 82 156 90 V218 Q156 226 150 226" marker-end="url(#kt-fringe-arrow-acc)"/>
<path class="dd-edge dd-dash" d="M88 244 V278" marker-end="url(#kt-fringe-arrow)"/>
<path class="dd-edge dd-dash" d="M88 316 V350" marker-end="url(#kt-fringe-arrow)"/>
<path class="dd-edge" d="M392 76 V158" marker-end="url(#kt-fringe-arrow)"/>
<path class="dd-edge" d="M392 92 H248 Q240 92 240 100 V158" marker-end="url(#kt-fringe-arrow)"/>
<path class="dd-edge-acc" d="M392 92 H536 Q544 92 544 100 V158" marker-end="url(#kt-fringe-arrow-acc)"/>
<path class="dd-edge-acc" d="M240 196 V230" marker-end="url(#kt-fringe-arrow-acc)"/>
<path class="dd-edge dd-dash" d="M240 268 V302" marker-end="url(#kt-fringe-arrow)"/>
<path class="dd-edge-acc" d="M392 196 V230" marker-end="url(#kt-fringe-arrow-acc)"/>
<path class="dd-edge dd-dash" d="M392 268 V302" marker-end="url(#kt-fringe-arrow)"/>
<path class="dd-edge dd-dash" d="M544 196 V230" marker-end="url(#kt-fringe-arrow)"/>
<path class="dd-edge dd-dash" d="M544 268 V302" marker-end="url(#kt-fringe-arrow)"/>
<path class="dd-edge" d="M704 100 V134" marker-end="url(#kt-fringe-arrow)"/>
<path class="dd-edge-acc" d="M704 172 V206" marker-end="url(#kt-fringe-arrow-acc)"/>
<path class="dd-edge dd-dash" d="M704 244 V278" marker-end="url(#kt-fringe-arrow)"/>
<path class="dd-edge dd-dash" d="M704 316 V350" marker-end="url(#kt-fringe-arrow)"/>
<rect class="dd-card" x="28" y="64" width="120" height="36" rx="6"/>
<circle class="dd-dot" cx="42" cy="82" r="4"/>
<text class="t-name" x="54" y="87">进程与信号</text>
<rect class="dd-card" x="28" y="136" width="120" height="36" rx="6"/>
<circle class="dd-dot" cx="42" cy="154" r="4"/>
<text class="t-name" x="54" y="159">tmux</text>
<rect class="dd-card" x="28" y="208" width="120" height="36" rx="6"/>
<rect class="dd-edge-acc" x="28" y="208" width="120" height="36" rx="6"/>
<text class="t-name t-acc" x="42" y="231">容器隔离</text>
<rect class="dd-derived" x="28" y="280" width="120" height="36" rx="6"/>
<text class="t-note" x="42" y="303">微虚拟机</text>
<rect class="dd-derived" x="28" y="352" width="120" height="36" rx="6"/>
<text class="t-note" x="42" y="375">Agent 沙箱</text>
<rect class="dd-card" x="332" y="40" width="120" height="36" rx="6"/>
<circle class="dd-dot" cx="346" cy="58" r="4"/>
<text class="t-name" x="358" y="63">哈希函数</text>
<rect class="dd-card" x="180" y="160" width="120" height="36" rx="6"/>
<circle class="dd-dot" cx="194" cy="178" r="4"/>
<text class="t-name" x="206" y="183">HMAC</text>
<rect class="dd-card" x="180" y="232" width="120" height="36" rx="6"/>
<rect class="dd-edge-acc" x="180" y="232" width="120" height="36" rx="6"/>
<text class="t-name t-acc" x="194" y="255">HOTP</text>
<rect class="dd-derived" x="180" y="304" width="120" height="36" rx="6"/>
<text class="t-note" x="194" y="327">TOTP</text>
<rect class="dd-card" x="332" y="160" width="120" height="36" rx="6"/>
<circle class="dd-dot" cx="346" cy="178" r="4"/>
<text class="t-name" x="358" y="183">Git 对象模型</text>
<rect class="dd-card" x="332" y="232" width="120" height="36" rx="6"/>
<rect class="dd-edge-acc" x="332" y="232" width="120" height="36" rx="6"/>
<text class="t-name t-acc" x="346" y="255">Git 传输协议</text>
<rect class="dd-derived" x="332" y="304" width="120" height="36" rx="6"/>
<text class="t-note" x="346" y="327">Git 服务器</text>
<rect class="dd-card" x="484" y="160" width="120" height="36" rx="6"/>
<rect class="dd-edge-acc" x="484" y="160" width="120" height="36" rx="6"/>
<text class="t-name t-acc" x="498" y="183">区块链</text>
<rect class="dd-derived" x="484" y="232" width="120" height="36" rx="6"/>
<text class="t-note" x="498" y="255">共识算法</text>
<rect class="dd-derived" x="484" y="304" width="120" height="36" rx="6"/>
<text class="t-note" x="498" y="327">知识共识链</text>
<rect class="dd-card" x="636" y="64" width="136" height="36" rx="6"/>
<circle class="dd-dot" cx="650" cy="82" r="4"/>
<text class="t-name" x="662" y="87">RR 间期</text>
<rect class="dd-card" x="636" y="136" width="136" height="36" rx="6"/>
<circle class="dd-dot" cx="650" cy="154" r="4"/>
<text class="t-name" x="662" y="159">SDNN 与 RMSSD</text>
<rect class="dd-card" x="636" y="208" width="136" height="36" rx="6"/>
<rect class="dd-edge-acc" x="636" y="208" width="136" height="36" rx="6"/>
<text class="t-name t-acc" x="650" y="231">HRV</text>
<rect class="dd-derived" x="636" y="280" width="136" height="36" rx="6"/>
<text class="t-note" x="650" y="303">注意力弹性</text>
<rect class="dd-derived" x="636" y="352" width="136" height="36" rx="6"/>
<text class="t-note" x="650" y="375">瞳孔测量</text>
<circle class="dd-dot" cx="28" cy="424" r="4"/>
<text class="t-note" x="40" y="428">带圆点：已点亮</text>
<text class="t-say" x="160" y="428">强调色：外缘，前置都亮了，下一步就能点。</text>
<text class="t-say" x="160" y="448">虚线：至少还有一个前置没亮，暂时点不了。</text>
</svg>
</div>
<figcaption>图 5 · 点亮七格之后，外缘是五格：每个区域各有一格可以接着点</figcaption>
</figure>

外缘不用人去数，写一个小脚本就能算。依赖关系写成 YAML，每个节点一行：

```yaml
# region 只管画在地图哪一块，requires 才决定能不能点亮
- {id: 哈希函数,    region: 基础, requires: [],            from: [git-server-from-clone, totp-google-authenticator]}
- {id: HMAC,        region: 安全, requires: [哈希函数],    from: [totp-google-authenticator]}
- {id: HOTP,        region: 安全, requires: [HMAC],        from: [totp-google-authenticator]}
- {id: Git对象模型, region: Git,  requires: [哈希函数],    from: [git-server-from-clone]}
- {id: 区块链,      region: 区块链, requires: [哈希函数],  from: [hyperliquid-speed]}
# ……共 20 个节点
```

脚本做三件事：检查依赖里没有环（有环就没法点亮），检查已点亮的集合是否合法，再算出外缘和内缘。

```python
import sys, yaml
nodes = {n["id"]: n for n in yaml.safe_load(open(sys.argv[1]))}
lit = set(sys.argv[2].split(","))

# 依赖必须是一张无环图：拓扑排序走不完说明有环
seen, order = set(), []
def visit(i, path=()):
    if i in path: sys.exit(f"环：{' → '.join(path + (i,))}")
    if i in seen: return
    for r in nodes[i]["requires"]: visit(r, path + (i,))
    seen.add(i); order.append(i)
for i in nodes: visit(i)

bad = [i for i in lit if not set(nodes[i]["requires"]) <= lit]
print("点亮不合法（前置没亮）：", bad or "无")
fringe = [i for i in order if i not in lit and set(nodes[i]["requires"]) <= lit]
print(f"已点亮 {len(lit)}/{len(nodes)}")
print("下一步能点亮（外缘）：", "、".join(fringe))
inner = [i for i in order if i in lit and not any(i in nodes[j]["requires"] for j in lit)]
print("最近点亮的末梢（内缘）：", "、".join(inner))
```

运行结果：

```text
$ python3 fringe.py tree.yaml 进程与信号,tmux,哈希函数,HMAC,Git对象模型,RR间期,SDNN与RMSSD
点亮不合法（前置没亮）： 无
已点亮 7/20
下一步能点亮（外缘）： 容器隔离、HOTP、Git传输协议、区块链、HRV
最近点亮的末梢（内缘）： tmux、HMAC、Git对象模型、SDNN与RMSSD
```

这五格外缘里，四格指向一篇读了一半的文章（HOTP 在 TOTP 那篇，Git 传输协议在 Git 服务器那篇，HRV 在 HRV 那篇，容器隔离在 E2B 那篇），区块链则指向一篇还没读过的 Hyperliquid。这就是“下一篇读什么”的推荐。

**知识空间理论给了点亮一个精确的含义。不过它的知识项是一道道能判对错的题，而一个知识点“亮了”又该怎么判？读过一遍算不算？**

## 六、点亮的程度

### 现象：读过和会用不是一回事

读完 TOTP 那篇，大概能说出“六位数是用时间算出来的”；但要自己写代码算出和手机上一样的六位数，又是另一回事；再往上，是能说清 TOTP 和短信验证码、通行密钥各自防住了什么。三个人都“读过”这一篇，掌握的程度差得很远。游戏里也常这样处理：同一个天赋可以点一级、二级、三级。

### 解决：用 Bloom 分类给节点分级

教育学里描述“掌握到什么程度”的经典框架是 Bloom 的认知目标分类。2001 年的修订版把它分成六级：记忆、理解、应用、分析、评价、创造[^krathwohl2002]。六级对一个个人知识树来说太细，合并成三级更好用。

<figure class="dd dd-figure dt-figure">
<div class="dt-panel">
<div class="dt-title" id="kt-bloom-title">TOTP 这个节点的三级点亮</div>
<div class="dt-scroll">
<table class="dt" aria-labelledby="kt-bloom-title">
<colgroup><col style="width:14%"><col style="width:20%"><col><col></colgroup>
<thead>
<tr><th scope="col">等级</th><th scope="col">Bloom 层级</th><th scope="col">在 TOTP 上意味着</th><th scope="col">怎么检验</th></tr>
</thead>
<tbody>
<tr><th scope="row">一级</th><td>记忆、理解</td><td>说得出 TOTP 把时间切成 30 秒一段，用段号代替 HOTP 的计数器</td><td>解释为什么手机开着飞行模式也能出码</td></tr>
<tr><th scope="row">二级</th><td>应用</td><td>拿一个密钥和当前时间，自己算出那六位数</td><td>写二十行代码，结果和身份验证器对上</td></tr>
<tr><th scope="row">三级</th><td>分析、评价、创造</td><td>说得清 TOTP、短信验证码、通行密钥各自防住了什么</td><td>给一个登录场景选方案，并说出理由</td></tr>
</tbody>
</table>
</div>
<p class="dt-note">每一级都配一个能判对错的检验，这就回到了知识空间理论“知识项是题目”的要求。</p>
</div>
<figcaption>图 6 · Bloom 分类不决定树的形状，它决定每个节点亮到第几级</figcaption>
</figure>

分级以后，前置关系也可以写得更细：学 TOTP 要求 HOTP 至少一级，而要把 TOTP 点到二级，可能就要求 HMAC 也到二级。

Bloom 分类本身不能当主结构：它对每个节点都是同样的几级，是一条纵向的刻度，不是一张图的骨架。

## 七、各自的位置

### 一个节点长什么样

到这里，前面几种方式各自擅长的地方已经清楚了。它们不是互相替代，而是可以同时出现在同一个节点上，各管一件事。

<figure class="dd dd-figure">
<div class="dd-scroll">
<svg class="dd-svg" viewBox="0 0 800 284" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="kt-node-title kt-node-desc">
<title id="kt-node-title">知识树里一个节点的完整记录</title>
<desc id="kt-node-desc">一张 TOTP 节点的记录卡，六个字段，每个字段右边写着它来自哪种方案：region 为安全，对应中图法 TP309、杜威 005.8，来自图书馆分类，只管画在哪；requires 为 HOTP，用强调色标出，来自知识空间理论，是主结构，决定能不能点亮；facets 为对象两步验证、做法算法拆解，来自刻面分类，用于筛选；levels 为一级说得出、二级算得出、三级比得清，来自 Bloom 分类；altLabel 为动态口令、基于时间的一次性密码，来自 SKOS；from 为 totp-google-authenticator，表示出自哪篇文章。</desc>
<rect class="dd-frame" width="800" height="284" rx="10"/>
<rect class="dd-zone" x="16" y="16" width="768" height="252" rx="8"/>
<text class="t-zone" x="32" y="44">tree.yaml 里的一个节点</text>
<rect class="dd-card" x="32" y="64" width="736" height="188" rx="6"/>
<path class="dd-card-head" d="M38 64 H762 Q768 64 768 70 V96 H32 V70 Q32 64 38 64 Z"/>
<text class="t-name" x="48" y="85">知识点</text>
<text class="t-mono" x="752" y="85" text-anchor="end">TOTP</text>
<text class="t-mono" x="48" y="113">region</text>
<text class="t-val" x="144" y="113">安全（TP309 / 005.8）</text>
<text class="t-note" x="752" y="113" text-anchor="end">图书馆分类 · 只管画在哪一块</text>
<line class="dd-hair" x1="32" y1="122" x2="768" y2="122"/>
<text class="t-mono t-acc" x="48" y="139">requires</text>
<text class="t-val t-acc" x="144" y="139">[HOTP]</text>
<text class="t-say t-acc" x="752" y="139" text-anchor="end">知识空间理论 · 主结构，决定能不能点亮</text>
<line class="dd-hair" x1="32" y1="148" x2="768" y2="148"/>
<text class="t-mono" x="48" y="165">facets</text>
<text class="t-val" x="144" y="165">{对象: 两步验证, 做法: 算法拆解}</text>
<text class="t-note" x="752" y="165" text-anchor="end">刻面 · 筛选</text>
<line class="dd-hair" x1="32" y1="174" x2="768" y2="174"/>
<text class="t-mono" x="48" y="191">levels</text>
<text class="t-val" x="144" y="191">[说得出, 算得出, 比得清]</text>
<text class="t-note" x="752" y="191" text-anchor="end">Bloom · 亮到第几级</text>
<line class="dd-hair" x1="32" y1="200" x2="768" y2="200"/>
<text class="t-mono" x="48" y="217">altLabel</text>
<text class="t-val" x="144" y="217">[动态口令, 基于时间的一次性密码]</text>
<text class="t-note" x="752" y="217" text-anchor="end">SKOS · 同义词</text>
<line class="dd-hair" x1="32" y1="226" x2="768" y2="226"/>
<text class="t-mono" x="48" y="243">from</text>
<text class="t-val" x="144" y="243">[totp-google-authenticator]</text>
<text class="t-note" x="752" y="243" text-anchor="end">出自哪篇文章</text>
</svg>
</div>
<figcaption>图 7 · 一个节点上，五种方案各占一个字段；只有 requires 那一行决定树的形状</figcaption>
</figure>

几个字段里只有 `requires` 决定树的形状。改掉 `region`，节点只是换个地方画；改掉 `requires`，能点亮的顺序就变了。

### 对照：各方案回答什么问题

<figure class="dd dd-figure dt-figure">
<div class="dt-panel">
<div class="dt-title" id="kt-role-title">几种组织方式在知识树里的位置</div>
<div class="dt-scroll">
<table class="dt" aria-labelledby="kt-role-title">
<colgroup><col style="width:30%"><col style="width:22%"><col style="width:20%"><col></colgroup>
<thead>
<tr><th scope="col">方案</th><th scope="col">回答的问题</th><th scope="col">边的含义</th><th scope="col">在知识树里</th></tr>
</thead>
<tbody>
<tr><th scope="row">图书馆分类（中图法、杜威、UDC）</th><td>放在哪</td><td>属于（单一父类）</td><td>地图分区</td></tr>
<tr><th scope="row">学科本体（ACM CCS、MSC2020）</th><td>属于哪些主题</td><td>属于（可多个）</td><td>某个分区要细分时参考</td></tr>
<tr><th scope="row">叙词表（SKOS）</th><td>叫什么，和谁有关</td><td>更宽、更窄、相关</td><td>存储格式、同义词</td></tr>
<tr><th scope="row">刻面分类</th><td>从哪几个角度看</td><td>无（正交的轴）</td><td>筛选</td></tr>
<tr class="hl"><th scope="row" class="acc">知识空间理论</th><td class="acc">先学什么</td><td class="acc">要求（有向、无环）</td><td class="acc">主结构</td></tr>
<tr><th scope="row">Bloom 分类</th><td>掌握到什么程度</td><td>无（每个节点的刻度）</td><td>节点等级</td></tr>
</tbody>
</table>
</div>
</div>
<figcaption>图 8 · 只有知识空间理论的边有方向，它当骨架，其余几种各管一个侧面</figcaption>
</figure>

### 分区用哪一套

剩下一个实际问题：地图分区用什么？可以自己起名字，但我更建议直接借中图法的大类或二级类，像图 7 里那样在 `region` 后面记下对应的类号。原因有两个：分区只影响布局，用一套现成的、大家都熟的划分，不必为“这一块叫什么”反复纠结；以后想和别人的知识树或者图书馆的书目对接，类号就是现成的接口。分区不必细，博客现在的文章，十个以内的区域就够了。

## 八、回到图书馆分类法

回到开头那个念头：图书馆分类法能不能当知识树的主结构？

能用，但只能用在一个地方：**地图的分区**。它把世界上的知识分成了大家都认得的几块，这对“这一格画在哪”很有用。可它的树枝是“属于”，一个知识点只能挂一处，它不知道哈希函数要先于 HMAC，也不知道 HMAC 要先于 HOTP。游戏里那种点亮的感觉，全来自“先有 A，才有 B”这条边，而这条边只能从知识本身的依赖里来，需要我们一条条写出来。

写依赖边比给文章挑分类号费事得多。一个可行的起点是在每篇文章的 frontmatter 里多写两行：这篇教了哪些知识点（`teaches`），读之前最好会哪些（`requires`）。文章就像游戏里的任务，完成一个任务，点亮一组节点。

## 小结

1. 图书馆分类法放得下博客文章吗？放得下，但一篇常跨两个大类，分类号只到文章，进不到知识点，树枝的意思是“属于”而不是“先学”。
2. 游戏里的知识树是什么结构？是一张前置依赖的有向无环图，一个节点可以有多个前置和多个后继。“哈希函数”就同时是 Git、TOTP、区块链三条线的起点。
3. 学科本体和叙词表补上了什么？ACM CCS 和 MSC 允许一篇标多个概念，粒度也更细；SKOS 给了存储格式和同义词。但它们的边仍然是“属于”和对称的“相关”。
4. 刻面分类补上了什么？把“讲什么”和“怎么做”拆成正交的轴，筛选能横穿大类，但轴之间没有先后。
5. “点亮”的精确含义是什么？知识空间理论的外缘：前置都已掌握、下一步能学的项。写成 YAML 加二十几行脚本就能算出来。
6. 点亮的深浅怎么分？用 Bloom 分类合并成三级，每级配一个能判对错的检验。
7. 主结构用什么？用知识点之间的前置依赖图；图书馆分类退为地图分区，刻面做筛选，Bloom 做等级，SKOS 做存储。

## 参考资料
