-
這個做業屬於哪一個課程:軟件工程 1916 | Whtml
-
這個做業要求在哪裏:結對第二次——文獻摘要熱詞統計及進階需求java
-
結對學號:021600823 餘秉鴻, 221600126 劉忠燏git
-
這個做業的目標:完成做業要求中的基本需求和進階需求;熟悉 Git 和 GitHub 的使用;學習並掌握單元測試技巧;藉助單元測試,適當重構部分代碼github
-
Fork 的 GitHub 項目地址:PairProject1-Java數組
-
GitHub 的簽入記錄markdown
一些備註
本次結對做業中,我和隊友之間的分工是這樣的:app
- 餘秉鴻:完成基礎需求,留下方便隊友完成進階需求的API
- 劉忠燏:在隊友代碼的基礎上,完成進階需求
因爲這種分工,普通需求和進階需求的倉庫其實是分別提交的,因此我只Fork了基本倉。函數
並且因爲對Git基本操做的不太熟練,因此提交記錄可能會有些許凌亂。。。post
PSP 表格
PSP2.1 | Personal Software Process Stages | 預估耗時(分鐘) | 實際耗時(分鐘) |
---|---|---|---|
Planning | 計劃 | 20 | 20 |
· Estimate | · 估計這個任務須要多少時間 | 20 | 20 |
Development | 開發 | 750 | 1335 |
· Analysis | · 需求分析(包括學習新技術) | 180 | 240 |
· Design Spec | · 生成設計文檔 | 30 | 45 |
· Design Review | · 設計複審 | - | |
· Coding Standard | · 代碼規範(爲目前的開發制定合適的規範 | 30 | 90 |
· Design | · 具體設計 | 120 | 240 |
· Coding | · 具體編碼 | 360 | 570 |
· Code Review | · 代碼複用 | - | |
· Test | · 測試(自我測試、修改代碼、提交修改) | 30 | 150 |
Reporting | 報告 | 40 | 40 |
· Test Report | · 測試報告 | 10 | 10 |
· Size Measurement | · 計算工做量 | 10 | 10 |
· Postmortem & Process Improvement | · 過後總結,並提出過程改進計劃 | 20 | 20 |
合計 | 810 | 1395 |
WordCount 基本需求——思路
做業發佈以後,我稍微看了一下需求,而且大概思考了一下如何完成此次做業才能更好的學到東西,由於自認爲半路出家,基礎可能稍微薄弱。最後和隊友商量,他完成進階需求,我來完成基礎需求,而且給出接口,可是咱們商量的時候交代沒有特別清楚(進階需求須要調用wordCount的地方我都實現了,隊友須要改爲進階模式的時候調用就行了)。單元測試
由於當時沒有溝通清楚,因此在前兩天隊友過來討論進度的時候可能讓他有點小崩潰,我當晚雖然加急重構了一下代碼,但心裏實在仍是感到有些過意不去。
個人基本思路就是用BufferedReader()和BufferedWriter()來進行文件讀寫。而且使用readLine()方法逐行讀取並分別傳入統計字符,統計行數,統計單詞三個方法。而後返回統計結果,類圖有空補上。
Wordcount 基本功能——實現
由於wordCount在基礎和進階的需求上有不同的地方,因此我須要添加一個判斷事件,根據進階和基礎需求的不一致來返回結果。但三個統計方法都是同樣的。
統計字符和統計行數較爲簡單,思路也不用多說,其中統計行數的方法原本能夠省略,可是考慮到可能有包含空字符如'\t'的非空行,因此須要對行內字符進行判斷。
主要難點在統計單詞上,咱們首先將不含有分隔符的字母數字數組稱爲單詞,而且按照本次做業的要求,將其分爲合法單詞和不合法單詞。
對於輸入的String,咱們認爲除了最後一個單詞,任何兩個單詞之間都有分隔符的存在,因此我寫了一個cutWords方法,返回每次讀取String遇到的分隔符的下標,若是其中不存在分隔符,就返回當前String的長度。咱們就認爲該String是一個單詞。
public int cutWords(String input) { // 分隔單詞 char[] charArray = input.toCharArray(); for (int index = 0; index < charArray.length; index++) { if (isNotDigitOrAlpha(charArray[index])) // 出現非字母數字字符則返回下標 return index; } return charArray.length; }
針對是不是一個合格的單詞,我寫了一個isWord()方法來判斷
public boolean isWord(String input) { // 判斷是不是符合標準的單詞 if(input.length() < 4) return false; for(int i = 0; i < 4; i++) { if(isNotDigitOrAlpha(input.charAt(i)) || !Character.isLowerCase(input.charAt(i))) return false; } for(int i = 4; i < input.length(); i++) { if(isNotDigitOrAlpha(input.charAt(i))) return false; } return true; }
在計數時,我使用了HashMap,方便存取,針對進階需求,則設置了權值,能夠經過addWeight()方法設置的權值隊列並從中獲取,這裏涉及了另外一塊代碼WordHeap。
int countWords(String input, HashMap<String, Integer> wordMap, String type) { // 統計單詞數,並存入哈希表 int result = 0; String word; int cut; int weight = 1; // 初始權重爲一 if (!type.equals("")) { int i = weightTable.isExist(type); // 判斷權值表中是否含有此類,有則替換權值。 if (i != -1) { weight = weightTable.heap.get(i).value; } } input = input.toLowerCase(); // 轉化爲小寫 cut = cutWords(input); while (cut != input.length()) { word = input.substring(0, cut); input = input.substring(cut); if (isWord(word)) { addMap(word, weight, wordMap); result++; } cut = cutSign(input); input = input.substring(cut); cut = cutWords(input); } word = input; if (isWord(word)) // 防止最後一個單詞漏讀 { addMap(word, weight, wordMap); result++; } return result; } }
void addWeight(String type, Integer value) {// 添加類型權重 type = type.toLowerCase(); // 所有替換成小寫 int index = weightTable.isExist(type); // 判斷原先是否有權重記錄,若是有則更新,沒有則添加。 if (index == -1) weightTable.add(type, value); else weightTable.heap.get(index).value = value; }
統計完全部的單詞後,會進行依照單詞的權值進行排序,這裏我選擇了最大堆,並依照了鍵值對的思想,構建了一個wordValue類,來存儲鍵值,至於堆的實現,大體就是造輪子。。。
private void wordClassify(HashMap<String, Integer> wordMap, wordHeap wh) // 單詞歸類,將哈希表中的單詞納入詞堆(最大堆) { String word; java.util.Iterator<Entry<String, Integer>> iter = wordMap.entrySet().iterator(); // 鍵值對遍歷哈希表 Integer value; while (iter.hasNext()) { Entry<String, Integer> entry = iter.next(); word = entry.getKey(); // 獲取key value = entry.getValue(); // 獲取value wh.insert(word, value); } }
static class wordValue // 鍵值對 { public String word; public Integer value; wordValue(String word, Integer value) { this.word = word; this.value = value; } }
public boolean compare(int a, int b) // 比較兩個鍵值對的順序,返回ture則a在前 { if(heap.get(a).value > heap.get(b).value) // 比較值 return true; else if(heap.get(a).value.equals(heap.get(b).value)) { int result = heap.get(a).word.compareTo(heap.get(b).word); //比較字典順序 return result <= 0; }
public void insert(String word, Integer value) // 插入鍵值對 { //在數組尾部添加,且注意下標爲0的位置不放元素 wordValue wv = new wordValue(word, value); if(heap.size()==0) add("", -1); heap.add(wv); heapUp(heap.size() - 1); } private void heapUp(int index) //上浮操做 { if(index > 1) { // 求出其父親節點 int parent = index / 2; // 若是父親節點的值小於index節點的值,交換二者的位置 if(compare(index, parent)) { Collections.swap(heap, parent, index); heapUp(parent); } } } void delete() // 刪除鍵值對 { heap.set(1, heap.get(heap.size() - 1)); //把最後的一個葉子的數值賦值給index位置 heapDown(1); heap.remove(heap.size() - 1); // 移除 } private void heapDown(int index) // 下沉操做 { // 由於第一個位置不存放數據,不考慮在內,最後一個也要刪除,不考慮在內 int n = heap.size()-2; //記錄較大的兒子的位置 int child = -1; if(2 * index>n) { //2*index>n 說明該節點沒有左右兒子節點了,則返回 return; } else if (2 * index < n) { //兩個兒子都在 child = 2*index; if(!compare(child, child + 1)) { child++; } } else if(2 * index == n) { //只有左兒子 child = 2 * index; } //交換和遞歸 if(compare(child, index)) { Collections.swap(heap, child, index); heapDown(child); } }
這塊部分是根據進階需求進行部分修改(值得一提的提交的代碼這塊地方是有點問題的,在符合規範爬取的文件中不會報錯,可是若是調用某些不符合規範的文件將出現越界的狀況————現告知搭檔並已修改)
修改以前
String content; // 利用readline()函數讀取每一行的值 while ((content = br.readLine()) != null) { String type = ""; if (!all) { int index = counter.cutWords(content); // 調用count類的單詞分割,將第一個單詞分割出來 if (index == content.length())//修改成index + 2 >= content.length() continue; type = content.toLowerCase().substring(0, counter.cutWords(content)); content = content.substring(index + 2); // 冒號與空格不計(若不修改此處在不符合規範的文件輸入中可能越界,符合規範的文件輸入則不受影響) if (!isWord(type))//已刪除,緣由:規範文件輸入狀況下的多餘判斷 continue; } characters += counter.countCharacters(content, true); // 字符計數 lines += counter.countLines(content); // 有效行計數 words += counter.countWords(content, wordMap, type); // 單詞計數 }
修改以後
String content; // 利用readline()函數讀取每一行的值 while ((content = br.readLine()) != null) { String type = ""; if (!all) { int index = counter.cutWords(content); // 調用count類的單詞分割,將第一個單詞分割出來 if ((index + 2) >= content.length()) continue; type = content.toLowerCase().substring(0, counter.cutWords(content)); content = content.substring(index + 2); // 冒號與空格不計 } characters += counter.countCharacters(content, true); // 字符計數 lines += counter.countLines(content); // 有效行計數 words += counter.countWords(content, wordMap, type); // 單詞計數 }
接口
public void countWords(String fileName, int top, boolean all) throws IOException // top爲前幾的詞頻 all爲是否爲基礎需求
WordCount 基本需求——單元測試
我對單元測試實際上是小白。。。因此此次的單元測試基本都是跟在隊友屁股後面學東西,本身的代碼也都是他幫忙測試的,我基本都是看他測試並提出本身的預期,就比較臭不要臉的從他那裏拿測試代碼了。。。
@Test public void testIsNotDigitOrAlpha() { for(int i = 0; i < 26; i++) { assertFalse(String.format("%c: ", 'A' + i), c.isNotDigitOrAlpha((char)('A' + i))); assertFalse(String.format("%c: ", 'a' + i), c.isNotDigitOrAlpha((char)('a' + i))); } for(int i = 0; i < 9; i++) { assertFalse(String.format("%c: ", '0' + i), c.isNotDigitOrAlpha((char)('0' + i))); } assertTrue("â: ", c.isNotDigitOrAlpha('â')); assertTrue("é: ", c.isNotDigitOrAlpha('é')); }
上述方法是判斷一個字符是不是非字母數字字符。因而測試用例就覆蓋了全部的英文字母和數字,至於最後兩個法文字母,是從爬蟲的結果裏找到了,將其做爲一個非英文字母的用例。
@Test public void testIsWord() { assertTrue(c.isWord("apple")); assertFalse(c.isWord("foo")); assertFalse(c.isWord("123foo")); assertFalse(c.isWord("Café")); assertTrue(c.isWord("file123")); assertFalse(c.isWord("Mâché")); }
剛纔那個例子,裏面的狀況是能夠列舉的,而上面的被測方法是判斷一個字符串(僅由小寫字母和數字構成)是不是合法的單詞,傳入這個方法的字符串是已經被分割符分割並所有轉成小寫後的單詞,因此以上的樣例大概能覆蓋到各類狀況。
評價
對本身的評價
我吧,一開始接到任務的時候,也沒有和隊友作好溝通和交流,雖然本身大體完成了本身的工做,並實現了進階的一部分接口,可是不少地方其實有些不夠,包括一些地方沒有考慮周全,致使若是出現拓展需求的話可能須要重構一部分代碼。。。仍是本身經驗太欠缺。須要多學習。
對隊友的評價
我很是感謝個人隊友,由於我本身的基礎實際上是有問題的,對於項目的理解不少都還不夠,好比Git的使用,好比單元測試,還有對於方法的構建的合理性,這些不少都是個人隊友跟在我身後教個人,並且並無嫌棄我拖後腿。。。他對於庫的瞭解要比我多不少,好比我想獲得一個方法,我第一反應是本身寫,而他更傾向從現有的庫中去找,這無疑減小了bug的可能性。