借助豆包做的这个相似度匹配公式vlookup公式的模糊匹配使用起来一言难尽经测试1500对1500的匹配大概3分钟完成。使用方法打开空白表将代码复制到宏编辑器保存为 .xlam文件再加载到wps里面后续使用就跟普通公式一样使用了。/** * 【自定义函数】多粒度精准相似度匹配跨Sheet 阈值过滤 显示最高匹配度 * 使用方法 * mlooklup(查找值, 查找列) → 返回最优匹配 * mlooklup(查找值, 查找列, 0.8) → 大于等于80%匹配度才返回 * mlooklup(查找值, 查找列, 80%) → 同上 * mlooklup(查找值, 查找列, 1.2) → 仅完全一致返回 * mlooklup(查找值, 查找列, 0) → 不限制返回最优 * param {Range} target - 查找值所在单元格 * param {Range} matchRange - 要匹配的列/区域鼠标直接点选支持跨Sheet * param {number|string} [thresholdnull] - 匹配阈值支持小数/百分比 * returns {string} 最匹配结果 或 无匹配提示含最高匹配度 */ function mlooklup(target, matchRange, threshold null) { // 1. 获取匹配区域的工作表与列信息跨Sheet核心 // 获取用户选中区域所在的工作表不是当前活动表保证跨表正常 const lookupSheet matchRange.Worksheet; // 获取匹配区域所在的列号自动识别无需手动输入数字 const matchColNum matchRange.Column; // 获取该列最后一行非空行号全空时默认1避免循环报错 const maxRow lookupSheet.Cells(lookupSheet.Rows.Count, matchColNum).End(-4162).Row || 1; // 2. 初始化最优结果变量 let bestItem null; // 存储最匹配的文本内容 let bestScore 0; // 存储最高综合相似度0~1 // 3. 处理查找值格式统一 // 安全获取查找值容错空值/无效对象 let targetValue (target?.Value2 ?? ).toString().trim(); // 查找值为空直接返回提示 if (!targetValue) return 目标值不能为空; // 替换罗马数字II→2I→1 targetValue replaceIIandI(targetValue); // 文本清洗全角转半角 小写 去空格 const tClean cleanText(targetValue); // 4. 遍历匹配列计算相似度 // 从第2行开始遍历跳过表头 for (let row 2; row maxRow; row) { try { // 获取当前行单元格内容 let cellValue (lookupSheet.Cells(row, matchColNum).Value2 ?? ).toString().trim(); // 空值跳过 if (!cellValue) continue; // 统一格式替换罗马数字 文本清洗 cellValue replaceIIandI(cellValue); const cClean cleanText(cellValue); // 核心计算4维度综合相似度 const sim getMultiSimilarity(tClean, cClean); // 更新最高分与最优匹配项 if (sim bestScore) { bestScore sim; bestItem cellValue; } } catch (e) { // 单条数据异常跳过并打印日志 console.log(【行${row}】异常${e.message}); } } // 5. 阈值处理 最终返回 // 初始化阈值 let finalThreshold 0; // 如果传入了阈值处理格式支持百分比 80% → 0.8 if (threshold ! null threshold ! undefined threshold ! ) { let val threshold; // 字符串百分比处理 if (typeof val string val.includes(%)) { val parseFloat(val.replace(%, )) / 100; } finalThreshold Number(val) || 0; } // 把最高相似度格式化为百分比保留2位小数 const bestPercent (bestScore * 100).toFixed(2); // 根据阈值规则返回结果 if (finalThreshold 1) { // 阈值 1 → 仅完全匹配100%才返回 return bestScore 1 ? bestItem : 无匹配最高匹配度 ${bestPercent}%; } else if (finalThreshold 0) { // 阈值 ≤0 → 直接返回最优匹配 return bestItem || 无匹配最高匹配度 ${bestPercent}%; } else { // 0~1之间 → 达到阈值返回否则提示无匹配 return bestScore finalThreshold ? bestItem : 无匹配最高匹配度 ${bestPercent}%; } } /** * 文本清洗函数统一格式消除干扰 * param {string} str - 原始字符串 * returns {string} 清洗后字符串半角、小写、去空格 */ function cleanText(str) { // 全角字符转半角 const toHalf s s.replace(/[\uff00-\uffff]/g, ch String.fromCharCode(ch.charCodeAt(0) - 65248)); // 全角转半角 → 小写 → 去首尾空格 return toHalf(str).toLowerCase().trim(); } /** * 替换罗马数字II→2I→1 * param {string} str * returns {string} */ function replaceIIandI(str) { return str.replace(/II/gi, 2).replace(/I/gi, 1); } /** * 4维度综合相似度计算高精度核心 * param {string} t - 目标清洗后文本 * param {string} c - 对比清洗后文本 * returns {number} 综合得分 0~1 */ function getMultiSimilarity(t, c) { const charSim charSimilarity(t, c); // 单字符匹配 const biSim ngramSimilarity(t, c, 2); // 双字符组合 const triSim ngramSimilarity(t, c, 3); // 三字符组合 const editSim editDistanceSimilarity(t, c); // 编辑距离 // 四个维度等权平均 return (charSim biSim triSim editSim) / 4; } /** * 单字符重合相似度Jaccard */ function charSimilarity(a, b) { const setA new Set(a.split()); const setB new Set(b.split()); const intersect [...setA].filter(x setB.has(x)).length; const union new Set([...setA, ...setB]).size; return union 0 ? 0 : intersect / union; } /** * N-gram连续子串相似度2-gram、3-gram */ function ngramSimilarity(a, b, n) { // 生成连续N字符组合 function getNgrams(s, n) { const ngrams []; for (let i 0; i s.length - n; i) { ngrams.push(s.slice(i, i n)); } return ngrams; } const g1 getNgrams(a, n); const g2 getNgrams(b, n); const set1 new Set(g1); const set2 new Set(g2); const intersect [...set1].filter(x set2.has(x)).length; const union new Set([...set1, ...set2]).size; return union 0 ? 0 : intersect / union; } /** * 编辑距离相似度Levenshtein * 衡量两个字符串的差异程度 */ function editDistanceSimilarity(a, b) { const lenA a.length; const lenB b.length; const maxLen Math.max(lenA, lenB); if (maxLen 0) return 1; // 构建动态规划表 const dp Array.from({ length: lenA 1 }, () Array(lenB 1).fill(0)); for (let i 0; i lenA; i) dp[i][0] i; for (let j 0; j lenB; j) dp[0][j] j; // 计算最小修改次数 for (let i 1; i lenA; i) { for (let j 1; j lenB; j) { const cost a[i - 1] b[j - 1] ? 0 : 1; dp[i][j] Math.min( dp[i - 1][j] 1, dp[i][j - 1] 1, dp[i - 1][j - 1] cost ); } } // 转为相似度 0~1 return 1 - dp[lenA][lenB] / maxLen; }