结对第二次—文献摘要热词统计及进阶需求

课程链接:软件工程1916|W(福州大学)
作业要求:结对第二次—文献摘要热词统计及进阶需求
结对学号:221600205 | 221600207
作业目标1:一、基本需求:实现一个能够对文本文件中的单词的词频进行统计的控制台程序。
作业目标2:二、进阶需求:在基本需求实现的基础上,编码实现顶会热词统计器

团队分工:###

黄权焕:
1.主要代码实现
2.需求分析讨论
3.博客撰写
4.代码测试

陈红宝:
1.爬虫代码实现
2.需求分析讨论
3.博客撰写
4.代码测试

作业正文##

一、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 |

二、解题思路描述###

说太多的解题思路描述,反倒像是事后诸葛亮。我就着实的描述一下当时的情况。####

记得开始工作的时候是上周六(3月9号)。周五晚上发布作业,大概看了一下需求。从感觉上来看,有些多,也有些复杂。
于是,周六早上和下午完成其他工作后,就开始讨论本次作业了。
但是,问题发生了!我的Eclipse用不了,要重新配置环境变量。跟着百度上的教程,一步一步的走,但总是解决一个错误又出现另外一个错误。期间,环境变量的配置方式都用了三种。花费一个晚上的时间,最后所有东西都卸载重装,问题终于解决了!顿时松了一口气。
但问题是,我的同伴,在这个晚上,已经把基础需求代码全部写出来了。
就像那句话怎么说:时间是讲公平的,当你忙于一件事情的时候,有人已经把另外一件事情忙完了!
有些惭愧,因为没有太多的讨论,以及对他有任何帮助!他一个人默默的完成了!
之后的一天也就是上周日(3月10号)晚上。我做的事情是理解同伴编写的代码。同时也理解作业的需求。
有很多收获,也发现了一些问题,之后会一一道来。

三、设计实现过程###

基础需求实现####

(1)需求分析####

第一步、实现基本功能####

1.统计文件的字符数:####

- 只需要统计Ascii码,汉字不需考虑####

- 空格,水平制表符,换行符,均算字符####

2.统计文件的单词总数,单词:至少以4个英文字母开头,跟上字母数字符号,单词以分隔符分割,不区分大小写。

英文字母: A-Z,a-z####

字母数字符号:A-Z, a-z,0-9####

分割符:空格,非字母数字符号####

例:file123是一个单词,123file不是一个单词。file,File和FILE是同一个单词####

3.统计文件的有效行数:任何包含非空白字符的行,都需要统计。####

4.统计文件中各单词的出现次数,最终只输出频率最高的10个。频率相同的单词,优先输出字典序靠前的单词。####

5.按照字典序输出到文件result.txt:例如,windows95,windows98和windows2000同时出现时,则先输出windows2000

输出的单词统一为小写格式####

分析:从宏观上来说,就是输入一个文件的内容,然后截取以4个英文字母开头及以上的单词,输出。并统计单词总数,有效行数,各单词的出现次数。####

然后,细分下来就是实现了!####

实现讲解:####

1.从文件中读取字符流到缓冲区,判断缓冲区的每一行字符,切割字符串(去掉非字母非数字的符号),将切割出来的字符串保存到字符数组中,并记录有效行
2.将字符数组中的字符串转换为小写格式(方便之后判断和输出)。循环判断字数组符中的字符串前4个字符是否是小写字母。如果是,则保存到哈希表中(方便字典序排序),并增加单词数。如果不是,则舍去该字符串。
3.有哈希表中存储的字符串,可以方便的输出单词和词频。
4.算法思路:
获取行数
将文件打开后,用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可以按插入顺序保存。

进阶需求

自定义输入输出
在类中额外保存输入输出名即可。

自定义词频统计输出
在类中额外保存一个最大单词数用来控制LinkedHashMap长度即可。

权重分析
在HashMap插值时,额外判断是否来自Title,是的话记录数+10,否则+1即可。

多参数的混合使用
读取一行,依旧用split(“-“)函数分割成不同指令,分别调用函数即可。

