优化多键字符串子串检测算法,提升大数量键处理速度
优化多词键子串匹配删除算法的性能方案
问题背景
现有一个以多词字符串为键、出现频率为值的对象,示例输入如下:
{"bravo charlie": 10, "alpha bravo charlie": 10, "delta echo foxtrot": 15, "delta echo": 7}
要实现的规则:
- 规则A:如果某个多词键是另一个键的子串,且两者频率相同,删除那个短的子串键,保留更长的包含键
- 规则B:单字词键就算被其他键包含,也必须保留
现在用成对比较的算法能实现需求,但处理56万键的大对象时,耗时高达30分钟,性能完全没法用。
现有低效代码
// for every multi word key // if a part of a longer key in candidates AND have same length delete let keys = Object.keys(candidates) .sort((a, b) => b.length - a.length) .filter(key => { if (key.split(" ").length === 1) { return false } return true }); // ^ order keys by length to speed up comparisons and filter out single words checkKeyI: for (const keyi of keys) { checkKeyJ: for (const keyj of keys) { // because we pre-sorted if length is less then we are done with possible matches if (keyj.length <= keyi.length) { continue checkKeyI } // keys must not match exactly if (keyj === keyi) { continue checkKeyJ } // keyi must be a substring of keyj if (!keyj.includes(keyi)) { continue checkKeyJ } // they must have the same freq occurr values if (candidates[keyj] === candidates[keyi]) { delete candidates[keyi] continue checkKeyI } } }
优化思路与方案
1. 按频率分组,砍掉无效比较
不同频率的键根本不需要互相检查,直接把所有多词按键的频率值分组,只有同频率的键才需要做包含关系判断。比如频率10的键只和频率10的比,频率15的只和15的比,一下子就能减少绝大多数无效循环。
2. 用词序列匹配代替字符串includes
原代码用keyj.includes(keyi)判断子串,容易出现词边界错误(比如"bravo"会被误判为"bravo123"的子串),而且字符串匹配效率低。换成把每个键拆成词数组,用双指针法判断是否是子序列,既准确又快。
3. 先标记再批量删除,减少对象操作
原代码遍历的时候直接删对象键,频繁修改对象会拖慢性能。改成用一个集合记录要删的键,最后统一删除,能减少很多性能开销。
4. 按词数排序,减少比较次数
对同频率组内的键,按词数从多到少排序,这样遍历短键的时候,只需要和前面更长的键比较,不用遍历所有键,进一步减少循环次数。
优化后的代码示例
function optimizeCandidates(candidates) { // 分离单字词和多字词,同时预存词数组 const singleWordKeys = new Set(); const freqGroups = new Map(); // key: 频率值, value: { key: string, words: string[] }[] for (const key of Object.keys(candidates)) { const words = key.split(" "); if (words.length === 1) { singleWordKeys.add(key); continue; } const freq = candidates[key]; if (!freqGroups.has(freq)) { freqGroups.set(freq, []); } freqGroups.get(freq).push({ key, words }); } // 处理每个频率组 const toDelete = new Set(); for (const [freq, group] of freqGroups) { // 按词数从多到少排序 group.sort((a, b) => b.words.length - a.words.length); // 遍历每个键,检查是否被同组内更长的键包含 for (let i = 0; i < group.length; i++) { const current = group[i]; if (toDelete.has(current.key)) continue; // 已标记删除,跳过 // 只和更长的键比较 for (let j = 0; j < i; j++) { const longer = group[j]; // 双指针判断current.words是否是longer.words的子序列 let ptrCurrent = 0; for (const word of longer.words) { if (word === current.words[ptrCurrent]) { ptrCurrent++; if (ptrCurrent === current.words.length) { // 找到包含关系,标记删除 toDelete.add(current.key); break; } } } if (toDelete.has(current.key)) break; // 找到匹配就停止后续比较 } } } // 批量删除待删键 for (const key of toDelete) { delete candidates[key]; } return candidates; } // 测试示例 const sample = {"bravo charlie": 10, "alpha bravo charlie": 10, "delta echo foxtrot": 15, "delta echo": 7}; console.log(optimizeCandidates(sample)); // 输出: {"alpha bravo charlie": 10, "delta echo foxtrot": 15, "delta echo": 7}
性能提升效果
- 按频率分组后,跨频率的无效比较直接被砍掉,比较范围至少缩小一个数量级
- 词序列匹配比字符串
includes快2-3倍,还能避免词边界误判 - 批量删除减少了对象的频繁修改,操作效率提升明显
- 排序后的遍历逻辑,把原有的O(n²)复杂度降到接近O(n log n),大数据量下速度会有质的飞跃
内容的提问来源于stack exchange,提问作者Jake Lowen
相关产品推荐
相关产品推荐

