經常遇到這樣的一個問題:在海量數據中找出出現頻率最高的前K個數,或者從海量數據中找出最大的前K個數,這類問題一般稱爲「top K」問題,如:在搜索引擎中,統計搜索最熱門的10個查詢詞;在歌曲庫中統計下載率最高的前10首歌等等。html
①: 本人初次學習軟件工程,近來便遇到一個相似的問題,問題是關於統計一片文章中統計出現頻率最高的前十個單詞。其實剛拿到這個程序時,也以爲很容易:無非是文件的導入、單詞的分類、以及排序算法。因此便開始編程(由於大學沒有養成需求設計的習慣,更加註重編寫代碼),因此,編着編着便越遍越多,仔細看來不只代碼冗餘,並且結構混亂。正如師所說,本身的代碼不只別人看不懂,本身幾天後都不知道本身的某個類時期什麼做用。因而我便開始着手分析程序(由於本身熟悉java,以爲java的封裝作的好,其實論速度仍是c語言比較快)。java
First:理解這道題作什麼的?正則表達式
這道小程序作的是統計一篇文章出現頻率最高的十個詞,及相同的次的次數排序。算法
Second:理解這道題須解決哪些問題?編程
(1)文件的導入(其實很簡單,一個BufferedReader便能搞定)。小程序
(2)單詞的讀取,由於是一整篇文章,須要分割單詞依據「,。?!」等這些進行分割。數組
(3)相同字數的統計。多線程
(4)排序算法。ide
Third:功能的實現?學習
(1)文件的導入,依據java提供的基於字符型的文件的輸入輸出與緩衝流的應用(相信學過I/O的輸入輸出都應該會)
關鍵代碼:
File file=new File(path); FileReader fileReader=new FileReader(file); //創建文件輸入流 BufferedReader bufferedReader=new BufferedReader(fileReader); //創建緩衝輸入流
(2)單詞的提取,其實剛開始原本想利用數組分割,可是以爲麻煩,後來查閱資料,之前學過的java正則表達式,起到了關鍵做用。
public String[] split(String regex,int limit)
此方法返回的數組包含此字符串的子字符串,每一個子字符串都由另外一個匹配給定表達式的子字符串終止,或者由此字符串末尾終止。數組中的子字符串按它們在此字符串中出現的順序排列。若是表達式不匹配輸入的任何部分,那麼所得數組只具備一個元素,即此字符串。
關鍵代碼:
String []splittStr=str.split("[\\s,.;!?]"); //利用java正則表達式實現單詞的分離
(3)相同字數的統計:由於學過Hashmap集合類,想到了Hashmap中有個方法:
containsKey(Object key)
若是此映射包含對於指定鍵的映射關係,則返回 true。故能夠利用此方法進行相同歸類:
關鍵代碼:
while((str=bufferedReader.readLine())!=null) { String []splittStr=str.split("[\\s,.;!?]"); //利用java正則表達式實現單詞的分離 for(int i=0;i<splittStr.length;i++) { if(!splittStr[i].equals("")) { splittStr[i]=splittStr[i].toLowerCase(); //利用hashmap的containsKey判斷是否包含鍵的映射關係 if(!hashMap.containsKey(splittStr[i])) { hashMap.put(splittStr[i], 1); } else { hashMap.put(splittStr[i], Integer.parseInt(""+hashMap.get(splittStr[i]))+1); } } } }
(4)排序算法:將Map轉化位List,利用sort(List<T> list, Comparator<? super T> c)
根據指定比較器產生的順序對指定列表進行排序。
關鍵代碼:
List<Map.Entry<String,Integer>> list_Data = new ArrayList<Map.Entry<String, Integer>>(hashMap.entrySet()); Collections.sort(list_Data, new Comparator<Map.Entry<String, Integer>>() { public int compare(Map.Entry<String, Integer> o1, Map.Entry<String, Integer> o2) { if(o2.getValue()!=null&&o1.getValue()!=null&&o2.getValue().compareTo(o1.getValue())>0){ return 1; }else{ return -1; } } });
Forth:結果測試:
②:公司解決如此問題:
針對top k類問題,一般比較好的方案是【分治 + trie樹/hash + 小頂堆】,即先將數據集按照hash方法分解成多個小數據集,而後使用trie樹或者 hash統計每一個小數據集中的query詞頻,以後用小頂堆求出每一個數據集中出頻率最高的前K個數,最後在全部top K中求出最終的top K。
實際上,最優的解決方案應該是最符合實際設計需求的方案,在實際應用中,可能有足夠大的內存,那麼直接將數據扔到內存中一次性處理便可,也可能機器有多個核,這樣能夠採用多線程處理整個數據集。
哈哈,到此本人第一次軟件工程初步認識,但願你們多提意見多多進步。
附源代碼:
package com.su.test; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.FileReader; import java.io.FileWriter; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Scanner; public class Test { //沒有處理異常,將異常拋出 public static void main(String[] args) throws Exception{ // TODO Auto-generated method stub System.out.print("請輸入所在文件的位置:"); Scanner scanner=new Scanner(System.in); //文本輸入 String path=scanner.next(); File file=new File(path); FileReader fileReader=new FileReader(file); //創建文件輸入流 BufferedReader bufferedReader=new BufferedReader(fileReader); //創建緩衝輸入流 String str=null; HashMap<String,Integer> hashMap=new HashMap<String, Integer>(); //hashmap集合,利用鍵值:key-單詞、value-出現次數 while((str=bufferedReader.readLine())!=null) { String []splittStr=str.split("[\\s,.;!?]"); //利用java正則表達式實現單詞的分離 for(int i=0;i<splittStr.length;i++) { if(!splittStr[i].equals("")) { splittStr[i]=splittStr[i].toLowerCase(); //利用hashmap的containsKey判斷是否包含鍵的映射關係 if(!hashMap.containsKey(splittStr[i])) { hashMap.put(splittStr[i], 1); } else { hashMap.put(splittStr[i], Integer.parseInt(""+hashMap.get(splittStr[i]))+1); } } } } /* * *對Hashmap進行排序(按value值進行排序) */ List<Map.Entry<String,Integer>> list_Data = new ArrayList<Map.Entry<String, Integer>>(hashMap.entrySet()); Collections.sort(list_Data, new Comparator<Map.Entry<String, Integer>>() { public int compare(Map.Entry<String, Integer> o1, Map.Entry<String, Integer> o2) { if(o2.getValue()!=null&&o1.getValue()!=null&&o2.getValue().compareTo(o1.getValue())>0){ return 1; }else{ return -1; } } }); int len=list_Data.size(); //獲取list_Data的大小 for(int i=0;i<10;i++) { System.out.print(list_Data.get(i)+" "); } bufferedReader.close(); } }