从文件中得到字符串####

	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 
			e.printStackTrace();
		} catch (IOException e) {
			// TODO
			e.printStackTrace();
		}
	}
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(6, fContent.length()-1 );
						fByteCount += fContent.length();//
						setMap(fContent,true);
					}
					else if(fContent.charAt(0) == 'A')
					{
						fContent = fContent.substring(9, fContent.length()-1 );//remove(Abstract: ) 
						fByteCount += fContent.length();//
						setMap(fContent,false);
					}
				}
			}
			fis.close();
		} catch (FileNotFoundException e) {
			System.out.print("");
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

判断字符是否符合要求####

	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]);
					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);
				}
			}
		}
	}

哈希表排序(方便输出字典序)####

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;
	}

代码优化和接口封装是同伴细化处理的,一眼看去就工整清秀。####

	public lib()
	{
		fileInput = "cvpr/result.txt";
	}
	public lib(String fName)
	{
		fileInput = fName;
		setWord();
	}
	public void setFileInput(String fName)
	{
		fileInput = fName;
	}
	public void setFileOutput(String fName)
	{
		fileOutput = fName;
	}
	public void setWValue(int num)
	{
		wValue = num > 0 ? true : false;
	}
	public void setMaxWordNum(int num)
	{
		if(num >= 0)	maxWordNum = num;
	}
	public int getFWordCount()
	{
		return fWordCount;
	}
	public int getFRowCount()
	{
		return fRowCount;
	}
	public int getfByteCount()
	{
		return fByteCount;
	}
	public int getMaxWordNum()
	{
		return maxWordNum;
	}
	public int getMaxWordNum(int num)
	{
		return maxWordNum = num;
	}
	public boolean isLower(char c)
	{
		return (c>='a' && c<='z');
	}
	public boolean isDigit(char c)
	{
		return c<='0' && c<='9';
	}

爬虫实现####

爬虫实现是我自己一步一步的,从没听说过,到查资料,写代码,最后是输出 result.txt文件,用了三个晚上加一个通宵,和其他一些零零散散的时间。
怎么说呢!还是先描述一下学习的过程吧!因为用得时间多,学得也多嘛!
周一(3月11日)晚上,开始学习爬虫之旅。有些惶恐,也有些期待。当时觉得这个爬虫技术学会了,以后会有大用处。就像一个朋友开玩笑说:“学了爬虫,以后都可以自己上起点小说网爬取小说内容了!”
我记得一开始百度搜索的时候,爬虫软件很多,我下载了一个叫“后羿”的爬虫软件,不过没用上,因为要学习后自己编码!
学习的过程,大多是重复历史。从网上找一些具体的代码例子,看看别人怎么写代码,又实现了哪些功能。
而我学习的过程,借鉴了五份代码。都是一份一份的从网上查找,选取感觉适合的。然后一边敲写,一边理解。
第一份:关于URL链接输出HTML全部信息。从这份中,我学到了URL链接以及获取网页html的内容。
第二份:从一段字符串中,使用正则表达式截取电话号码。(当时还不知道正则表达式,只记得搜索相关资料的时候看见网上有人表示:“正则表达式写错了一点点,后面就全部错了。”于是,谨慎细微,不敢擅自修改样例中的正则表达式。但最后在自己理解正则表达式后发现,这好像也不是当时想的那么难的。)
第三份:从单个网页中截取需要的信息(用正则表达式)。当时在里的时候,就是很谨慎的,正则表达式和对应网页上的信息看了一遍又一遍,勉强理解了,但几乎不会用。直到把正则表达式的规则看了许多遍,才豁然开朗。
第四份:是关于读取从第一个页面出发,然后读取很多页面的链接以及链接里的内容。这份样例代码不多,但当时理解起来很繁琐,头都要炸了
第五份:和第三份一样,是从单个网页中截取需要的信息。我最后完成的代码,大部分格式都是模仿这一份的。
完成篇:代码爬取出论文后,依旧还有两个改进,这个下面优化的时候会讲。

爬虫实现:####

