【问题标题】:Optimizing JavaScript autocorrect implementation优化 JavaScript 自动更正实现
【发布时间】:2013-10-01 08:02:44
【问题描述】:

我也修改了 jQuery Autocomplete 实现以生成自动更正建议。我使用 Levenshtein 距离作为衡量最接近匹配的指标。

在 jQuery 自动完成没有更多建议之后,我的代码在每次按键时运行。这是我写的代码:

// Returns edit distance between two strings
edit_distance : function(s1, s2) {
    // Auxiliary 2D array
    var arr = new Array(s1.length+1);
    for(var i=0 ; i<s1.length+1 ; i++)
        arr[i] = new Array(s2.length+1);

    // Algorithm
    for(var i=0 ; i<=s1.length ; i++)
        for(var j=0 ; j<=s2.length ; j++)
            arr[i][j] = 0;
    for(var i=0 ; i<=s1.length ; i++)
        arr[i][0] = i;
    for(var i=0 ; i<=s2.length ; i++)
        arr[0][i] = i;

    for(var i=1 ; i<=s1.length ; i++)
        for(var j=1 ; j<=s2.length ; j++)
            arr[i][j] = Math.min(arr[i-1][j-1] + (s1.charAt(i-1)==s2.charAt(j-1) ? 0 : 1), arr[i-1][j]+1, arr[i][j-1]+1);

    // Final answer
    return arr[s1.length][s2.length].toString(10);
},

// This is called at each keypress
auto_correct : function() {
    // Make object array for sorting both names and IDs in one go
    var objArray = new Array();
    for(var i=0 ; i<idArray.length ; i++) {
        objArray[i]      = new Object();
        objArray[i].id   = idArray[i];
        objArray[i].name = nameArray[i];
    }

    // Sort object array by edit distance
    var out = this;
    companyObjArray.sort (
        function(a,b) {
            var input = jQuery("#inputbox").val().toLowerCase();
            var d1    = a.name.toLowerCase();
            var d2    = b.name.toLowerCase();
            return out.editDistance(input,d1) - out.editDistance(input,d2);
        }
    );

    // Copy some closest matches in arrays that are shown by jQuery
    this.suggestions = new Array();
    this.data = new Array();
    for(var i=0 ; i<5 ; i++) {
        this.suggestions.push(companyObjArray[i].name);
        this.data.push(companyObjArray[i].id);
    }
}

所有名称都有与之关联的 ID,因此在排序之前我只是从它们中创建一个对象数组,并对数组进行排序。

由于要搜索的列表有数千个,因此速度很慢。我发现了一种叫做 BK-tree 的数据结构,它可以加快速度,但我现在无法实现它。我正在寻找优化建议以加快速度。欢迎任何建议。提前致谢。

编辑。我决定使用Sift3 作为我的字符串距离度量而不是 Levenshein 距离,它可以提供更有意义的结果并且速度更快。

【问题讨论】:

  • var input = jQuery("#inputbox").val().toLowerCase();上移三行,外部比较函数。
  • 其实就是把编辑距离存储在object里。为每次比较重新计算它似乎很浪费。
  • @zch 是的,改变了它。谢谢!

标签: javascript performance optimization


【解决方案1】:

您可以在这里优化很多东西,但大部分都归结为:每个“较重”的计算只执行一次。您在每次按键、每次排序比较等方面都做了很多工作,缓存这些值会有很大帮助。

但是,对于显着的性能提升,您可以使用另一个非常漂亮的优化技巧。每个主要搜索引擎都使用它,包括 Amazon.com 等网站上的现场搜索引擎。

它利用了您实际上不需要对整个列表进行排序这一事实,因为您所显示的只是前 10 或 12 个项目。数千个列表项中的其余部分是否按正确顺序排列并不重要。因此,在浏览列表时,您真正需要跟踪的是到目前为止您看到的前 10 或 12 个项目,事实证明,这比完全排序快 很多 .

这是伪代码中的想法:

  1. 用户输入一个字符,创建一个新的搜索词
  2. 我们定义了一个长度为 12 的空 shortlist 数组(或者我们想要的许多建议)
  3. 遍历完整的单词列表(我们称之为dictionary):
    1. 计算字典词和搜索词之间的 (Levenshtein) 编辑距离
    2. 如果距离小于(更好)当前threshold,我们:
      • 将单词添加到shortlist 的顶部
      • shortlist 'overflow' 中最下面的单词脱离列表
      • 设置我们的threshold 距离以匹配shortlist 底部的单词
  4. 循环结束后,我们有一个 shortlist 包含一些最好的词,但它们没有排序,所以我们只对这 12 个词进行排序,这会非常快。

一个小警告:根据数据集和候选名单中的元素数量,候选名单可能与实际的顶级元素略有不同,但这可以得到缓解通过增加候选名单的大小,例如到 50,并在最终排序完成后删除底部的 38。

至于实际代码:

// Cache important elements and values
var $searchField = $('#searchField'),
    $results = $('#results'),
    numberOfSuggestions = 12,
    shortlistWindowSize = 50,
    shortlist,
    thresholdDistance;

// Do as little as possible in the keyboard event handler
$searchField.on('keyup', function(){
    shortlist = [];
    thresholdDistance = 100;

    // Run through the full dictionary just once,
    // storing just the best N we've seen so far
    for (var i=0; i<dictionarySize; i++) {
        var dist = edit_distance(this.value, dictionary[i]);
        if (dist < thresholdDistance) {
            shortlist.unshift({
                word: dictionary[i],
                distance: dist
            });
            if (shortlist.length > shortlistWindowSize) {
                shortlist.pop();
                thresholdDistance = shortlist[shortlistWindowSize-1].distance;
            }
        }
    }

    // Do a final sorting of just the top words
    shortlist.sort(function(a,b){
        return a.distance - b.distance;
    });


    // Finally, format and show the suggestions to the user
    $results.html('<p>' + $.map(shortlist, function(el){
        return '<span>[dist=' + el.distance + ']</span> ' + el.word;
    }).slice(0,numberOfSuggestions).join('</p><p>') + '</p>').show();
});

试试这个12.000 word demo on jsFiddle中的方法

【讨论】:

  • 不错的答案!但我认为初始阈值 100 太高了;它将无用的建议纳入短名单,从而导致更多相关建议的丢失。通过这种方法,我得到的结果不太准确,但它们非常快。我现在正在试验阈值以获得最佳结果。谢谢!
  • 顺便说一句,我不明白每次更改thresholdDistance 的意义。在我实施它之后,它产生了糟糕的结果。不每次都更新它会产生更好的结果。
  • 初始值并不是为了区分好坏,它只是被定义为高于字典中的每个单词。如果你不更新thresholdDistance,算法根本不起作用——如果你得到的结果很差,那么你可能在某个地方遇到了一个实现错误。
  • @Bruce:具体来说,您是否尝试像我在示例中那样增加“窗口大小”?如果您只跟踪 shortlist 中的目标元素数量,而不是更宽的窗口,则可能会导致准确性下降。
  • 是的,有一个小错误!感谢你的帮助。顺便说一句,我决定使用 this 而不是 Levenshtein 距离。它速度更快,也被广泛使用。
猜你喜欢
  • 1970-01-01
  • 2016-10-16
  • 2018-08-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-06
  • 1970-01-01
  • 2012-11-12
  • 2014-06-30
相关资源
最近更新 更多