第二次结对作业
结对同学的博客链接http://www.cnblogs.com/wyz0918/p/9744932.html
**作业链接 **https://edu.cnblogs.com/campus/fzu/FZUSoftwareEngineering1816W/homework/2160
Github项目地址:https://github.com/wyz0918/PairProject-Java
具体分工
雷光游: 负责爬虫实现与附加题的设计
吴宜钊: 负责词频统计,代码组织与设计,单元测试
PSP表格
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 20 | 20 |
• Estimate | • 估计这个任务需要多少时间 | 15 | 20 |
Development | 开发 | 300 | 350 |
• Analysis | • 需求分析 (包括学习新技术) | 30 | 50 |
• Design Spec | • 生成设计文档 | 20 | 20 |
• Design Review | • 设计复审 | 10 | 10 |
• Coding Standard | • 代码规范 (为目前的开发制定合适的规范) | 15 | 20 |
• Design | • 具体设计 | 30 | 40 |
• Coding | • 具体编码 | 200 | 250 |
• Code Review | • 代码复审 | 30 | 40 |
• Test | • 测试(自我测试,修改代码,提交修改) | 20 | 25 |
Reporting | 报告 | 20 | 20 |
• Test Repor | • 测试报告 | 10 | 15 |
• Size Measurement | • 计算工作量 | 20 | 40 |
• Postmortem & Process Improvement Plan | • 事后总结, 并提出过程改进计划 | 20 | 30 |
合计 | 760 | 950 |
解题思路描述与设计实现说明##
爬虫使用
- 用C++实现论文信息的爬取,主要思路:首先便是通过获取CVPR2018官网的内容,对网页内容进行分析,发现里面有关论文介绍的网页文件存储在服务器主机的/content_cvpr_2018/html/目录下,所以对应论文网页URL中的文件路径均以此开头以.html结尾。从网页上提取相关URL存入队列就可以得到每编论文介绍网页的URl。最后遍历队列去浏览每个URL,从URL中获取的网页内容里会有title和abstract的内容,从网页内容中匹配相关信息保存到文件result.txt中即可。以上内容涉及编程的实现过程中用到了WinSock编程、sscanf字符串匹配的使用、文件的操作知识。
代码组织与内部实现设计###
一、相关类设计
- 一个Main类,用于处理文件输入输出,Main类对象直接调用Lib类中的方法。
- 一个Lib类作为工具类,用于计算字符数、行数、单词总数、单词频率、词组频率等静态方法。
二、相关函数设计
-
1、Main类中的方法
- readFile方法实现从控制端口读入文件名,获得相关命令行参数。
- writeFile方法实现把信息按照指定格式写入指定文件。
-
2、Lib类中的方法
- Preprocess方法实现预处理,计算出行数以及字符数,将Title和Abstract中的单词分开存放。
- wordCount方法实现单词词频相关计算,包括Title和Abstract中的单词。
- phraseCount方法实现词组词频相关计算,包括Title和Abstract中的词组。
- listSort()方法实现hashMap中的单词/词组排序
三、类图
算法的关键与关键实现部分流程图###
算法的关键
- 我认为算法的关键在于实现词组词频统计。在上一个workOut版本中,已经实现了基础的功能,只需要稍微调整即可用于当前版本。而除了排序可以重用外,词频统计功能应该算得上是一个全新的功能,不仅需要考虑词组的格式,也需要检索出所有词组。
流程图
附加题设计与展示
设计的创意独到之处
- 通过爬取论文信息,提取出标题、作者和摘要。可以统计出每位作者发表的论文数量,对每个作者发表论文数进行排序,可以得出前50位发表论文数量最多的作者。在这50位作者中,通过爬取到的数据进行分析,可以查看到这50位论文发表数量最多的作者之间的合作关系。通过最后产生的一个合作关系图谱,如果合作关系密切,说明多次合作经验和可以成功发表论文是有关系的,也就是说合作经验越足,他们的论文往往会被成功发表。而如果合作关系稀疏,也就是说和不同的人合作,涉及广泛,那么也就有更多新鲜的合作经历,新颖点也会越多,这样的论文发表的成功性更大。
实现思路
- 首选便是对网页论文提取不仅仅涉及标头、摘要,还有作者,只要根据对应的关键字进行匹配,就可以提取到相关信息。在提取生成的result.txt文件里,通过遍历文件,将作者名字作为key,Authors行中其出现的次数作为values值加入Treemap中,最后将Treemap根据values排序,可以得到一个作者论文发表数量降序排序的Treemap。之后保留其中前50位作者,将剩余作者移出Treemap。之后在一个继承Jpanel的类中再次遍历文件,查看作者是不是在之前的Treemap中,如果是就绘制一个点表示该作者,同时会出其名字和发表论文数。再查看其同编论文的合作伙伴是否也在Treemap中,如果是则也绘制一个点,并与之前的作者之间绘一条线,表示两人有合作关系,这样如果一个点引出的线越多,其与别人合作越多,最后就绘出了整个发表论文数量前50名作者之间的合作关系。
实现成果展示
- 从图中可以看出合作关系还是挺紧密的,并且这50位作者中都有多次合作的关系。也就是说合作次数越多,那么合作经验则相对丰富,效率和成功作品的可能性都会有所提高,当然这不可一概而论。
相关执行程序附件
关键代码解释
- 词频统计代码:
词频统计中Title部分代码
//处理Title
for (int p = 0; p < titleList.size(); p++) {
tmp = 0;
count = 0;
wordArray = titleList.get(p).split("\\s*[^a-zA-Z0-9]+");
sepArray = titleList.get(p).split("[a-zA-Z0-9]+");
// 若以分隔符开头 合并时以第二个分隔符开始
if (titleList.get(p).matches("\\s*[^a-zA-Z0-9]+[\\s\\S]*")) {
tmp = 1;
}
for (int i = 0; i < wordArray.length; i++) {
if (wordArray[i].matches("[a-zA-Z]{4,}[a-zA-Z0-9]*")) {
Lib.words++;
count++;
} else {
if (count >= m) {
int tmpCount = count - m + 1;
for (int j = 0; j < tmpCount; j++) {
sb.delete(0, sb.length());
sb.append(wordArray[i - count + j]);
for (int k = 1, l = i - count + 1 + j; k < m; k++, l++) {
sb.append(sepArray[l - tmp]).append(wordArray[l]);
}
String phrase = sb.toString().toLowerCase();
if (hashMap.containsKey(phrase)) {
hashMap.put(phrase, hashMap.get(phrase) + incre);
} else {
hashMap.put(phrase, incre);
}
}
}
count = 0;
}
}
if (count >= m) {
int tmpCount = count - m + 1;
wALength = wordArray.length;
for (int j = 0; j < tmpCount; j++) {
sb.delete(0, sb.length());
sb.append(wordArray[wALength - count + j]);
for (int k = 1, l = wALength - count + 1 + j; k < m; k++, l++) {
sb.append(sepArray[l - tmp]).append(wordArray[l]);
}
String phrase = sb.toString().toLowerCase();
if (hashMap.containsKey(phrase)) {
hashMap.put(phrase, hashMap.get(phrase) + incre);
} else {
hashMap.put(phrase, incre);
}
}
}
}
- 代码解释:
在词组词频计算中,词组由单词以及分隔符共同构成,因此我采用分开单词和分隔符,最后再组合在一起的方法。分开单词的分隔符采用String.spilt()方法,组合采用StringBuffer.append()方法,因为有设定统计的词组长度m的存在,因此用count变量计数,当遇到不符合要求的单词或到达结尾,count清零,并同时开始检索符合要求的词组,加入hashMap中,其中需要注意的是合并单词与分隔符时由于位置关系需要注意考虑两种情况.
性能分析与改进
JProfiler性能分析图:
由图可知,程序中开销最大的函数是词频统计中的HashMap。
改进的思路:
- 用StringBuffer.append()代替String的“+”进行循环添加操作,这样可以避免多次创建String对象,提高性能。
- 尽量在一次br.readline中完成所有操作,避免多次读取造成时间成本增加。
单元测试##
预处理统计字符数Preprocess()的测试函数:###
@Test
public void testPreprocess() {
File f = new File("input.txt");
Lib.Preprocess(f);
assertEquals(Lib.characters,74);
assertEquals(Lib.titleList.get(0),"Monday Tuesday Wednesday Thursday");
assertEquals(Lib.abstractList.get(0),"Monday Tuesday Wednesday Thursday Friday");
}
- 构造测试数据的思路:
将input.txt作为参数输入,对照字符数以及存放Title和Abstract的数组是否符合预期的结果。
词组词频统计PhraseCount()的测试函数:###
测试一:
@Test
public void testPhraseCount() {
Lib.titleList.add("monday tuesday {thursday");
Lib.abstractList.add("tuesdaa,wednesday thursday");
Lib.abstractList.add("tuesdaa,wednesday thursday");
Lib.abstractList.add("tuesday wednesday thursday");
Lib.abstractList.add("tuesday wednesday thursday");
String[] str = {"monday tuesday {thursday","tuesdaa,wednesday thursday","tuesday wednesday thursday"};
String[] str2 = {"10","2","2"};
ArrayList<Map.Entry<String, Integer>> list = new ArrayList<Map.Entry<String, Integer>>();
Lib.phraseCount(1, 3);
Lib.listSort();
list = Lib.list;
for (int i = 0; i < 10 && i < list.size(); i++) {
assertEquals(list.get(i).getKey(),str[i]);
}
for (int i = 0; i < 10 && i < list.size(); i++) {
assertEquals(list.get(i).getValue().toString(),str2[i]);
}
}
}
测试二:
@Test
public void testPhraseCount2() {
Lib.titleList.add("monday tuesday {thursday");
Lib.abstractList.add("\"tuesdaa\",wednesday thursday");
Lib.abstractList.add("tuesdaa,wednesday thursday");
Lib.abstractList.add("tuesdaa,wednesday thursday.");
String[] str = {"monday tuesday {thursday","tuesdaa,wednesday thursday","tuesdaa\",wednesday thursday"};
String[] str2 = {"10","2","1"};
ArrayList<Map.Entry<String, Integer>> list = new ArrayList<Map.Entry<String, Integer>>();
Lib.phraseCount(1, 3);
Lib.listSort();
list = Lib.list;
for (int i = 0; i < 10 && i < list.size(); i++) {
assertEquals(list.get(i).getKey(),str[i]);
}
for (int i = 0; i < 10 && i < list.size(); i++) {
assertEquals(list.get(i).getValue().toString(),str2[i]);
}
}
}
- 构造测试数据的思路:
将w=1、m=3作为参数输入,对照List中存储的值看是否符合预期的结果,为了更加全面地测试,我检测了多种分隔符的情况 。
其他函数因为之前的作业有测试过不再写出。
Github的代码签入记录##
遇到的代码模块异常或结对困难及解决方法
主要存在的代码问题
- 由于在vs中采用C语言,会出现不安全警告的问题,使得爬虫程序无法执行,还有就是在存取网页内容时,对字符串指针的开辟、回收和使用都存在内存泄漏的问题。
解决方法
- 对于异常,先自己排查代码,如若无法察觉问题可将异常贴到网上查询,一般可以找到解决方法。其次,基础知识十分重要,对于指针的使用,要遵循开辟、回收的原则,同时可借助代码质量分析工具查出内存泄漏,指针等存在的问题。
评价你的队友
- 队友吴宜钊对于任务的处理很细心,解决问题的效率高,所以每次任务都能够提早完成。在两次结对任务中,通过讨论和交流,他对问题有新的看法和见解都会提出分享。在最新的一次结对作业中,他承担了主要的任务,解决问题和代码优化均做的很好。这是很值得我学习的,自己就常常代码超时,所以他对自己的要求与不断改善的动力都是我学习的目标。
学习进度
第N周 | 新增代码(行) | 累计代码(行) | 本周学习耗时(小时) | 累计学习耗时(小时) | 重要成长 |
---|---|---|---|---|---|
1 | 30 | 30 | 5 | 5 | 对github基本的操作,博客的编写 |
2 | 350 | 380 | 16 | 21 | 熟悉了VS对C++进行编程,以及C++对于文件的操作,集合的操作 |
3 | 0 | 0 | 15 | 36 | 学习了构建之法中模型设计的NABCD。学习了墨刀原型的设计 |
4 | 300 | 680 | 15 | 51 | 学习了C++对于网络编程的基本知识,对数据分析的摸索 |