四個開關裡,預設開著的是哪三個
四個開關的預設態我沒核實過——起因是一輪並集結果比預期多出一截,回頭發現「結果去重」被取消過勾選。於是把模板拆下來:本體 8641 位元組、215 個換行,腳本正文 4630 個碼元按 LF 切出 146 段,一字不改跑在 DOM 樁上。讀出來的 markup:opt-trim、opt-skip-empty、opt-dedup 三條都帶 checked,唯獨 opt-case 不帶——預設就是「去首尾空格 + 忽略空行 + 結果去重 + 不區分大小寫」,currentOp 初值逐字是 intersection。名單樣本一律是 DEMO- 前綴的虛構編號。
這套預設值在 文本集合運算 頁管著三件事:大小寫檔位決定 Apple 和 apple 算不算同一個元素;trim 與忽略空行決定「看著非空」的行算不算元素;結果去重不只作用在結果層,還喂著 parseSet 的第 5 步。整頁 textarea 2 個、tab 3 個、checkbox 4 個、下拉 0 個,動哪個都全量重算。
parseSet 的五步順序,與「代表元取首現」
順序是:按換行切開 → trim 開著就改寫該行 → 改寫後是空字串且忽略空行開著就跳過 → key 取該行或它的 toLowerCase → 去重開著才查 seen → 最後 push 的是改寫後但未摺疊的那一行。最後半句就是代表元規則:「Apple / apple / APPLE」3 行預設檔 cnt-a=1,數組就是 ["Apple"];勾上區分大小寫才變 3。
A=「Apple / apple / DEMO-7」、B=「apple / Apple / DEMO-8」,交集是 ["Apple"],取 A 側首現寫法;兩側對調,結果變成 ["apple"]。並集先把整個 A 的首現序推完,再接 B 裡沒出現過的。「差集有方向」那套講法在舊文 對比兩份名單 寫過,本篇不重講。
摺疊只用 toLowerCase() 一個函數
模板裡 toLowerCase 命中 7 次、toLocaleLowerCase 與 toUpperCase 各 0 次,後果分三類。一類正常並:ß 與 ẞ 都摺成 U+00DF 同 key,「ß / ẞ / ss / ß」4 行得 cnt-a=2,ss 摺後是 2 個碼元、不並。二類不並:İ 摺成 i 加 U+0307 組合點、共 2 個碼元,UTF-8 從 2 位元組變 3 位元組,所以 İ 與 I、i 的 key 不相等——「İ / I / i / İIII」4 行預設檔 cnt-a=3,數組是 ["İ", "I", "İIII"],I 與 i 並成一個。三類:開爾文符號 U+212A 摺成 k,「U+212A / K / k」3 行 cnt-a=1,代表元取首現那個 3 位元組的原始寫法;ẛ 摺完原地不動。「預設不區分大小寫」不等於所有變體都並上。
關掉結果去重,那條短路整條失效
parseSet 裡那行 if (dedup && seen.has(key)) continue 在去重關時整條短路,摺疊層與結果層去重一起失效。固定 A=「apple / Apple / APPLE / DEMO-X」4 行(前三行摺疊後同 key)、B=「apple / BANANA」2 行,只切這一個開關:
| 檔位 | cnt-a | 交集 | 差集 | 並集 |
|---|---|---|---|---|
| 去重開(預設) | 2 | 1 | 1 | 3 |
| 去重關 | 4 | 3 | 1 | 6 |
差集兩檔都是 1,因為 A 裡不在 B 的只有 DEMO-X;交集 1 漲到 3 是 apple 三種寫法全留;並集 3 漲到 6,那塊 innerHTML 實測 542 碼元。規模版:A、B 各 1000 行、重疊 500,去重開並集 1500,正好 1000+1000−500;關掉變 2000,守恆差 500。所以列表頁 intro「合併兩個列表並去重(A∪B)」與頁面說明的「合併去重」只在勾選著去重時成立,引用並集計數必須註明檔位。
把結果整段貼回 A 再算:去重開時三檔第 2 輪與第 1 輪逐字相同;去重關時並集不收斂,連貼 4 輪 cnt-r 走 6、8、10、12,每輪淨增的 2 行恰好等於 cnt-b。
圖 1:守恆與冪等都掛在同一個勾選框上
cnt-a 報的不是輸入框的行數
三個統計格顯示的都是 parseSet 之後的數組長度,不是 textarea 的行數。「段數」指按換行切開的份數(B 留空,其餘預設):
| 輸入(A 側) | 段數 | 非空行 | 預設 cnt-a | 忽略空行關 |
|---|---|---|---|---|
| 兩行編號、無尾隨換行 | 2 | 2 | 2 | 2 |
| 同兩行、末尾多一個換行 | 3 | 2 | 2 | 3 |
| 兩行編號加中間空行與尾隨換行 | 4 | 2 | 2 | 3 |
| 中間一行只有 2 個全形空格 U+3000 | 3 | 3 | 2 | 3 |
| 4 行純空白(空格、製表符、全形空格、NBSP) | 4 | 4 | 0 | 1 |
尾隨換行多出的空段,只在關掉忽略空行後才被計成元素;4 行全空白在預設檔 cnt-a=0,整個 A 變空集,「非空行=4」與「cnt-a=0」同屏。NBSP(U+00A0)與 BOM(U+FEFF)都被 trim 清零。
空集合 ∅ 那一支,和沒被清掉的上一輪
結果數組長度為零時腳本連做三件事:結果卡 display 成 none、空態塊顯示、文案覆寫成「運算結果為空集合 ∅」——這句 10 個碼元、28 位元組,∅ 是 U+2205、佔 1 碼元 3 位元組。模板末尾裸調的那次 calc 在首屏就把兩個空框推進這個分支,markup 裡那句 15 碼元的「請在上方輸入集合元素後自動計算」在被看見之前就遭覆寫,此後沒有任何程式碼寫回。
∅ 分支還是不清空結果區的 innerHTML:先算出 1 行結果(innerHTML 長 94 碼元),再改輸入讓交集變 ∅,cnt-r 歸 0、結果卡已隱藏,此時點「複製結果」仍帶走上一輪那 1 行 DEMO-0001——隱藏不等於清空。escHtml 按 &、<、>、" 四組順序替換(& 排第一,保住往返),<b> 行顯示與複製都是字面 <b>;「<b> / a&b / "q"」3 行樣本 innerHTML 284 碼元,複製串 11 碼元、含 2 個換行。500 行結果的 innerHTML 實測 47892 碼元、序號佔 1392 碼元、末行 #500;scrollHeight 未實測——本次全程純 Node,沒有排版引擎。這些檔位只屬於本頁程式碼,別外推成所有集合運算工具都長這樣。
FAQ
Q:並集行數比名單的元素數還多,是頁面出錯了嗎?
不是。先核對 opt-dedup 勾選框狀態,再對照上面那張兩檔表。
Q:Apple 和 apple 都在,最後留下哪一個?
留 A 側首現的原始寫法,對調後得 ["apple"]。前提是區分大小寫沒勾。
Q:只想去重、不做交並差呢?
另一頁 文本排序去重 更貼近這個需求;它自己的摺疊口徑這次沒測,不替它下結論。