40320 computed, 5000 rows on the same screen
Type 8 single letters into the element box and the field labelled "permutation total" reads 40,320, while the list, scrolled to the bottom, holds only 5000 rows and closes with the line "40320 permutations in total, showing only the first 5000". Copying permutation.html (measured at 233 lines / 9158 bytes) word for word into a plain Node probe showed why: the page carries two unrelated rulers, one for the arithmetic and one for how many lines reach the screen, while the machine between them, which generates the permutations, has no notion that two elements can look alike.
Two gates, each reading its own quantity
The if (n > 8) at L177 tests the number of elements left after the split: four inputs of nine or more items all produced the same toast, "element count exceeds 8, too many permutations, please reduce the elements", and that return sits before heap, so the list length comes back as 0.
The maxShow = Math.min(perms.length, 5000) at L215 tests displayed rows instead, and perms stays full in memory: n=7 really generates 5040, writes 5000 and hides 40; n=8 really generates 40320, writes the same 5000 and hides 35320. That cap of 5000 falls between 720 and 5040, so of the nine stops only two ever reach the tail line. The page under test here is the permutation calculator.
Figure 1: for the same n stops, how many permutations are generated and how many rows are shown.
heap only ever swaps indices
heap swaps two indices throughout according to whether k is odd or even and never compares element values, so duplicate elements are still counted by n!: A, A, B yields 6 rows and 3 once deduplicated; A, A, A yields 6 identical rows and 1; 甲 / 甲 / 乙 likewise yields 6 rows and 3. Both places in the interface, the total field and the "n! =" statistics line, print n! and never the deduplicated count. Across a batch of 40 inputs, 11 of them had a deduplicated count smaller than the number of rows written out. All of this is behaviour of this page alone, not evidence about permutation tools as a class.
Figure 2: the number the interface prints, next to the two ledgers actually kept.
Expansion and reduction appear only while n stays at 20 or below
The if (n <= 20) at L136 decides how many lines the process panel shows: n=5,r=3 gives 4 lines; n=20,r=7 still gives 4, the reduction line carrying 7 factors and the result 390,700,800; from n=21,r=1 only 3 lines remain, since the expansion and reduction blocks collapse into one line labelled "result", and n=22,r=22 behaves the same. The reduction line always carries as many factors as r, and when r=0 that cell reads "reduce: = 1".
The generate side has no such branch: calc sets no ceiling on n, so n=100000 with r=2 still answers 9,999,900,000, only it takes 9553.75 ms. The factorial itself lives on the factorial calculation page.
The separator regex edits n before you do
The character class /[,、\s,\n]+/ at L170 treats both comma styles, the enumeration comma, newlines and the whole whitespace class as separators. So New York, 北京 parses into 3 elements and the interface reports 3!=6, though the person typing had 2 items in mind; the other direction is just as quiet, since A;B splits into one element: the fullwidth semicolon is not in that class, and neither are the halfwidth semicolon, the vertical bar or the hyphen.
Two silent paths stay apart: input that is empty after trimming leaves through L164, while input made only of separators gives n as 0 and leaves through L173. Both hide the panel, raise no toast, and leave the total field unwritten. L173 is not dead code, since a lone comma reaches it.
fmt is a dead function with zero call sites
A word-boundary regex hits fmt only at its definition on L102, with 0 call sites, while fmtBig is hit 6 times, so every displayed value travels the BigInt route. The Number ruler lies unused, yet two boundaries hide behind it: the notation boundary sits at 22!, written as 1.1240007277776077e+21 while still exact, and the precision boundary sits at 23!, where the BigInt reading is 25852016738884976640000 and reading it back as a double gives 25852016738884978212864, two different numbers.
onclick is assembled out of strings
L218 packs the joined element string into a single-quoted argument and escapes the single quote alone. Two string-layer measurements: with a single quote inside an element the attribute string comes out as copyItem(this, 'A\', B'), and parsing that JS returns A', B, identical to the visible text of that row once the row number prefix is stripped; with a double quote the onclick value closes at the first double quote, so the attribute regex only recovers the fragment copyItem(this, 'A; with a less-than sign that character enters innerHTML unchanged, and what a browser makes of it was not measured.
Two sets of claims also stay apart. The structure after innerHTML injection, how the toast is presented and its 2500 ms, the copy feedback and when the button text returns at 1500 ms, what type=number writes into value for 5.7 and 1e3, and how long a 2019032 byte list string takes to render: no browser was opened for this batch, so all of these are marked not measured. On the string side the claim does hold: at n=8 the list contains 5000 onclick fragments, and the 40320th permutation has no copy entry, because its node was never created.
Switching back always reruns
L82 reruns whichever side you switch back to. Returning to calc leaves 132 in the result field, recomputed from n=12 and r=2, not the 60 that was there before switching away. When r exceeds n the function returns before writing textContent, so the node keeps the old string while display is already none; the same residue appears on the generate side, where after 9 items replace the input the 2019012 characters written earlier are not cleared by one character.
15 copy checks
From line 885 of config/tools.php, the entry and its two template hints were judged one by one: 7 agree, 2 conflict, 6 agree in part.
| Copy | Measured | Verdict |
|---|---|---|
| Every permutation can be copied | at n=8 the list holds only 5000 clickable entries, and the other 35320 have no entry at all | conflict |
| Take r elements out of n distinct ones | distinctness is never checked, so A, A, B is counted as 3!=6 | conflict |
| Supports at most 8 elements (8! = 40320 permutations) | 8 and 40320 both hold, while 5000 is written into no piece of copy | partly agree |
5000 is hit 3 times in the template, at L215, L220 and L221, all inside code, and 5040 is hit 0 times. The only place the cap surfaces is the tail line, which carries no thousands separator and so uses a different notation from the 40,320 on the same screen. The earlier article permutations and combinations already treats the distinction, so it is not retold here.
Q: so is 40320 simply wrong?
It is not wrong. 40,320 is both factorialBig(8) and the perms.length that 8 elements really generate; the misunderstanding comes from reading the total field as a row count. The screen parks at 5000 rows while the remaining 35320 entries sit in memory with no exit.
Q: with duplicate elements, how do I keep just one row?
This page offers no deduplication switch, and both places print n!. The string-layer reconciliation is n! divided by the product of the factorials of each value's occurrence count: A,A,B,C,D,E,F,G folds down from 40320 to 20160, and that stop reveals only 2520 kinds inside the first 5000 rows.
Q: the result changed after I switched tabs, was nothing saved?
That is a rerun rather than a loss: switching back always computes again from the current input, while invalid input only hides the panel instead of overwriting the string already there, which is why the state ends up hidden yet still present.