优化JavaScript自动更正实现

时间:2013-07-03 07:39:17

标签: javascript performance optimization

我修改了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树的数据结构,它可以加速它,但我现在无法实现它。我正在寻找优化建议来加快速度。欢迎任何建议。提前谢谢。

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

1 个答案:

答案 0 :(得分:3)

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

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

它利用了这样一个事实,即你真的不需要对整个列表进行排序,因为你所显示的只是前10或12项。数千个列表项中的其余项目是否按正确顺序排列并不重要。因此,在列表中运行时你真正需要跟踪的是你到目前为止看到的十大或十二个项目,事实证明,它比完全排序更快批次

这是伪代码的想法:

  1. 用户键入字符,创建新的搜索字词
  2. 我们定义一个长度为12的空shortlist数组(或者我们想要的许多建议)
  3. 遍历完整的单词列表(我们将其称为dictionary):
    1. 计算词典单词和搜索词之间的(Levenshtein)编辑距离
    2. 如果距离比当前threshold更低(更好),我们:
      • 将该字词添加到shortlist
      • 的顶部
      • shortlist'中的底部字溢出'列表
      • 设置我们的threshold距离以匹配shortlist
      • 底部的单词
  4. 当循环结束时,我们有一个shortlist包含一些最好的单词,但它们没有排序,所以我们只排序那12个单词,这将非常快。
  5. 一个小警告:根据数据集和候选名单中的元素数量,短名单可能与实际的顶级元素略有不同,但这可以减轻通过增加候选名单大小,例如最后一次排序后,只需删除底部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

    中的方法