在大字典中查找单词的存在

时间:2009-08-26 02:46:22

标签: dictionary

假设我在平面文件中给出了一个包含2亿字的大字典,我的函数需要检查字典中任何给定单词的存在,最快的方法是什么?您无法将字典存储在内存中,因为您只有1GB的内存。您可以将其存储在数据库中,但是在没有任何优化的情况下查询它仍然会非常慢。您无法索引完整的单词,因为您没有足够的资源。

编辑:除了下面提到的文件优化方法,还有任何数据库优化吗?我正在考虑创建部分索引,比如在单词中每2个字母达到一个限制,我创建一个索引。这会加速数据库查询吗?

8 个答案:

答案 0 :(得分:21)

二进制搜索

假设字典按字母顺序排列,我会尝试修改binary search。通过跳转到文件中的中点位置来分割和征服文件,并查看其中的单词。如果猜到高,则将下半部分分成两半并再试一次,直到没有文件位置可以尝试或找到该单词。

(作为outis mentioned in a comment,在跳转到文件位置后,您需要向后和向前扫描以找到您跳转到的单词的边界。)

您可以通过根据单词的第一个字母直接猜测位置块来优化此功能。例如,如果单词以“c”开头,则围绕文件的3/26部分开始搜索。虽然,实际上,我认为这种早期猜测只能在总体上产生微不足道的差异。

其他优化可能包括保留索引的一小部分。例如,保留以字母表中每个字母开头的第一个单词的索引,或者保留每个单词的索引,每个单词以每个可能的两个字母组合开头。这样您就可以立即缩小搜索范围。

O(log n)

答案 1 :(得分:14)

这是Bloom filter的经典用例。 Bloom过滤器是一种概率数据结构,它针对成员资格测试进行了优化(“X是此集合的成员吗?”),并提供 O(1)查找。作为交换,你引入一个任意小的假阳性概率 - 也就是说,过滤器会说一个特定的词存在,但实际上并不存在。你使用的内存越多,你就可以越小。但是,漏报的概率为零:过滤器永远不会说如果一个单词实际存在则不存在。

在您的特定情况下,使用80亿比特(1 GB),您可以在每1,000,000,000次试验中获得比1更好的误报率。这是一个非常低的误报率。如果你查找了2亿个随机字符串,那么你从未遇到过一个假阳性的概率大约为82%。

这不需要对字典进行排序,节省空间,并且不需要数据库或其他辅助存储结构。总的来说,它可能是满足您需求的不错选择。

答案 2 :(得分:5)

使用Trie可以有效地解决经典的单词查找问题。不幸的是,正如您所提到的,您无法在内存中存储所有所需的数据,但这不应该阻止您使用Trie来减少搜索空间。假设您不是在Trie中存储整个单词集,而是只存储初始段,而您的终端节点指向在数据库中轻松(快速)搜索的小型数据集。

答案 3 :(得分:3)

如果单词共享很多前缀和后缀,你可以使用Directed Acyclic Word Graph将它们全部加载到内存中(What up,DAWG!)

它就像一个特里,但压缩共享的后缀。这是否有用取决于词典中的内容,但将200M装入1GB内存可能是可行的。

答案 4 :(得分:0)

答案 5 :(得分:0)

如果您没有索引,只需使用流。

有时最简单的解决方案是最好的。

   public Int32 IndexOf(FileStream file, Byte[] ascii_bytes, Int32 start_index)
   {
       Int32 index = -1;
       {
           Int32 current = 0;
           Int64 original_index = 0;
           Boolean found = true;

           file.Position = start_index;
           current = file.ReadByte();
           while (current >= 0)
           {
               if ((Byte)current == ascii_bytes[0])
               {
                   found = true;
                   original_index = file.Position - 1;
                   for (Int32 i = 1; (i < ascii_bytes.Length && current > 0); i++)
                   {
                       current = file.ReadByte();
                       if ((Byte)current != ascii_bytes[i])
                       {
                           file.Position--;
                           found = false;
                           break;
                       }
                   }

                   if (found)
                   {
                       file.Position = original_index;
                       index = (Int32)original_index;
                       break;
                   }
               }
               current = file.ReadByte();
           }
       }
       return index;
   }

答案 6 :(得分:0)

假设:

  1. 您将在流程的生命周期中多次搜索一个单词(而不是每次查找单词时都启动流程)。
  2. 文件已排序。
  3. 您可以对数据进行部分索引,占用大部分可用内存:使用B树或排序数组在文件中存储单词及其起始位置(后者更节省空间,但需要单个连续块;另外,b-tree要求你存储一个块的结束位置而数组不存在。留出足够的内存空间来从文件中加载一大块单词。搜索索引(树遍历或二进制搜索)以查找包含该单词的块。从部分索引中找到特定块后,将相应的块从文件加载到内存中并对其执行二进制搜索。

    如果您需要额外的内存,可以从索引中抛出一些元素。使用该数组,您可以使用以下伪C伪代码将索引缩减为 n 元素:

    struct chunk {
        string word;
        int start;
    };
    chunk index[];
    d = index.length / n;
    for (i=0;i<n; ++i) {
        index[i] = index[i*d];
    }
    realloc(index, sizeof(chunk) * n);
    

    由于块i的结尾是index[i+1].start,因此算法对于数组实现来说非常简单。对于基于B树的索引,您可以轻松地将叶子与父亲合并。

答案 7 :(得分:0)

如果某些单词的查询频率高于其他单词,那么在内存中使用LRU缓存和数据库可能是有意义的。