如何在内存不足的环境中找到书中的高频词?

时间:2009-04-12 18:02:23

标签: text frequency

最近在一次技术访谈中,我被要求编写一个程序来查找教科书中的高频词(出现最多次数的词)。程序的设计应该以最小的内存处理整个教科书。性能不是问题。我能够编程来查找单词的频率,但它耗费了大量的内存。

如何减少内存密集型操作?任何策略/解决方案?

-Snehal

12 个答案:

答案 0 :(得分:5)

您可能使用了内存密集但具有常量查找时间的哈希表 - 因此性能/内存权衡显而易见。当你到达书的末尾时,你会知道你的答案。此外,为每个单词递增计数器的速度很快(因为快速散列表查找)。

光谱的另一端是查看第一个单词,然后浏览整本书以查看该单词出现的次数。这需要最少的内存。然后你为下一个单词做同样的事情并浏览整本书。如果该单词出现的次数较多,则将其添加为顶部单词(或前N个单词)。当然,这是非常低效的 - 如果第一个和第三个词是相同的,你将会再次阅读整本书,即使你只是为第一个词做了同样的事情。

答案 1 :(得分:4)

好的,如果您只对最高n个单词感兴趣,一种方法是两次通过,第一次通过基于修改后的Bloom Filter。不使用位图来跟踪哈希出现,而是使用整数数组 - 字节,16位,32位甚至64位,具体取决于您的输入大小。如果Bloom过滤器只是设置对应于单词的每个散列值的位,则会增加数组中散列索引的计数。

这种方法的问题是两个单词可能会给出相同的哈希值。所以你需要做第二遍,你忽略单词,除非它们的散列总数超过某个阈值,从而减少你需要分配的内存量以进行准确的计数。

因此,只需创建一个位图,其中的位设置为最高出现的哈希值。然后在单词的第二遍中,如果一个单词在位图中为其哈希值“点击”,请查找它或将其添加到哈希表并增加其计数。这通过创建仅出现最高单词的哈希表来最小化内存使用。

答案 2 :(得分:4)

我是一名物理学家,所以我最喜欢的方法是近似。 您无需查看整个文本即可获得最常用的字词。代替:

  • 解析一个足够小的块以允许你的内存限制,
  • 跳过随机数量的文字,
  • 重复,结合累积的结果。
  • 当列表令人满意地收敛时停止。

如果对较小的块使用内存有效算法(例如排序),那么即使是读取每个单词的最有效算法,也可以获得更快的性能。

注意:这确实假设最频繁的单词确实在整个文本中最常出现,而不仅仅发生在文本中的一个位置。对于英语文本,这个假设是正确的,因为整个单词的频率如'the'等。如果您担心此要求,请要求算法至少完成整个文本的一次传递。

答案 3 :(得分:4)

我可能会为此投票......

如果文字是英文,你只想找到前5个最常用的单词,这是你的程序:

print "1. the\n";
print "2. of\n";
print "3. and\n";
print "4. a\n";
print "5. to\n";

快速运行消耗最少的内存!

答案 4 :(得分:3)

如果表现确实无关紧要,你可以依次浏览每个单词,检查它是否在你的“前N”中,如果不是,则计算它的全部出现次数。这样您只需存储N个值。当然,你会多次计算相同的单词,但是,正如你所说,性能不是问题 - 而且代码是微不足道的(通常更可取 - 所有其他条件相同)。

答案 5 :(得分:2)

一种方法是先对列表进行排序。

我们可以在没有大量内存的情况下对这些单词进行排序(以较慢的性能进行交易)。

然后我们可以有一个简单的计数循环来查找具有最大频率的单词,而不必将所有内容保存在内存中,因为它们处于排序状态。

答案 6 :(得分:2)

你的意思是很多进程内存吗?如果是这样,一种方法是将磁盘用作虚拟内存(也就是写一个文件系统包装器)。

答案 7 :(得分:2)

一种可能的解决方案是使用trie数据结构来存储与其出现次数相关联的所有单词。

在此相关问题的答案中可以找到其他解决方案:Space-Efficient Data Structure for Storing a Word List?

答案 8 :(得分:2)

像许多好的面试问题一样,这个问题的含义有点模糊/不精确,迫使受访者要求澄清问题和陈述假设。我认为这里的其他一些答案都很好,因为他们嘲笑这些假设并表现出全局的理解。

