谷歌搜索建议实施

时间:2013-08-14 08:26:40

标签: data-structures

在最近的一次亚马逊采访中,我被要求实施Google“建议”功能。当用户输入“Aeniffer Aninston”时,谷歌建议“你的意思是Jeniffer Aninston”。我尝试使用散列解决它,但无法覆盖角落的情况。请让我知道你对此的想法。

1 个答案:

答案 0 :(得分:2)

有4种最常见的错误类型 -

  1. 省略的信:“stck”而不是“stack”
  2. 一个字母错字:“styck”而不是“stack”
  3. 额外的字母:“starck”而不是“stack”
  4. 相邻的字母交换:“satck”而不是“stack”
  5. 顺便说一句,我们可以交换不相邻的字母而是任何字母,但这不是常见的拼写错误。

    初始状态 - 键入的单词。从初始顶点运行BFS / DFS。搜索深度是您自己的选择。请记住,增加搜索深度会导致“可能的更正”数量急剧增加。我认为深度4-5是一个好的开始。

    生成“可能的更正”后,在字典中搜索每个生成的单词候选 - 在排序的字典中进行二进制搜索,或者在填充了字典的trie中进行搜索。

    Trie更快但二进制搜索允许在随机访问文件中搜索而无需将字典加载到RAM。您只需加载预先计算的integer数组[]。 Array[i]给出了跳过访问第i个字的字节数。 Random Acces File中的单词应按排序顺序写入。如果你有足够的RAM来存储字典,请使用trie。

    在建议更正之前,请检查输入的字词 - 如果它在字典中,则不提供任何内容。

    <强>更新

    生成更正应该由BFS完成 - 当我尝试DFS时,像“Jeniffer”这样的条目显示“编辑距离= 3”。 DFS不起作用,因为它可以在一个步骤中进行大量更改 - 例如,Jniffer->nJiffer->enJiffer->eJniffer->Jeniffer而不是Jniffer->Jeniffer

    通过BFS生成更正的示例代码

    static class Pair
        {
            private String word;
            private byte dist; 
            // dist is byte because dist<=128.
            // Moreover, dist<=6 in real application
    
            public Pair(String word,byte dist)
            {
                this.word = word;
                this.dist = dist;
            }
    
            public String getWord()
            {
                return word;
            }
    
            public int getDist()
            {
                return dist;
            }
        }
    
    
        public static void main(String[] args) throws Exception
        {
    
            HashSet<String> usedWords;
            HashSet<String> dict;
            ArrayList<String> corrections;
            ArrayDeque<Pair> states;
    
            usedWords = new HashSet<String>();
            corrections = new ArrayList<String>();
            dict = new HashSet<String>();
            states = new ArrayDeque<Pair>();
    
            // populate dictionary. In real usage should be populated from prepared file.
            dict.add("Jeniffer");
            dict.add("Jeniffert"); //depth 2 test
    
            usedWords.add("Jniffer");
            states.add(new Pair("Jniffer", (byte)0));
            while(!states.isEmpty())
            {
                Pair head = states.pollFirst();
                //System.out.println(head.getWord()+" "+head.getDist());
                if(head.getDist()<=2) 
                {
                    // checking reached  depth.
                    //4 is the first depth where we don't generate anything  
    
                    // swap adjacent letters
                    for(int i=0;i<head.getWord().length()-1;i++)
                    {
                        // swap i-th and i+1-th letters
                        String newWord = head.getWord().substring(0,i)+head.getWord().charAt(i+1)+head.getWord().charAt(i)+head.getWord().substring(i+2);
                        // even if i==curWord.length()-2 and then i+2==curWord.length 
                        //substring(i+2)  doesn't throw exception and returns empty string
                        // the same for substring(0,i) when i==0
    
                        if(!usedWords.contains(newWord))
                        {
                            usedWords.add(newWord);
                            if(dict.contains(newWord))
                            {
                                corrections.add(newWord);
                            }
                            states.addLast(new Pair(newWord, (byte)(head.getDist()+1)));
                        }
                    }
    
                     // insert letters 
                    for(int i=0;i<=head.getWord().length();i++)
                          for(char ch='a';ch<='z';ch++)
                          {
                                String newWord = head.getWord().substring(0,i)+ch+head.getWord().substring(i);
                                if(!usedWords.contains(newWord))
                                {
                                    usedWords.add(newWord);
                                    if(dict.contains(newWord))
                                    {
                                        corrections.add(newWord);
                                    }
                                    states.addLast(new Pair(newWord, (byte)(head.getDist()+1)));
                                }
                          }
                }
            }
    
            for(String correction:corrections)
            {
              System.out.println("Did you mean "+correction+"?");
            }
    
            usedWords.clear();
            corrections.clear();
            // helper data structures must be cleared after each generateCorrections call - must be empty for the future usage. 
    
        }
    

    字典中的单词 - Jeniffer,Jeniffert。 Jeniffert仅用于测试)

    输出:

    你的意思是Jeniffer吗? 你的意思是Jeniffert?

    重要!

    我选择生成深度= 2.在实际应用深度应该是4-6,但随着组合数量呈指数增长,我不会那么深。有一些optomizations专门用于减少搜索树中的分支数量,但我对它们没有太多考虑。我只写了主要想法。

    另外,我使用HashSet存储字典和标记用过的单词。当它包含数百万个对象时,似乎HashSet的常量太大了。也许你应该在词典检查中使用单词检查,而是单词标记为检查

    我没有实现擦除字母和更改字母操作,因为我只想显示主要想法。