重複検知(ファジーマッチ)
:LiTarget: 用途
会社名・メアドの表記揺れを検出。「株式会社SCALE」「(株)SCALE」を同一視。
:LiSparkle: 特徴
- Levenshtein距離
- 正規化(全/半角・カタカナ等)
- クラスタリング
- 統合候補提示
:LiCode: コード(コピペ用)
// 文字列正規化
function normalize(s: string) {
return s
.toLowerCase()
.replace(/[\((]株[\))]/g, '株式会社')
.replace(/\s+/g, '')
.normalize('NFKC');
}
// Levenshtein距離
function distance(a: string, b: string): number {
const dp = Array.from({ length: a.length + 1 }, () => new Array(b.length + 1).fill(0));
for (let i = 0; i <= a.length; i++) dp[i][0] = i;
for (let j = 0; j <= b.length; j++) dp[0][j] = j;
for (let i = 1; i <= a.length; i++) for (let j = 1; j <= b.length; j++) {
dp[i][j] = a[i - 1] === b[j - 1]
? dp[i - 1][j - 1]
: Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]) + 1;
}
return dp[a.length][b.length];
}
export function findDuplicates(items: { id: string; name: string }[], threshold = 2) {
const groups: string[][] = [];
items.forEach(it => {
const norm = normalize(it.name);
const g = groups.find(g => g.some(id => {
const other = items.find(x => x.id === id)!;
return distance(normalize(other.name), norm) <= threshold;
}));
if (g) g.push(it.id); else groups.push([it.id]);
});
return groups.filter(g => g.length > 1);
}
:LiHandPointer: 使い方
対象プロジェクトに該当ファイルをコピーして、props を流し込むだけ。
:LiAlertCircle: 注意事項
- 依存パッケージを忘れず追加