我如何计算重复的单词?

时间:2011-07-23 23:13:44

标签: java algorithm word-count

给定1GB(非常大)的文件包含单词(有些重复),我们需要读取文件并输出每个单词重复的次数。如果我的解决方案性能很高,请告诉我。

(为简单起见,假设我们已经捕获了arraylist<string>

中的单词

我认为大O(n)是“n”。我是对的吗?

public static void main(String[] args) {

            ArrayList al = new ArrayList();
            al.add("math1");
            al.add("raj1");
            al.add("raj2");
            al.add("math");
            al.add("rj2");

            al.add("math");
            al.add("rj3");
            al.add("math2");
            al.add("rj1");
            al.add("is");
            Map<String,Integer> map= new HashMap<String,Integer>();

            for (int i=0;i<al.size();i++)
            {
                String s= (String)al.get(i);

                    map.put(s,null);

            }
            for (int i=0;i<al.size();i++)
            {
                String s= (String)al.get(i);
                if(map.get(s)==null)
                    map.put(s,1);
                else
                {
                    int count =(int)map.get(s);
                        count=count+1;
                        map.put(s,count);
                }


            }

            System.out.println("");
        }

5 个答案:

答案 0 :(得分:2)

我认为你可以比使用HashMap做得更好。

关于hashmap解决方案的深思熟虑

你的anwser是可以接受的,但考虑到这一点:为了简单起见,我们假设你一次将一个字节的文件读入StringBuffer,直到你找到一个空格。此时,您将调用toString()将StringBuffer转换为String。然后检查字符串是否在HashMap中,并且它是存储还是计数器增加。

英语词典。包含在linux中有400k字,大小约为5MB。因此,在您阅读的“1GB”文本中,我们可以猜测您只需在HashMap中存储大约5MB的文本。文件的其余部分将转换为字符串,在您在地图中完成查找后,需要将其作为垃圾收集。我可能错了,但我相信字符串将在构造String期间再次迭代,因为字节数组需要在内部复制以便再次计算HashCode。因此,该解决方案可能会浪费相当多的CPU周期并迫使GC经常发生。

可以在面试中指出这样的事情,即使这是你能想到的唯一解决方案。

我可以考虑使用自定义RadixTree或类似结构

请记住RadixT / Trie的插入方法是如何工作的。这是采用字符串/字节流(通常是字符串)并将每个元素与树中的当前位置进行比较。如果前缀存在,它只是在锁定步骤中向下前进树和字节流。当它遇到新的后缀时,它开始将节点添加到树中。到达流的末尾后,它将该节点标记为EOW。现在考虑我们可以在读取更大的流时做同样的事情,通过在我们到达空间时将当前位置重置为树的根。

如果我们编写了我们自己的Radix树(或者可能是Trie),那么谁的节点具有字结束计数器(而不是标记)并且直接从文件读取插入方法。我们可以一次一个字节/字符将节点插入树中,直到我们读取空格。此时插入方法将递增字结束计数器(如果它是现有字)并将树中的当前位置重置回头并再次开始插入字节/字符。基数树的工作方式是折叠单词的重复前缀。例如:

The following file:

math1 raj1 raj2 math rj2 math rj3 

would be converted to:

(root)-math->1->(eow=1)
     |    |-(eow=2)
     |    
      raj->1->(eow=1)
      | |->2->(eow=1)
      | |->3->(eow=1)
      j2->(eow=1)

像这样插入树的插入时间是O(k),其中k是最长字的长度。但是因为我们在读取每个字节时插入/比较。我们已经不仅仅是阅读文件了,效率也不高。

另外,请注意我们将字节读入一个临时字节,它将是一个堆栈变量,因此我们需要从堆中分配内存的唯一时间是我们遇到一个新单词(实际上是一个新的后缀) 。因此,垃圾收集几乎不会经常发生。并且Radix树使用的总内存比HashMap要小很多。

答案 1 :(得分:1)

理论上,由于HashMap访问通常是O(1),我猜你的算法是O(n),但实际上有几个效率低下。理想情况下,您只需迭代文件的内容一次,在读取它们时处理(即计数)单词。不需要将整个文件内容存储在内存中(您的ArrayList)。你循环遍历内容三次 - 一次读取它们,以及上面代码中两个循环中的第二次和第三次。特别是,上面代码中的第一个循环是完全没必要的。最后,您对HashMap的使用将比所需的慢,因为构造时的默认大小非常小,并且它必须在内部增长很多次,每次都会强制重建哈希表。最好从适合您期望的尺寸开始。您还必须考虑负载因子。

答案 2 :(得分:1)

您是否考虑过使用mapreduce解决方案?如果数据集变大,那么将它分成片并将字数统一计算

真的会更好

答案 3 :(得分:0)

你应该只用文字阅读文件一次。

无需预先设置空值 - 您可以在主循环中执行此操作。

在这两种情况下,复杂性确实是O(n),但是你想让常数尽可能小。 (O(n)= 1000 * O(n),右:))

答案 4 :(得分:0)

要回答您的问题,首先,您需要了解HashMap的工作原理。它由桶组成,每个桶都是一个链表。如果由于哈希另一对需要占用相同的桶,它将被添加到链表的末尾。因此,如果map具有高负载因子,则搜索和插入将不再是O(1),并且算法将变得低效。此外,如果地图载荷系数超过预定载荷系数(默认为0.75),则整个地图将被重新定位。

这是JavaDoc http://download.oracle.com/javase/6/docs/api/java/util/HashMap.html的摘录:

  

地图中预期的条目数及其加载因子应该是   在设定初始容量时要考虑到   最小化重复操作的次数。如果初始容量是   大于最大条目数除以负载系数,   不会发生任何重复操作。

所以我建议你预定一个地图容量,猜测每个单词都是唯一的:

Map<String,Integer> map= new HashMap<String,Integer>(al.size());

如果没有这个,你的解决方案效率不够,尽管它仍然具有线性逼近O(3n),因为由于重新散列的摊销,元素的插入将花费3n而不是n。