我正在开发一款游戏,我只需要检查两个单词之间是否有0或1的距离,如果是这样,则返回true。我找到了一个通用的levenshtein距离算法:
function levenshtein(s, t) {
if (s === t) { return 0; }
var n = s.length, m = t.length;
if (n === 0 || m === 0) { return n + m; }
var x = 0, y, a, b, c, d, g, h, k;
var p = new Array(n);
for (y = 0; y < n;) { p[y] = ++y; }
for (;
(x + 3) < m; x += 4) {
var e1 = t.charCodeAt(x);
var e2 = t.charCodeAt(x + 1);
var e3 = t.charCodeAt(x + 2);
var e4 = t.charCodeAt(x + 3);
c = x; b = x + 1; d = x + 2; g = x + 3; h = x + 4;
for (y = 0; y < n; y++) {
k = s.charCodeAt(y);
a = p[y];
if (a < c || b < c) { c = (a > b ? b + 1 : a + 1); }
else { if (e1 !== k) { c++; } }
if (c < b || d < b) { b = (c > d ? d + 1 : c + 1); }
else { if (e2 !== k) { b++; } }
if (b < d || g < d) { d = (b > g ? g + 1 : b + 1); }
else { if (e3 !== k) { d++; } }
if (d < g || h < g) { g = (d > h ? h + 1 : d + 1); }
else { if (e4 !== k) { g++; } }
p[y] = h = g; g = d; d = b; b = c; c = a;
}
}
for (; x < m;) {
var e = t.charCodeAt(x);
c = x;
d = ++x;
for (y = 0; y < n; y++) {
a = p[y];
if (a < c || d < c) { d = (a > d ? d + 1 : a + 1); }
else {
if (e !== s.charCodeAt(y)) { d = c + 1; }
else { d = c; }
}
p[y] = d;
c = a;
}
h = d;
}
return h;
}
哪个有效,但是这个地方将成为一个热点并且可能每秒运行数十万次,我想优化它,因为我不需要通用算法,只需检查是否存在距离为0或1。
我尝试写它并想出了这个:
function closeGuess(guess, word) {
if (Math.abs(word.length - guess.length) > 1) { return false; }
var errors = 0, guessIndex = 0, wordIndex = 0;
while (guessIndex < guess.length || wordIndex < word.length) {
if (errors > 1) { return false; }
if (guess[guessIndex] !== word[wordIndex]) {
if (guess.length < word.length) { wordIndex++; }
else { guessIndex++; }
errors++;
} else {
wordIndex++;
guessIndex++;
}
}
return true;
}
但是在分析之后我发现我的代码慢了两倍,这让我感到惊讶,因为我认为通用算法是O(n * m),我认为我的代码是O(n)。
我一直在测试这个小提琴的性能差异:https://jsfiddle.net/aubtze2L/3/
我可以使用更好的算法,还是可以更快地优化代码?
答案 0 :(得分:2)
我没有看到一种更优雅的方式,它同时比旧的for-loop更快:
function lev01(a, b) {
let la = a.length;
let lb = b.length;
let d = 0;
switch (la - lb) {
case 0: // mutation
for (let i = 0; i < la; ++i) {
if (a.charAt(i) != b.charAt(i) && ++d > 1) {
return false;
}
}
return true;
case -1: // insertion
for (let i = 0; i < la + d; ++i) {
if (a.charAt(i - d) != b.charAt(i) && ++d > 1) {
return false;
}
}
return true;
case +1: // deletion
for (let i = 0; i < lb + d; ++i) {
if (a.charAt(i) != b.charAt(i - d) && ++d > 1) {
return false;
}
}
return true;
}
return false;
}
console.log(lev01("abc", "abc"));
console.log(lev01("abc", "abd"));
console.log(lev01("abc", "ab"));
console.log(lev01("abc", "abcd"));
console.log(lev01("abc", "cba"));
效果比较(Chrome):
答案 1 :(得分:1)
考虑以下情况:
以下是一个示例实现:
var areSimilar;
areSimilar = function(guess, word) {
var charIndex, foundDiff, guessLength, lengthDiff, substring, wordLength, shortest, longest, shortestLength, offset;
guessLength = guess.length;
wordLength = word.length;
lengthDiff = guessLength - wordLength;
if (lengthDiff < -1 || lengthDiff > 1) {
return false;
}
if (lengthDiff !== 0) {
if (guessLength < wordLength) {
shortest = guess;
longest = word;
shortestLength = guessLength;
} else {
shortest = word;
longest = guess;
shortestLength = wordLength;
}
offset = 0;
for (charIndex = 0; charIndex < shortestLength; charIndex += 1) {
if (shortest[charIndex] !== longest[offset + charIndex]) {
if (offset > 0) {
return false; // second error
}
offset = 1;
if (shortest[charIndex] !== longest[offset + charIndex]) {
return false; // second error
}
}
}
return true; // only one error
}
foundDiff = false;
for (charIndex = 0; charIndex < guessLength; charIndex += 1) {
if (guess[charIndex] !== word[charIndex]) {
if (foundDiff) {
return false;
}
foundDiff = true;
}
}
return true;
};
我已经更新了你的小提琴以包含这种方法。以下是我机器上的结果:
close: 154.61
lev: 176.72500000000002
sim: 32.48000000000013
答案 2 :(得分:0)
如果你知道你正在寻找距离0和1,那么通用DP算法就没有意义了(而且你所展示的算法看起来很复杂,请看一个更好的解释here )。
要检查距离是否为0,您只需要检查2个字符串是否相同。现在,如果距离为1,则意味着应该发生插入,删除或替换。因此,从原始字符串生成所有可能的删除,并检查它是否等于第二个字符串。所以你会得到这样的东西:
for (var i = 0; i < s_1.length; i++) {
if s_2 == s_1.slice(0, i) + s_1.slice(i + 1) {
return true
}
}
对于插入和替换,您需要知道所有字符的字母。您可以将其定义为大字符串var alphabet = "abcde...."
。现在你做了类似的事情,但是当你引入替换或插入时,你也会遍历字母表中的所有元素。我不打算在这里写完整个代码。
其他一些事情。你可以在这里进行大量的微观优化。例如,如果两个字符串的长度相差超过1,则它们显然不能有距离1.另一个字符串与字符串中基础字符的频率有关。