文本行去重:别让trim悄悄改变数据
从一个小工具确定规则
今天做文本行去重助手时,我先写了一个独立的参考函数,并在本机 Node.js 中运行了六组用例。这个工具的范围很小:按行去重、保留首次出现的顺序、统计结果。本文给出实际测试过的核心代码,重点讨论“什么算同一行”。
为什么不直接trim
把每行先trim再放进Set,确实能合并很多看起来相似的内容,但也会把 a、前面有空格的 a、后面有空格的 a 合成同一条。处理标识符清单、文本样例或格式敏感的片段时,这可能改变原始数据。我的默认规则是精确字符串比较,只统一换行,不改普通行中的字符。空白字符组成的行也不自动等同于空字符串。
已运行的参考实现
function dedupeLines(text, ignoreEmpty = true) {
const lines = text === '' ? [] : text.replace(/\r\n?/g, '\n').split('\n');
const seen = new Set();
let empty = 0, removed = 0;
for (const line of lines) {
if (ignoreEmpty && line === '') { empty++; continue; }
if (seen.has(line)) { removed++; continue; }
seen.add(line);
}
return { input: lines.length, empty, removed, lines: [...seen] };
}
输入为空字符串时计为零行;其他输入先将CRLF和单独的CR统一成LF,再切分。因此,末尾换行会产生一个空行。默认过滤空字符串行,也允许调用者保留。两种模式都应明确,而不能靠隐含trim决定。
Set负责记录已出现的字符串,并按首次插入顺序迭代。这里既没有排序,也没有大小写折叠,因而 b、a、b 的结果仍是 b、a。
六组测试的实际结果
本机执行结果为6/6通过。测试对象就是上面的函数,断言覆盖以下情况:
| 用例 | 观察结果 |
|---|---|
| b、a、b | 保留b、a的首次顺序 |
| 混合CRLF、CR、LF | 统一切行后正确去重 |
| a、空行、a、末尾换行 | 默认输入4行、过滤2行、删除重复1行、保留1行 |
| a与首尾带空格的变体 | 三条分别保留 |
| 中文及两种é写法 | 重复中文合并;不同Unicode序列保留 |
| 相同HTML片段出现两次 | 返回一条原始字符串 |
关闭空行过滤后,上述四行输入会保留 a 和一个空字符串,重复删除数为2。完全空输入仍为零行。
不把过滤数当成重复数
默认模式中,输入4行减去保留1行得到3,这个3包含两条空行,不能全部叫“重复删除”。因此函数分别返回empty和removed,便于界面分别展示。可核对的关系是:输入行数等于过滤空行数、重复删除数与保留行数之和。
字符相似和字符串相等是两件事
外观相同的é可能来自单个字符,也可能由e和组合重音构成。当前函数不自动normalize,因此它们分别保留。若业务希望合并,应单独设计归一化选项,并说明它会改变相等规则,而不是悄悄加到去重步骤中。
HTML用例只证明函数返回文本。浏览器展示时还要把结果写入textarea.value或textContent,不能为了展示方便直接赋给innerHTML。纯文本转换和DOM渲染属于不同环节,应分别检查。
使用边界与参考
这是适合小型清单的内存实现,没有做大文件流式处理,也没有进行大规模性能测试。输入、切分数组和集合都会占用内存;用于大文件时,应另行评估读取方式和大小限制。
语言行为参考:MDN的Set描述与String.prototype.normalize()文档。以下为参考地址:
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Set
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/String/normalize
- 点赞
- 收藏
- 关注作者
评论(0)