雜記 › 生活隨筆 › 奶奶要連點三下才出來:親戚關係計算器 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