Which three of the four switches are on by default
I had never verified the default state of the four switches on this page. One union run came back longer than expected, and going back to the checkboxes I found dedupe-results unchecked. So I took the template down and read it: the file is 8641 bytes across 215 line feeds, the script body runs 4630 code units and splits on LF into 146 segments, and I ran all of it unchanged against a DOM stub. The markup reads: opt-trim, opt-skip-empty and opt-dedup each carry checked, opt-case alone does not, so the default is trim-outer-spaces, ignore-blank-lines, dedupe-results and ignore-case. The initial value of currentOp is literally intersection. Every list sample below is a fictional DEMO- prefixed code.
That set of defaults on the text set operations page governs three things. The case switch decides whether Apple and apple count as one element. trim together with ignore-blank-lines decides whether a line that looks non-empty counts as an element at all. Dedupe-results does not stop at the result layer, it also feeds step 5 inside parseSet. The page holds 2 textareas, 3 tabs, 4 checkboxes and 0 dropdowns, and any one of them recomputes everything.
The five steps of parseSet, and the representative being the first spelling seen
The order is fixed: split on line breaks; if trim is on, rewrite that line; if the rewritten line is an empty string and ignore-blank-lines is on, skip it; take the key as the line itself or its toLowerCase form; consult seen only when dedupe is on; and what gets pushed last is the line after rewriting but before collapsing. That last half-sentence is the representative rule: the 3 lines "Apple / apple / APPLE" under the defaults give cnt-a=1 and the array is exactly ["Apple"]. Checking case sensitivity is what turns that 1 into 3.
Set A to "Apple / apple / DEMO-7" and B to "apple / Apple / DEMO-8": the intersection is ["Apple"], the A-side spelling seen first. Swap the two sides and the result becomes ["apple"]. The union pushes the whole first-seen order of A before appending anything from B that has not appeared yet. Difference having a direction was already written up in the older article comparing two lists, and this one does not retell it.
The collapse is a single call to toLowerCase()
In the template toLowerCase is hit 7 times, toLocaleLowerCase and toUpperCase 0 times each, so that one function is the whole folding layer, and its consequences fall into three groups. One group merges normally: ß and ẞ both collapse to U+00DF and share a key, so the 4 lines "ß / ẞ / ss / ß" give cnt-a=2, since ss collapses into a string 2 code units long and stays separate. One group refuses: İ collapses to an i plus the combining point U+0307, 2 code units in all, and its UTF-8 form grows from 2 bytes to 3 bytes, so the keys of İ, of I and of i are not equal; the 4 lines "İ / I / i / İIII" under defaults give cnt-a=3 and the array is ["İ", "I", "İIII"], with I and i merged into one. The third group: the kelvin sign U+212A collapses to k, so the 3 lines "U+212A / K / k" give cnt-a=1 and the representative is that first, 3-byte original spelling; ẛ comes back out unchanged. Case-insensitive by default is not a promise that every variant merges.
Unchecking dedupe disables the entire short circuit
The line if (dedup && seen.has(key)) continue in parseSet short-circuits as a whole once the box is off, and the key-level folding and the result-level dedupe lose effect together. Pin A to the 4 lines "apple / Apple / APPLE / DEMO-X", whose first three share one key after collapsing, and B to the 2 lines "apple / BANANA", and toggle only that switch:
| setting | cnt-a | intersection | difference | union |
|---|---|---|---|---|
| dedupe on, the default | 2 | 1 | 1 | 3 |
| dedupe off | 4 | 3 | 1 | 6 |
The difference stays 1 in both rows, because the only element of A outside B is DEMO-X. The intersection climbing from 1 to 3 is all three spellings of apple surviving. The union climbs from 3 to 6, and the innerHTML of that block measures 542 code units. At scale: A and B of 1000 lines each with 500 lines overlapping, dedupe on gives a union of 1500, exactly 1000+1000−500; turning it off gives 2000, a conservation gap of 500. So the listing page intro line 合并两个列表并去重(A∪B) and the on-page note 合并去重 only hold while the box stays checked, and any union count has to be cited together with the setting that produced it.
Paste the whole result back into A and run it again: with dedupe on, all three operations give a second round identical to the first; with dedupe off the union never converges, and 4 rounds in a row walk cnt-r through 6, 8, 10, 12, each adding exactly 2 lines, which is cnt-b.
Figure 1: conservation and idempotence hang from one and the same checkbox
cnt-a is not the number of lines in the input box
All three stat cells show array lengths taken after parseSet, never the line count sitting in the textareas. Segments below means the pieces the text falls into once it is split on line breaks. B stays empty in this batch and everything else keeps its default:
| input on the A side | segments | non-empty lines | cnt-a by default | ignore-blank off |
|---|---|---|---|---|
| two numbered lines, no trailing break | 2 | 2 | 2 | 2 |
| the same two lines with one break added at the end | 3 | 2 | 2 | 3 |
| two numbered lines plus a blank line between them and a trailing break | 4 | 2 | 2 | 3 |
| a middle line holding only 2 ideographic spaces U+3000 | 3 | 3 | 2 | 3 |
| 4 whitespace-only lines: space, tab, ideographic space, NBSP | 4 | 4 | 0 | 1 |
The empty segment a trailing break leaves behind only becomes an element once ignore-blank-lines is unchecked. Those 4 whitespace-only lines give cnt-a=0 under the defaults, so A turns into an empty set while the screen still shows 4 non-empty lines and 0 elements side by side. NBSP at U+00A0 and BOM at U+FEFF are both trimmed down to nothing.
The empty set ∅ branch, and the previous round it never clears
When the result array has length zero the script does three things in one pass: writes none into the display of the result card, shows the empty-state block, and overwrites its text with 运算结果为空集合 ∅. That sentence is 10 code units and 28 bytes, and that ∅ is U+2205, taking 1 code unit and 3 bytes. The bare calc call at the end of the template walks both empty boxes into that branch before first paint, so the 15-code-unit prompt line written in the markup, 请在上方输入集合元素后自动计算, is overwritten before anyone sees it, and nothing afterwards writes it back.
The ∅ branch also leaves the innerHTML of the result area alone. Produce 1 result line first, whose innerHTML is 94 code units long, then change the input so the intersection becomes ∅: cnt-r returns to 0 and the result card is hidden, yet clicking copy still carries off that single DEMO-0001 line from the previous round, because hidden is not cleared. escHtml runs its four replacements in the order &, <, >, ", with & first so that the round trip survives, which is why a <b> line displays and copies as the literal <b>. The 3-line sample "<b> / a&b / "q"" has an innerHTML 284 code units long and a copied string 11 code units wide containing 2 line breaks. A 500-line result measures 47892 code units of innerHTML, the numbering alone accounts for 1392 of them, and the last line reads #500. scrollHeight went unmeasured: this run never left plain Node, so there was no layout engine to ask. These readings belong to the code on this page, and not to set-operation tools in general.
FAQ
Q: the union has more lines than either list has elements, is the page broken?
No. Check the opt-dedup box first, then read it against the two-row table above.
Q: both Apple and apple are in the input, which spelling survives?
The original spelling that appeared first on the A side, and after swapping the two sides you get ["apple"] instead. The case-sensitivity box has to stay unchecked.
Q: what if I only want dedupe and none of the three operations?
The other page text sort and dedupe sits closer to that need; its own folding rules were not measured this time, so nothing is concluded on its behalf here.