1.获取一个页面上的标题和摘要信息####

	    private static String  htmlFiter(String html,int num) 
	    {
	    	StringBuffer buffer = new StringBuffer();
	      
	    	String str1= "";
	    	String str2= "";	    	
	    		// 匹配Title(题目),题目被包含在<div id="papertitle"> 和 </div>中
	    		Pattern p1 = Pattern.compile("(<div id=\"papertitle\">)(.+?)(</div>*)");    		
	    		Matcher m1 = p1.matcher(html);  
	    		// 匹配Abstract(摘要),摘要被包含在<div id="abstract"> 和 </div>中
	    		Pattern p2 = Pattern.compile("(\"abstract\")(.+?)(</div>*)");
	    		Matcher m2 = p2.matcher(html);
	    		if(m1.find() && m2.find()) 
	    		{
	    			
	    			str1 = m1.group(2);
	    			str1 = num+"\r\n"+"Title: "+str1+"\r\n";
	    	//		buffer.append("\nTitle: ");
	    //			buffer.append(str1);
	    			str2 = m2.group(2);
	    			str2 = str2.replace(">","");
	    			str2 = "Abstract:"+str2+"\r\n"+"\r\n";
	   // 			buffer.append("\nAbstract: ");
	    //			buffer.append(str2);
	    //			buffer.append("\n");
	    			
  			try
  			{
      			 File file = new File("D:\\result.txt");               			 
      			 FileWriter fw = new FileWriter(file,true);
      			 String str5 = str1;
      			 fw.write(str5);
      			 String str6 = str2 + System.getProperty("line.separator");
      			 fw.write(str6);
                   
                   fw.close();
  			}catch(Exception e) {
  				e.printStackTrace();
  			}
	    		}   	
	    	return buffer.toString();
	    }

2.获取主页上论文的链接信息。调用第一个类,接入链接信息,爬取页面上的标题和摘要信息。并保存到result.txt文档中####

	    private static String  htmlFiter(String html,int num) 
	    {
	    	StringBuffer buffer = new StringBuffer();
	    	pre_2 p2 = new pre_2();
	    	
	    	String str = null;
	    	Pattern p = Pattern.compile("(<div id=\"content\">)(.*)(</div>*)");    		
   		Matcher m = p.matcher(html);    		
   		if(m.find()) 
   		{
   			str = m.group(2);
   			String str1 = null;
   			String str2 = null;
   	    	Pattern p1 = Pattern.compile("(class=\"ptitle\">)(.+?)(a href=\")(content_cvpr_2018)(.+?)(_2018_paper.html\">)");    		
       		Matcher m1 = p1.matcher(str);
    
       		while(m1.find())
       		{
       	    	
       			str1 = m1.group(5);
       			str2 = ("http://openaccess.thecvf.com/content_cvpr_2018"+str1+"_2018_paper.html");
       			buffer.append(str2);
       			buffer.append("\n");
       			p2.getTodayTemperatureInfo(str2,num); 
       			num++;
       		}
   		}	 				
	    	return buffer.toString();
	    }

3.主函数####

public class pre_main 
{
	pre_1 p1 = new pre_1();
	String info = p1.getTodayTemperatureInfo("http://openaccess.thecvf.com/CVPR2018.py");
}

感言:####

就像一道门,在你走进去之前,你会胡乱猜测甚至害怕。因为你不知道门里面是什么,里面对于你来说是黑漆漆的一片。而人生来就对未知的东西有着期待和恐惧。
待你真正走进这道门的时候,你感叹一声:“原来如此。”
当时我完成截取论文的爬虫代码的时候,用了三个晚上加一个通宵。但现在,你让我再爬取难度相似的文档时,我只要十分钟。这大概就是师傅领进门的重要性吧!

四、改进的思路###

(1)当时,我开始完成爬虫代码的时候,一共有两个程序。第一个:爬取主页中,关于论文链接的链接地址,保存到一个名叫 1.txt 文档中。第二个:从 1.txt 文档中,获取链接地址,再从相应的地址获取需要的论文标题和内容信息,保存到result.txt。
第一次完成的时候,截取的链接地址有两个不符合要求,改了很多次正则表达式,甚至把对的改错了!最后发现的问题是:有两个链接的地址中后面的 “CVPR”是小写,其他的是大写。发现这个的时候很开心,因为究竟了许久,最后在逐字对照正确爬取和错误爬取链接的时候发现。
(2)已经爬取了result.txt 后,还有一个问题是,会产生一个1.txt 文件。于是便需要优化,优化便是把上面两个程序都封装成包,最后在主程序中调用,既解决了产生多余 1.txt 的要求,也满足了测试要求。

正确爬取结果####

五、关键代码####

