给定一个文件,尽可能有效地找到十个最常出现的单词

时间:2010-12-21 00:18:24

标签: algorithm string

这显然是一个面试问题(在面试问题的集合中找到),但即使它不是很酷。

我们被告知要在所有复杂性措施上有效地做到这一点。我想创建一个HashMap,将单词映射到它们的频率。这将是时间和空间复杂度的O(n),但由于可能有很多单词,我们不能假设我们可以将所有内容存储在内存中。

我必须补充一点,问题中没有任何内容说这些词不能存储在内存中,但是如果是这样的话怎么办?如果情况并非如此,那么这个问题似乎并不具有挑战性。

15 个答案:

答案 0 :(得分:19)

优化我自己的时间:

sort file | uniq -c | sort -nr | head -10

可能后跟awk '{print $2}'以消除计数。

答案 1 :(得分:12)

我认为trie data structure是一种选择。

在trie中,您可以在每个节点中记录字数,表示从根到当前节点的路径上由字符组成的字的频率。

设置trie的时间复杂度是O(Ln)~O(n)(其中L是最长单词中的字符数,我们可以将其视为常数)。为了找到前10个单词,我们可以遍历trie,这也需要花费O(n)。所以需要O(n)来解决这个问题。

答案 2 :(得分:3)

完整的解决方案是这样的:

  1. 执行外部排序O(N log N)
  2. 在文件O(N)中计算单词freq
  3. (替代方案是使用Trie作为@Summer_More_More_Tea来计算频率,如果你能负担得起这么多的内存)O(k * N)//用于前两个步骤
  4. 使用最小堆:
    • 将前n个元素放在堆上
    • 对于每个单词,将其添加到堆中并删除堆中的新min
    • 最后堆将包含第n个最常用的单词O(| words | * log(n))
  5. 使用Trie,成本将为O(k * N),因为总词数通常大于词汇量的大小。最后,由于大多数西方语言的k值较小,因此可以假设线性复杂度。

答案 3 :(得分:2)

我在C#中做过这样的(样本)

int wordFrequency = 10;
string words = "hello how r u u u u  u  u u  u  u u u  u u u u  u u u ? hello there u u u u ! great to c u there. hello .hello hello hello hello hello .hello hello hello hello hello hello ";            

var result = (from word in words.Split(new string[] { " " }, StringSplitOptions.RemoveEmptyEntries)
                          group word by word into g
                          select new { Word = g.Key, Occurance = g.Count() }).ToList().FindAll(i => i.Occurance >= wordFrequency);

答案 4 :(得分:2)

假设我们为26个字母表中的每一个分配一个随机素数。然后我们扫描文件。每当我们找到一个单词时,我们就会计算它的哈希值(基于位置的公式和制作单词的字母表的值)。如果我们在哈希表中找到这个值,那么我们肯定知道我们没有第一次遇到它并且我们增加它的键值。并维护一个最大为10的数组。但是如果我们遇到一个新的哈希,那么我们存储该哈希值的文件指针,并将该键初始化为0.。

答案 5 :(得分:1)

您可以进行时间/空间权衡并通过O(n^2)获取时间和O(1)(内存)空间,方法是计算每次在线性传递中每次出现单词时出现的次数。数据。如果计数在目前为止发现的前10名之上,那么保留单词和计数,否则忽略它。

答案 6 :(得分:1)

说构建哈希并对值进行排序是最好的。我倾向于同意。 http://www.allinterview.com/showanswers/56657.html

这是一个类似的Bash实现......我想 http://www.commandlinefu.com/commands/view/5994/computes-the-most-frequent-used-words-of-a-text-file

答案 7 :(得分:1)

根据输入数据的大小,保留HashMap可能是也可能不是一个好主意。比如说,我们的哈希映射太大而无法放入主内存。这可能导致大量内存传输,因为大多数哈希映射实现需要随机访问,并且在缓存上不会很好。

在这种情况下,对输入数据进行排序将是更好的解决方案。

答案 8 :(得分:1)

我认为这是计数排序的典型应用,因为每个单词的出现总和等于单词的总数。具有计数排序的哈希表应该在与单词数量成比例的时间内完成工作。

答案 9 :(得分:0)

循环显示单词串并将每个单词存储在字典中(使用python)以及它们作为值出现的次数。

答案 10 :(得分:0)

如果单词列表不适合内存,则可以将文件拆分为止。生成每个部分的直方图(顺序或并行),并合并结果(如果您希望保证所有输入的正确性,但不应该损害O(n)工作量,或者不应该损害O(n)工作,其结果可能有点繁琐。 k任务的O(n / k)时间。)

答案 11 :(得分:0)

Radix tree或其中一个变体通常会允许您通过折叠常用序列来节省存储空间。
构建它将需要O(nk) - 其中k是“集合中所有字符串的最大长度”。

答案 12 :(得分:0)

第1步:如果文件非常大且无法在内存中进行排序,则可以将其拆分为可在内存中排序的块。

第2步:对于每个已排序的块计算排序对(单词,nr_occurrence),在他的位置,您可以放弃到块,因为您只需要排序的对。

第3步:对块进行迭代并对块进行排序,并始终保持前十位。

示例:

第1步

a b ab ab ab b b c c ab ab

分成:

块1:a b a ab
块2:abb a a b b
块3:c c ab ab

第2步

块1:a2,b1,ab1 块2:a2,b2,abb1
块3:c2,ab2

第3步(合并块并保持前十名):

a4 b3 ab3 c2 abb1

答案 13 :(得分:0)

    int k = 0;
    int n = i;
    int j;
    string[] stringList = h.Split(" ".ToCharArray(),
                                  StringSplitOptions.RemoveEmptyEntries);
    int m = stringList.Count();
    for (j = 0; j < m; j++)
    {
        int c = 0;
        for (k = 0; k < m; k++)
        {
            if (string.Compare(stringList[j], stringList[k]) == 0)
            {
                c = c + 1;
            }
        }
    }

答案 14 :(得分:0)

不是最有效的CPU,而且是UGLY,但只花了2分钟才敲出来:

perl -lane '$h{$_}++ for @F; END{for $w (sort {$h{$b}<=>$h{$a}} keys %h) {print "$h{$w}\t$w"}}' file | head

使用-n来循环每一行 使用@F将每行分成-a个单词 每个$_字增加散列%h
到达END file后, 频率为sort哈希值 打印频率$h{$w}和单词$w
使用bash head停在10行

使用此网页的文字作为输入:

121     the
77      a
48      in
46      to
44      of
39      at
33      is
30      vote
29      and
25      you

我将这个解决方案与顶级shell解决方案(ben jackson)在一个3.3GB的文本文件上进行了基准测试,其中包含580,000,000个单词。
Perl 5.22在171秒内完成,而shell解决方案在474秒内完成。