結對第二次—文獻摘要熱詞統計及進階需求
課程鏈接 | 軟件工程1916|W(福州大學) |
作業要求 | 結對第二次—文獻摘要熱詞統計及進階需求 |
結對學號 | 221600207|221600205 |
作業目標 | 本次作業分為兩部分: 一、基本需求:實現一個能夠對文本文件中的單詞的詞頻進行統計的控制台程序。 二、進階需求:在基本需求實現的基礎上,編碼實現頂會熱詞統計器 |
作業正文 | 作業正文 |
基礎需求 | WordCount |
進階需求 | WordCountPro |
提交截圖如下
一、寫在前面
本此作業中進階需求里的-m功能尚未完成,基本思路為將讀取的String分割為單詞,創建數組並依次記錄單詞出現的下標,非法單詞記錄下標為-1。即String="key,hello word"對應數組為a[0]=0;a[1]=-1;a[2]=4;a[3]=10;取m=2時則取出subString(a[2],a[3]+a[3].length);
二、團隊分工
221600207黃權煥
- 完成基礎需求src目錄下的全部代碼
- 完成進階需求src目錄下的全部代碼
- 編寫博客
- 軟件測試
221600205陳紅寶
- 完成進階需求cvpr目錄下全部代碼
- 編寫博客
- 軟件測試
三、PSP 表格:
PSP2.1 | Personal Software Process Stages | 預估耗時(分鍾) | 實際耗時(分鍾) |
---|---|---|---|
Planning | 計划 | ||
• Estimate | • 估計這個任務需要多少時間 | 900 | |
Development | 開發 | ||
• Analysis | • 需求分析 (包括學習新技術) | 120 | 600 |
• Design Spec | • 生成設計文檔 | 60 | 60 |
• Design Review | • 設計復審 | 30 | 60 |
• Coding Standard | • 代碼規范 (為目前的開發制定合適的規范) | 30 | 30 |
• Design | • 具體設計 | 120 | 200 |
• Coding | • 具體編碼 | 600 | 900 |
• Code Review | • 代碼復審 | ||
• Test | • 測試(自我測試,修改代碼,提交修改) | 30 | 120 |
Reporting | 報告 | 60 | 150 |
• Test Repor | • 測試報告 | 30 | 30 |
• Size Measurement | • 計算工作量 | 30 | 30 |
•Postmortem & Process Improvement Plan | • 事后總結, 並提出過程改進計划 | 30 | 30 |
|合計 | 1110 |2210 |
四、解題思路
1.基礎需求
獲取行數
將文件打開后,用readLine()函數逐行讀取文本內容並保存在fContent上,此時疊加行數。
獲取字符數
將字符串fContent讀取成功后,字符數+=fContent.length;
獲取單詞數
將fContent使用split("\W+")分割成只有可寫字符的單詞組存入String [] ch中,單詞數+=ch.length;
數據結構
使用HashMap保存單詞和使用頻率,不使用TreeMap的原因是,TreeMap沒有自帶按值排序后,相同值按字典序排序的特性。而HashMap可以使存儲、查找的時間效率都在O(1)內完成,而不是TreeMap的log(N);
詞頻排序
值得注意的是,Map本身排序需要轉化成List,排序成功后應將結果應重新轉化為LinkedHashMap。
LinkedHashMap可以按插入順序保存,方便后續使用。時間復雜度N*log(N);
2.進階需求
自定義輸入輸出
在類中額外保存輸入輸出名即可。
自定義詞頻統計輸出
在類中額外保存一個最大單詞數用來控制LinkedHashMap長度即可。
權重分析
在HashMap插值時,額外判斷是否來自Title,是的話記錄數+10,否則+1即可。
詞組詞頻統計功能
詞組詞頻統計分析由於時間關系尚未實現,思路在寫在前面已介紹過。將讀取的String分割為單詞,創建數組並依次記錄單詞出現的下標,非法單詞記錄下標為-1。即String="key,hello word"對應數組為a[0]=0;a[1]=-1;a[2]=4;a[3]=10;取m=2時則取出subString(a[2],a[3]+a[3].length);取m=1時則取出subString(a[2],a[2]+a[2].length)等。
多參數的混合使用
讀取一行,依舊用split("-")函數分割成不同指令,分別調用函數即可。
3.爬蟲(由我隊友00205完成)
諸位還是到我隊友文章查看,由於本人無法總結,且他文章字數太多,我不再贅述。221600205
五、代碼組織與測試
lin.java完成函數的封裝。其中setWord接受文件名,調用setMap,調用isLow;getWord輸出文件,調用sortMap。
Main.java進行調用和設置lib類的參數。
類圖如下
存放時的流程圖如下
單元測試圖
單元測試代碼
import static org.junit.Assert.*;
import org.junit.Test;
public class libTest {
@Test
public void testSetFileInput() {
lib b = new lib();
b.setFileInput("input.txt");
assertEquals("input.txt",b.getFileInput());
}
@Test
public void testSetFileOutput() {
lib b = new lib();
b.setFileOutput("output.txt");
assertEquals("output.txt",b.getFileOutput());
}
@Test
public void testSetWValue() {
lib b = new lib();
b.setWValue(3);
assertEquals(true,b.getWValue());
}
@Test
public void testSetMValue() {
lib b = new lib();
b.setMValue(5);
assertEquals(5,b.getMValue());
}
@Test
public void testSetMaxWordNum() {
lib b = new lib();
b.setMaxWordNum(5);
assertEquals(5,b.getMaxWordNum());
}
@Test
public void testGetFWordCount() {
lib b = new lib("input.txt");
b.setWord();
assertEquals(9,b.getFWordCount());
}
@Test
public void testGetFRowCount() {
lib b = new lib("input.txt");
b.setWord();
assertEquals(2,b.getFRowCount());
}
@Test
public void testGetfByteCount() {
lib b = new lib("input.txt");
b.setWord();
b.getWord();
assertEquals(74,b.getfByteCount());
}
@Test
public void testGetMaxWordNum() {
lib b = new lib("input.txt");
b.setMaxWordNum(12);
assertEquals(12,b.getMaxWordNum());
}
@Test
public void testIsLower() {
lib b = new lib();
assertEquals(false,b.isLower('A'));
}
@Test
public void testIsDigit() {
lib b = new lib();
assertEquals(true,b.isDigit('0'));
}
}
六、關鍵代碼實現
1.類的數據字段
public class lib {
private int fWordCount = 0;//字詞數
private int fRowCount = 0;//行數
private int fByteCount = 0;//字節數
private int maxWordNum = 10;//詞頻輸出數
private String fileInput= "cvpr/result.txt";
private String fileOutput= "src/output.txt";
private boolean wValue = false;//-w 值
private int mValue = 1;//-m 值
private HashMap<String,Integer> map = new HashMap<String,Integer>();//存放字詞處
//……
}
2.讀入文檔並統計
public void setWord()
{
try {
String fContent = "";
FileInputStream fis = new FileInputStream(fileInput);
InputStreamReader isr = new InputStreamReader(fis);
BufferedReader br = new BufferedReader(isr);
fWordCount = fByteCount = fRowCount = 0;
while ((fContent = br.readLine()) != null) {
if(fContent.length() > 3)//排除空行和編號
{
fRowCount ++;
if(fContent.charAt(0) == 'T')
{
fContent = fContent.substring(7, fContent.length()-1 );//remove(Title:)
//換行+1
fByteCount += fContent.length()+1;
if(mValue >= 1 )
setMap(fContent,true);
else
setMapPro(fContent,true);
}
else if(fContent.charAt(0) == 'A')
{
fContent = fContent.substring(9, fContent.length()-1 );//remove(Abstract: )
//換行+1
fByteCount += fContent.length()+1;
if(mValue >= 1 )
setMap(fContent,false);
else
setMapPro(fContent,false);
}
}
}
fis.close();
} catch (FileNotFoundException e) {
System.out.print("文件不存在");
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
3、根據字符串分割成單詞並保存
public void setMap(String fContent,boolean isTitle)
{
String [] ch = fContent.split("\\W+");
for(int i = 0; i< ch.length ;i++)
{
if(ch[i].length()>=4)
{
ch[i] = ch[i].toLowerCase();
if (isLower(ch[i].charAt(0)) && isLower(ch[i].charAt(1)) && isLower(ch[i].charAt(2)) && isLower(ch[i].charAt(3)) )
{
//System.out.print(ch[i]);
//新增紀錄或者記錄數+1
fWordCount ++;
if( map.containsKey(ch[i]) )
map.put(ch[i],(wValue & isTitle) ? map.get(ch[i])+10 : map.get(ch[i])+1);
else
map.put(ch[i], (wValue & isTitle) ? 10 : 1);
}
}
}
}
4.Map排序
public LinkedHashMap<String,Integer> sortMap(int num)
{
List<Map.Entry<String,Integer>> list = new ArrayList<Map.Entry<String,Integer>>(map.entrySet());
Collections.sort(list, new Comparator<Map.Entry<String, Integer>>() {
public int compare(Map.Entry<String, Integer> o1, Map.Entry<String, Integer> o2) {
return o2.getValue() != o1.getValue() ? (o2.getValue() - o1.getValue()) : (o1.getKey()).toString().compareTo(o2.getKey());
//return (o1.getKey()).toString().compareTo(o2.getKey());
}
});
LinkedHashMap<String,Integer> tmp = new LinkedHashMap<String,Integer>();
for (int i = 0; i < list.size() && i< num; i++) {
String id = list.get(i).toString();
Integer value = list.get(i).getValue();
tmp.put(id, value);
//System.out.println(id + (value));
}
return tmp;
}
5.文檔輸出(可自定義)
public void getWord()
{
LinkedHashMap<String,Integer> list = sortMap(maxWordNum);
try {
FileOutputStream fos = new FileOutputStream(fileOutput);
OutputStreamWriter osw = new OutputStreamWriter(fos);
BufferedWriter buff = new BufferedWriter(osw);
String content = "characters: " + fByteCount + "\r\n";
content += "words: "+ getFWordCount() + "\r\n";
content += "lines: "+ fRowCount + "\r\n";
Iterator<String> iterator = list.keySet().iterator();
while (iterator.hasNext()) {
String key = iterator.next();
content += "<" + key.replace("=", ">: ") + "\r\n";
//System.out.println(key.replace("=", ">: "));
}
//System.out.print(content);
buff.write(content);
buff.flush();
fos.close();
} catch (FileNotFoundException e) {
// TODO 自動生成的 catch 塊
e.printStackTrace();
} catch (IOException e) {
// TODO 自動生成的 catch 塊
e.printStackTrace();
}
}
七、算法改進
在sortMap排序中,我使用的是java自帶的sort函數,故時間效率為N*log(N).但是我們也可以改為維護一個最大堆MaxHeap,設定堆的容量為maxWordNum,以下簡稱M。則時間效率可以改進為N*log(M),在通常情況下,M很小,log(M)約等於1,可惜java沒有現成的最大堆。
八、總結與隊友評價
本次任務時間相對緊張,像性能測試和單元測試並沒有做得很好。這次結對暴露出一些問題,我是想好的時間計划很容易被隊友的編程速度打敗。比如這次,我獨立完成了基礎需求和進階需求的大部分代碼,即便如此,我依舊是在作業發布的第五天才收到我隊友的爬蟲相關代碼。
另外不可否認的是,項目進度拖延也有我的一部分責任,盡管我是近乎每天都有跟進與催促,但效果甚微,我卻在完善和測試自己的代碼,沒有過多給與支持(可能我是不想一個人完成整個項目吧,攤手.jpg)
對於這次項目的要求,GitHub、單元測試與jProfiler的使用也花費了我不少時間,且由於最初測試時沒有使用單元測試,函數也不夠細致,導致后續的單元測試只能非常簡單,性能分析也草草結束。(這三項幾乎花了我一半的代碼時間,不能小覷)。
關於隊友,我還是認可他的學習態度的,他也因熬夜寫代碼而熬到了四點余。盡管我接觸的爬蟲是依據文檔樹查找葉節點實現的,而他是通過匹配正則而實現,但我並不清楚他這么做到底有多難,也就無從評價,還是請諸君移步至他的博客一觀。
在文章末尾,我也想借此提出疑問:一個軟件項目中,你無法准確評估隊友的編程能力高低,那么是事前分工和計划被打破的情況下,如何重新組織分工與計划呢?