两份 TXT 先对行数
起因很朴素:我手上有两份 TXT,一份 555 段、一份 819 段。想看的不是内容,而是逐行对比页在语料级输入下给出的账——公共行怎么数、增删按什么先后、统计那行写了几个数。两份文件只当字节与行的语料,正文不引句子。
语料的字节与段数
两份都是站内已发布资源:《声律启蒙》26353 字节、9185 码元、9183 码点,555 段、非空 403、空行 152;《笠翁对韵》28903 字节、10211 码元、10209 码点,819 段、非空 671、空行 148;都无 CRLF、也无 BOM。码元比码点多 2,来自各有 2 个增平面码点。被测页就是文本对比,模板 118 行、4487 字节、脚本 3 个函数。
整档喂进去的三笔账
两份全文直接调 lineDiff:added 665、removed 401、equal 154、结果 1220 条,dp 表 556 × 820 = 455920 个单元格,规模代价旧文算过、本篇只登记这档数。恒等式:555 减 154 得 401、819 减 154 得 665、555 加 819 减 154 得 1220;方向对调两数互换,equal 与单元格数不变。
| 输入口径 | A 段 | B 段 | removed | added | equal | 结果条数 |
|---|---|---|---|---|---|---|
| 全文切段 | 555 | 819 | 401 | 665 | 154 | 1220 |
| 先去掉空行 | 403 | 671 | 397 | 665 | 6 | 1068 |
| 两档各自自比 | 555 与 819 | 同左 | 0 | 0 | 555 与 819 | 同左 |
两档 added 同为 665,差额只在 removed:A 侧 152 个空行被吸收 148 个,剩 4 个进删除账,所以 401;去空行那档 dp 表 404 × 672 = 271488。耗时只说脚本层:本地脚本连调 5 轮得 113.9、139.5、120.5、122.7、117.6 ms,中位 120.5 ms,它是单次函数调用的耗时、不是页面响应。
守卫只在两侧都空时命中
doDiff() 里两侧 join 后 trim、都成空串才 toast「请输入要对比的文本」并 return:判据是合起来全空白,不是逐侧。四条实测:
- 两侧都空、或 A 侧只有 3 个半角空格:命中后什么都不改,统计与显示停在上一轮。
- 一侧空、另一侧 403 条非空语料行:照跑,统计给「新增 403 行,删除 1 行」。
- 把 403 行挪到 A 侧:镜像的「新增 1 行,删除 403 行」。
- 一侧空、另一侧换成全 555 段:给「新增 554 行,删除 0 行」。
关键在最后一条:空串切出 1 段,对面没空行时它落成删除 1 行;对面有 152 条空行时被 LCS 吸收、删除归 0,新增变 554。
图 1:守卫两档与空行改账。
同分取舍定出删前增后
回溯条件写成 dp[i][j-1] >= dp[i][j]:同分先进 added 分支、j 减 1;回溯从表尾往回走、末尾又有 reverse(),于是输出流固定删前增后:
- a、b 对 b、a:得 removed a、equal b、added a;走完
doDiff后 A 侧是「- a、b、占位」、B 侧「占位、b、+ a」。 - old 对 new:removed 与 added 相邻一对;a、b 对 a、c:equal、removed、added。
- 1、2、3、4 对 3、4、1、2:removed 1、2,equal 3、4,added 1、2,公共段锚在 3 与 4。
另开上下文只把 >= 换成 >:同批样本 4 组顺序翻转、块互换换锚到 1 与 2,1 组逐字相同——只作对照记录。
移动一行:取 10 行、把第 2 行插到第 9 位,自比 0 增 0 删,挪后 added 1、removed 1、equal 9、结果 11 条,代码里没有移动检测。
统计那一行只有两个数
页面那行串逐字是 新增 ${added} 行,删除 ${removed} 行,抽数字恰好 2 个,整档成品「新增 665 行,删除 401 行」;公共行数与 dp 终值的 LCS 长度都不进串。上方文案「统计概览:显示新增行数、删除行数、修改行数」这半句与代码冲突:按修改计数的分支要行类型不属于 equal、removed、added 才进,而三处 push 的字面量恰是这三种,1220 条那一档的类型集合仍是三个;改行内几个字符也只整行进两侧,词级那层本页没有(旧文登记过)。
结果行进 DOM 的方式与残留
渲染逐条 createElement 配 textContent:每侧 1220 个 div、两侧 2440 个一次全进,容器 max-height 400px 加 overflow auto,对照实验 scrollHeight 29328px 对 400px。本页无转义函数也无复制按钮,模板里 <button 命中 2 个;含尖括号、& 与引号的行原样读回,对侧占位是单个 U+00A0;「清空」只设回隐藏,两侧各残留 2 行。
CRLF 分两层,旧文那条只登记一句:字符串层带 \r 的行对不带的是 1 删 1 增,走 textarea 则回车归一成换行、粘贴得 0 增 0 删。尾随 LF 另有一笔:切分各多 1 个空段,对去尾 LF 版实测 equal 554、删除 1、新增 0。
图 2:整档对跑的三笔账。
公共行的三本账与它们落在文件尾
公共行的口径有三本:多重集下含空行 154 条,空行按少的一档贡献 148、非空只剩 6;按种类去重 6 种、去空串 5 种,28 码元的分隔线两侧各 2 次;同位置只有 25 行。这 6 条不在文件开头而在尾部说明块,前者第 544 至 553 行、后者第 808 至 817 行,字符串不搬进正文。整档 LCS 恰是 154、去空行 LCS 恰是 6,与交集相等只因先后一致,是巧合不是恒等式。
常见问题
Q:一侧什么都不填,会被当成错误拦下来吗?
不会,守卫要两侧合起来全空白才命中;一侧空、另一侧 403 条非空行照跑,给「新增 403 行,删除 1 行」。
Q:只挪动了一行,为什么删与增各涨 1?
结果里没有移动这个类型,lineDiff 只产出 equal、added、removed 三种,挪一行就记 added 1、removed 1。
Q:公共行 154 条为什么统计里看不到?
统计串只有「新增 665 行,删除 401 行」两个数,公共行数与 LCS 长度都不进串。
资源说明
两处资源链接只交代语料出处:两部书在这里只按字节与行计数使用,正文不引句子、不作内容层面的解释,尾部那 6 条公共字符串只作行计数登记。页面没有文件输入通道,整档靠粘贴。