杂记 › 生活随笔 › 奶奶要连点三下才出来:亲戚关系计算器 36 条规则与 11110 条链的兜底实测

奶奶要连点三下才出来:亲戚关系计算器 36 条规则与 11110 条链的兜底实测

把称谓规则表当被测对象做全枚举:两步 100 条只出 28 个词、兜底 34 条,三步 1000 条兜底 384 条、四步 10000 条兜底 4032 条;长幼在两处被丢掉,6 个快捷示例有 5 个抛错。

先把被测对象摆出来

起因是一条关系链:我按爸爸、爸爸,屏幕上出「爷爷」,以为再补一下就换人,连点三下才等到「奶奶」。这个页面没有文本框,input 与 textarea 都是 0 个,能喂给它的输入只有 22 个按钮,所以「点出来什么」完全由一张关系定义表和一段 if 链决定。我把它当被测对象处理了:模板本体 15121 字节、13713 个 UTF-16 码元、418 个换行,函数体原样切出来跑在 DOM 桩上;插桩版与页面真实管线在两步 100 条、三步 1000 条链上逐条比结果,不一致 0 条——下面这些账都建立在这条校验上。

36 条规则站点,判定顺序就是行号

查找函数 lookupTerm 的块是第 261 到 391 行、131 行、3569 码元。块内 return 出现 38 次,其中 2 次在 chain.some 的回调里、不算规则,剩下 36 次就是 36 条称谓站点;同一段里 if 47 个、else 2 个、三元 14 个。顺序就是源码顺序:第 272 行的单档分支最先,第 388 行那条无条件兜底最后。先后很要紧,一条链只要被前面的站点接住,后面写得再细也走不到。把命中的那行摘成空语句、再看它落到哪,我挑了 5 组:

链命中行结果摘掉命中行之后
father,mother386长辈(女性)388 晚辈(女性)
father,older_brother,wife290姑姑318 伯母
mother,older_brother,wife294姨妈319 舅妈
father,father,son287伯伯/叔叔388 晚辈(男性)
father,son,son388晚辈(男性)函数末尾再无 return

全枚举:兜底与二义各占多少条

把 10 档按链长做笛卡尔积(多维组合的行数是相乘不是相加,这类账旧文 列出所有组合 讲过),正向、男性口径:两步 100 条只出 28 个称谓,兜底 34 条、二义 15 条、专名 51 条摊在 19 个词上;三步 1000 条出 30 个词,兜底涨到 384、二义 165、专名 451;四步 10000 条出 34 个词,兜底 4032、二义 1368。1 到 4 步合计 11110 条。这里说的兜底就是兄弟、姐妹、长辈(男性)、长辈(女性)、晚辈(男性)、晚辈(女性)6 个词,二义则是含斜杠的那 6 个串。

10 档按钮的笛卡尔积:兜底、二义、专名三条线 两步 100 条 兜底 34、二义 15、专名 51 三步 1000 条 兜底 384、二义 165、专名 451 四步 10000 条 兜底 4032、二义 1368;合计 11110 条链

图 1:兜底那条线涨得比词表快

isOlder 与 reverseRel:长幼在两处被丢掉

isOlder 写在第 406 到 412 行,从链尾往回扫,命中 older_ 返回 true、younger_ 返回 false,链里压根没有兄弟档时走第 411 行返回 false。这个默认值直接决定同辈拿到哪个词:两步 100 条里同辈的 8 个词只剩表弟 4 条与表妹 4 条,而这 8 条全部来自默认 false——有效链里真含 younger_ 的是 0 条,含哥、姐那 4 个词也是 0 条。三步 1000 条才有得判:堂弟 5 条、堂哥 5 条、堂妹 1 条、堂姐 1 条,判成弟与妹的 48 条全部来自链中真含 younger_ 的那一组,默认组在这 12 条里占 0 条;链里完全不含兄弟档的有 216 条,全都走第 411 行。

长幼这一维:默认 false 与换向塌档 两步链 100 条 正反不同 74,二次反转还原 34 三步链 1000 条 正反不同 914,二次反转还原 242 同辈 8 词只剩 表弟 4、表妹 4

图 2:换向一次,长幼就没了

另一处在 reverseRel,表体在第 239 到 249 行:M 口径下哥哥、姐姐、弟弟、妹妹 4 个档全部映射成 younger_brother,F 口径下全部映射成 younger_sister,长幼在换向时被抹平;而传 male 时因为它不等于大写 F,恒走 F 那一侧,两张表 10 档全同。塌链是看得见的:[father,older_brother] 与 [father,younger_brother] 换向之后都是 [younger_sister,son]。反向三步枚举出现的 23 个词里,伯伯、嫂子、姐夫、堂哥、表哥、堂姐、表姐出现次数都是 0。

快捷示例 6 条里 5 条抛错,链区写出 undefined

这一页是 亲戚关系计算器,快捷示例一行 6 个按钮,标签是爷爷的儿子、姑姑的儿子、舅舅的女儿、叔叔的妻子、外婆、姑姑,对应 3 档链 4 条、2 档链 2 条。除外婆那条的原文是 'mother,mother' 之外,其余 5 条的 token 串里带 brother 或 sister,而 relDefs 的 10 个键里没有这两个。点下去以后 calculate() 在脚本第 105 行读 def.side 时抛 TypeError: Cannot read properties of undefined (reading 'side')。

抛错之后两个出口的状态并不一致。结果区不被改写:先前算过,称谓就停在上一条的旧值——实测先点出姑姑,再喂一条带 brother 的链,屏幕上仍是姑姑。链区却已经被改写过,因为 addRel 先 push 再渲染,relLabels 取不到 brother,于是 innerHTML 尾部写成字面量 undefined(9 码元);kcChain 也照样把非法 token 原样留着,实测是 ["father","father","brother"]。拿 6 个陌生 token 复核同一个位置,6/6 抛的是同一个错。同一句手工换成合法档 'father,father,older_brother',出来是爷爷。现场该怎么把一条链拆开数人,本文不复述那篇场景文 叫不出称呼的那顿饭,本篇只谈这张表。

FAQ

Q:拿到兜底词,是不是我链拼错了?

不一定。两步 100 条里 34 条、三步 1000 条里 384 条、四步 10000 条里 4032 条走的是兜底,链完全合法也照样落进去——gen 只有 3 个取值,规则只看 state 那 7 个字段。

Q:这些条数怎么复现?

把模板的 <script> 块里 relDefs、calculate、lookupTerm、singleTerm、isOlder、reverseRel 原样切出来,跑在只实现 getElementById、textContent、innerHTML 这几个能力的 DOM 桩上,再按 10 档逐长度枚举。插桩版与真实管线在两步 100 条、三步 1000 条上不一致 0 条,这 36 条站点表才被当成可信的输入。

📎 相关文章

口算题卡每张 A4 能排多少题:字号、页边距与信息栏怎么改分页 2026-11-03 同一串 23 字节的内容切换 4 个纠错等级:这一页自己的二维码编码器走到了哪一步 2026-10-30 帧间隔填 137 ms,成品每帧其实停 140 ms:tick 量化、4096 页填充与跟着第一张走的画布 2026-10-30 拼豆图纸的色号是怎么挑出来的:开一次抖动,1024 格里有 150 格被改写 2026-10-30 横图裁掉 25%、竖图裁掉 43.75%:7 个拼图模板逐格算一笔几何账 2026-10-28