我正在尝试为我的特定应用找到最佳算法。我在SO,Google上搜索过,阅读了有关Levenshtein距离的各种文章,但是说实话,这有点超出我的专业领域。大多数人似乎发现两个输入字符串有多相似,例如字符串之间的汉明距离。
我要寻找的是不同的,更多是模糊记录搜索(而且我敢肯定有一个名字,我对Google不了解)。我确信有人之前已经解决了这个问题,我正在寻找建议,为我的进一步研究指明正确的方向。
就我而言,我需要模糊搜索音乐艺术家及其专辑条目的数据库。可以想象,数据库将具有数百万个条目,因此,良好扩展的算法至关重要。对于我的问题,艺术家和专辑不在不同的列中,这并不重要,数据库可以将所有单词存储在一个列中,只要这有助于搜索。
要搜索的数据库:
|-------------------|---------------------|
| Artist | Album |
|-------------------|---------------------|
| Alanis Morissette | Jagged Little Pill |
| Moby | Everything is Wrong |
| Air | Moon Safari |
| Pearl Jam | Ten |
| Nirvana | Nevermind |
| Radiohead | OK Computer |
| Beck | Odelay |
|-------------------|---------------------|
查询文本将仅包含整个Artist_Album串联中的一个单词,直至整个单词。查询文本来自OCR,并且可能具有单个字符换位符,但是最有可能发生的事情是单词不能保证具有正确的顺序。此外,搜索中可能还会有一些不属于专辑的单词(例如封面文字)。例如,“确定计算机”可能在专辑的顶部,而在其下方的“ Radiohead”,或者某些专辑的文字排列在列中,混合了单词顺序。
可能的搜索字符串:
C0mputer Rad1ohead
Pearl Ten Jan
Alanis Jagged Morisse11e Litt1e Pi11
Air Moon Virgin Records
Moby Everything
请注意,使用OCR时,某些字母看起来像数字,或者完全是错误的字母(用Jan代替Jam)。对于Radiohead的 OK计算机和Moby的万事大吉,查询文本甚至都没有全部单词。对于Air的 Moon Safari ,将搜索Virgin Records多余的单词,但缺少Safari。
是否有一种通用算法可以从数据库中返回最可能的单个结果,并且如果没有一个算法满足某些“似然性”分数阈值,则什么也不返回?我实际上是用Python开发的,但这只是一个好处,我正在寻找开始研究的更多地方。
答案 0 :(得分:0)
让我们将问题分为两部分。
第1部分:指标
您已经提到Levenshtein距离,这是一个很好的起点。 跳出框框思考。
LD做出某些假设(每个字符替换的可能性均相同,删除与插入的可能性相同,等等)。考虑到OCR可能带来的故障,显然可以提高此度量标准的性能。
例如将“ 1”转换为“ i”不应像将“ 0”转换为“ _”那样严厉地处罚。
我将分两个阶段实施该指标。对于任何给定的两个字符串:
这是一个示例实现(弄乱常量):
static double m(String a, String b){
String[] aParts = a.split(" ");
String[] bParts = b.split(" ");
boolean[] bUsed = new boolean[bParts.length];
int matchedTokens = 0;
int tokensInANotInB = 0;
int tokensInBNotInA = 0;
for(int i=0;i<aParts.length;i++){
String a0 = aParts[i];
boolean wasMatched = true;
for(int j=0;j<bParts.length;j++){
String b0 = bParts[j];
double d = levenshtein(a0, b0);
/* If we match the token a0 with a token from b0
* update the number of matchedTokens
* escape the loop
*/
if(d < 2){
bUsed[j]=true;
wasMatched = true;
matchedTokens++;
break;
}
}
if(!wasMatched){
tokensInANotInB++;
}
}
for(boolean partUsed : bUsed){
if(!partUsed){
tokensInBNotInA++;
}
}
return (matchedTokens
+ tokensInANotInB * -0.3 // the query is allowed to contain extra words at minimal cost
+ tokensInBNotInA * -0.5 // the album title should not contain too many extra words
) / java.lang.Math.max(aParts.length, bParts.length);
}
此函数使用了经过修改的levenshtein函数:
static double levenshtein(String x, String y) {
double[][] dp = new double[x.length() + 1][y.length() + 1];
for (int i = 0; i <= x.length(); i++) {
for (int j = 0; j <= y.length(); j++) {
if (i == 0) {
dp[i][j] = j;
}
else if (j == 0) {
dp[i][j] = i;
}
else {
dp[i][j] = min(dp[i - 1][j - 1]
+ costOfSubstitution(x.charAt(i - 1), y.charAt(j - 1)),
dp[i - 1][j] + 1,
dp[i][j - 1] + 1);
}
}
}
return dp[x.length()][y.length()];
}
哪个使用了“替代成本”功能(按说明工作)
static double costOfSubstitution(char a, char b){
if(a == b)
return 0.0;
else{
// 1 and i
if(a == '1' && b == 'i')
return 0.5;
if(a == 'i' && b == '1')
return 0.5;
// 0 and O
if(a == '0' && b == 'o')
return 0.5;
if(a == 'o' && b == '0')
return 0.5;
if(a == '0' && b == 'O')
return 0.5;
if(a == 'O' && b == '0')
return 0.5;
// default
return 1.0;
}
}
我仅列举了几个示例(将“ 1”变成“ i”或将“ 0”变成“ o”)。 但是我敢肯定,你会明白的。
第2部分:数据结构
查看BK-trees。它们是用于保存度量标准信息的特定数据结构。您的指标必须是真实的指标(从字面上的数学意义上来说)。但这很容易安排。