假设文本在某处存储为“离线”,但有一种方法可以迭代文本中的每个单词而不将整个文本加载到内存中。

然后下面的F#代码找到前N个单词。它只是数据结构是键值对(字,频率)的映射,它只保留那些键的前N个,所以内存使用是O(N),这是小的。运行时为O(numWordsInText ^ 2),这很差,但在问题约束下可以接受。算法的要点很简单,对于文本中的每个单词,计算它出现的次数,如果它在运行的最佳N中,则将其添加到列表中并删除先前的最小条目。

请注意,下面的实际程序会将整个文本加载到内存中,仅仅是为了便于说明。

#light
// some boilerplate to grab a big piece of text off the web for testing
open System.IO 
open System.Net 
let HttpGet (url: string) = 
    let req = System.Net.WebRequest.Create(url) 
    let resp = req.GetResponse() 
    let stream = resp.GetResponseStream() 
    let reader = new StreamReader(stream) 
    let data = reader.ReadToEnd() 
    resp.Close() 
    data 
let text = HttpGet "http://www-static.cc.gatech.edu/classes/cs2360_98_summer/hw1"
let words = text.Split([|' ';'\r';'\n'|], System.StringSplitOptions.RemoveEmptyEntries)
// perhaps 'words' isn't actually stored in memory, but so long as we can 
// 'foreach' over all the words in the text we're good
let N = 5  // how many 'top frequency' words we want to find
let FindMin map =
    // key-value pair with mininum value in a map
    let (Some(seed)) = Map.first (fun k v -> Some(k,v)) map
    map |> Map.fold_left 
        (fun (mk,mv) k v -> if v > mv then (mk,mv) else (k,v)) 
        seed
let Main() =
    let mutable freqCounts = Map.of_list [ ("",0) ]
    for word in words do
        let mutable count = 0
        for x in words do
            if x = word then
                count <- count + 1
        let minStr,minCount = FindMin freqCounts
        if count >= minCount then
            freqCounts <- Map.add word count freqCounts
        if Seq.length freqCounts > N then
            freqCounts <- Map.remove minStr freqCounts
    freqCounts 
    |> Seq.sort_by (fun (KeyValue(k,v)) -> -v) 
    |> Seq.iter (printfn "%A")
Main()

输出:

[the, 75]
[to, 41]
[in, 34]
[a, 32]
[of, 29]

答案 9 :(得分:1)

您可以结合使用外部合并排序优先级队列。合并排序将确保您的内存限制得到尊重,优先级队列将保持您的前K个搜索。显然,优先级队列必须足够小以适应内存。

  • 首先,将输入字符串分成块,对每个块进行排序并存储到二级存储中(外部排序) - O(n log n)
  • 读取每个块并在块内,计算单词的频率,因此在此步骤结束时,每个块在块中减少为(唯一字 - 频率计数)。为O(n)
  • 开始读取块中的元素并聚合每个单词。由于块是排序的,您可以在O(n)
  • 中完成
  • 现在,维护K个元素的最小优先级堆(堆顶部是堆中的最小元素)。如果其计数大于堆中的顶部元素,弹出顶部和推送当前单词,则通过前K个元素填充优先级堆,然后填充下一个(唯一字 - 最终计数)。 O(n log k)

因此,您的最终时间复杂度为 O(n(log k + log n))         -

答案 10 :(得分:0)

好吧,如果你想要绝对可怕的表现......

取出书中的第一个单词,并计算它出现的次数。拿出书中的第二个字,计算它出现的次数。如果它超过了最后一个单词,则丢弃最后一个单词。等等......除非你在某处保留一份列表,否则你最终会多次计算相同的单词,但如果真的想要最小化内存,那么这应该只需要几个整数。应该在O(n ^ 2)时间运行,其中n是书中的单词数。

答案 11 :(得分:0)

如何创建单词密钥的二叉树(当您继续阅读文件中的单词时)。这有助于在O(Log(n))中搜索已经重复的单词。所以最后你得到O(nLog(n))用于顶级单词搜索。

基本算法

对于文件中的每个单词:

  1. 为给定单词创建唯一键(加权ascii char,例如“bat”可以是1 *'b'+ 2 *'a'+ 3 *'c';
  2. 将此单词添加到树中。如果单词已存在则增加新计数。
  3. 将单词和当前计数输入到maintainTop5(单词,计数)。 maintainTop5()维护前5个计数和相关单词的动态列表。
  4. 文件的结尾有前5个单词。