1.从文件中读取字符流到缓冲区,判断缓冲区的每一行字符,切割字符串(去掉非字母非数字的符号),将切割出来的字符串保存到字符数组中,并记录有效行####

2.将字符数组中的字符串转换为小写格式(方便之后判断和输出)。循环判断字数组符中的字符串前4个字符是否是小写字母。如果是,则保存到哈希表中(方便字典序排序),并增加单词数。如果不是,则舍去该字符串。####

3.有哈希表中存储的字符串,可以方便的输出单词和词频。####

从文件中得到字符串####

	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 
			e.printStackTrace();
		} catch (IOException e) {
			// TODO
			e.printStackTrace();
		}
	}
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(6, fContent.length()-1 );
						fByteCount += fContent.length();//
						setMap(fContent,true);
					}
					else if(fContent.charAt(0) == 'A')
					{
						fContent = fContent.substring(9, fContent.length()-1 );//remove(Abstract: ) 
						fByteCount += fContent.length();//
						setMap(fContent,false);
					}
				}
			}
			fis.close();
		} catch (FileNotFoundException e) {
			System.out.print("");
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

判断字符是否符合要求####

	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]);
					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);
				}
			}
		}
	}

哈希表排序(方便输出字典序)####

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;
	}

六、关于队友####

队友很强!我也会努力!####

队友在代码实现和优化方面,很值得我学习。就比如说,在基础需求的字典排序中,我还在思考是不是用字符数组保存字符串后,比较相同字符串,然后进行字典排序。这样的方法,想想就很麻烦!而队友已经想到用哈希表保存数据,无论是字典安排序和输出,都很方便。
其次,这次作业在两人的沟通上远不如上一次,作业类型不同是一个原因。还有原因是,我得加强代码能力,希望能做到和队友基本同步。

七、总结####

在一番辛勤劳动以及一份充硕的收获之后,感悟颇多!
当时在阅读理解同伴的基础需求代码上就有一些想法,现在补上。
大学已经三年了!过去的时光零零散散,孤独的,寂寞的,大笑的,啜泣的。无论以前如何,现在都需要将心境沉浸下来了!而如今,有一个优秀的同伴,我很幸运。就像同样学过数据结构,同伴能灵活运用,而我还是处于老旧的思想。我要学习这样的思维方法,这一次的作业,是一个好的开端,希望能继续下去。
心里的感悟颇多,但到了嘴边,好像一切都简单了!
简单便简单罢!再说一说自学的感受。当一座你恐惧的高山被你踩再脚下时,再看看四周,便有“会当凌绝顶,一览众山小”的豪情,心中无限舒畅。就算当初学习过程中,陷入困境时,也曾无奈。挣扎着,想要抓住什么当作救命稻草。会发现,能依靠的有朋友,有自己。
当匆忙时,会过得很充实。偶尔停下脚步,会发现,这一刻以前忽略了的闲暇,是那么的美好舒服。

八、发现问题####

总结了收获,也有一些问题发现。
第一个是关于交流讨论和实践编码的
上一次作业中,交流讨论的时间很多,也从讨论中明确了工作的需求和目的。在实际工作的时候,可以引用一个成语就是:胸有成竹。
但这次作业中,一但涉及编写代码,讨论的时间就会减少很多。而且当同伴写出代码后,就不知道从哪里插手,好像自己修改就会破坏了同伴代码的完整性。
这里需要助教老师给我们解答一下疑惑。
第二个是关于github使用的
老实说,因为以前没有接触过,而且是全英文的。根据老师的作业要求,fork 了链接,新建文件夹以及Pull Request ,等等操作后,不知道是否正确。我当时是建立了一个文件,然后把完成的代码复制粘贴上去,选择了new Pull Request。但后面又经过同学的讲解,好像要下载一个github,然后克隆文件什么的。操作起来依旧是不知方向。
而我想向老师提议一下,如果下次有什么新的网站啊,还是软件什么需要用到。能不能像很多百度指导一样,给一些图片加上红色箭头标记,这样能让我们快速的熟悉运用这些网站。也许在熟练的使用者眼里,这只是简单的操作,但对于初学者来说,是莫大的帮助了!

posted @ 2019-03-15 20:07  星夜、痕  阅读(210)  评论(2编辑  收藏  举报