软工实践第五次作业
写在前面
具体分工
两个人一起在实验室完成的,有什么问题都当面沟通解决+使用github多人协作的形势,一人写一部分函数然后传到github上面去,通过git pull更新本地仓库
- 吴杰婷:词数统计部分,词频统计部分,代码整合,文档撰写,单元测试,性能分析
- 许舒玲:爬虫部分,字符统计,行数统计,附加题部分,代码整合,文档撰写
PSP表格
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) |
---|---|---|
Planning | 计划 | 10 |
• Estimate | • 估计这个任务需要多少时间 | 10 |
Development | 开发 | 700 |
• Analysis | • 需求分析 (包括学习新技术) | 30 |
• Design Spec | • 生成设计文档 | 5 |
• Design Review | • 设计复审 | 5 |
• Coding Standard | • 代码规范 (为目前的开发制定合适的规范) | 10 |
• Design | • 具体设计 | 20 |
• Coding | • 具体编码 | 500 |
• Code Review | • 代码复审 | 70 |
• Test | • 测试(自我测试,修改代码,提交修改) | 60 |
Reporting | 报告 | 20 |
• Test Repor | • 测试报告 | 5 |
• Size Measurement | • 计算工作量 | 5 |
• Postmortem & Process Improvement Plan | • 事后总结, 并提出过程改进计划 | 10 |
合计 | 730 | 1014 |
解题思路描述与设计实现说明
爬虫使用
- 使用java jsoup包,jsoup最主要用到的就是的elements类和select()方法。elements类相当于网页元素中的标签,而select()方法用于按一定条件选取符合条件的标签,组成符合条件的标签数组。element支持转成字符串或者文本等。
- 打开网页查看元素-选择代码行-右键-copy-copy selector 获取便签 如://#content > dl > dt:nth-child(2935)获得链接
Document document = Jsoup.connect("http://openaccess.thecvf.com/CVPR2018.py").userAgent("Mozilla/4.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)").get(); Element main = document.getElementById("content"); //#content > dl > dt:nth-child(0) Elements url = main.select("dl").select("dt:nth-child(0)"); for(Element question:url) { String URL = question.attr("abs:href"); Document document2 = Jsoup.connect(URL).get(); //#papertitle Elements title = document2.select("#papertitle"); //#abstract Elements Abstract = document2.select("#abstract");}
代码组织与内部实现设计(类图)
说明算法的关键与关键实现部分流程图
- 计算行数(linesCount.java)
- 获取有效文本(HandleContent.java)
- 计算字符数(charsCount.java)
- 计算单词数(wordsCount.java)
- 词频统计(WordsCount.java)
附加题设计与展示
设计的创意独到之处(貌似创新性不高)
生成高频词云:由于对可视化的功能用代码实现不出来,所以这里的词云功能是直接将爬取到的数据放到网上的在线生成器中生成。
- 生成发布论文前?的单位柱状、折线图 括号中的内容为作者的单位,所以我直接通过获取()中的内容得到各个单位的名称,并放入map中进行词频统计,将得到最高的10个单位输出到文件,借助在线图表生成平台得到柱状图
实现思路
- 从网站上爬去论文的类型,作者,作者单位等。但是我在这个网站上没有找到我们之前爬取过的摘要等信息,但是通过对比,我发现这个这个网站的论文顺序和我们之前爬取的顺序是一样的。然后就直接将两个文件合并。
实现成果展示
关键代码解释
- 计算行数
- 通过匹配正则表达式判断该行是否为数字,如果是数字,则不计入
- 完全通配,并判断该行ifou为空,如果不为空,则linesCount++
while ((line = bufferedReader.readLine()) != null) { if (line.matches("\\d+")) { System.out.println(line); } else if (line.length() != 0 && !line.matches("\\s+")) { linescount++; }}
- 获取有效文本
- 按行读取,判断该行是Title开头还是Abstract开头
- 将有效字符串从7/10算起
while ((line = bufferedReader.readLine()) != null) { if (line.startsWith("Title: ")) { titles.add(line.substring(7)); stringBuilder.append(line.substring(7)); stringBuilder.append("\n"); } else if (line.startsWith("Abstract: ")) { abstracts.add(line.substring(10)); stringBuilder.append(line.substring(10)); stringBuilder.append("\n"); }}
计算字符数
正则匹配ASCIIString regex = "\\p{ASCII}"; Pattern pattern = Pattern.compile(regex); Matcher matcher = pattern.matcher(content); int sum = -1; while (matcher.find()) { sum++; } return sum;
- 计算单词数
- 正则匹配出单词
- 判断改单词是否为合法单词
int wordscount = 0;String[] temp = content.split("[\\s+\\p{Punct}]+");String countRegex = "^[a-zA-Z]{4,}.*";for (String i : temp) { if (i.matches(countRegex)) { wordscount++; }}return wordscount;
- 词频统计
- 遍历content,判断是否为合法单词,并将单词按照一定数量组成词组,将map分为titlemap和abstractmap
- 按照是否有权值不同,将titlemap和abstractmap合并
- 将map存在list当中,并重写了compare方法
for (String content : contents) {String[] temp = content.split(splitRegex);List
splits = new ArrayList<>();Matcher matcher = pattern.matcher(content);while (matcher.find()) { splits.add(matcher.group());}boolean isSplitStart = content.matches(splitStartRegex);for (int i = 0; i < temp.length - m + 1; i++) { StringBuilder stringBuilder = new StringBuilder(); if (temp[i].matches(wordRegex)) { stringBuilder.append(temp[i]); } else { continue; } boolean flag = true; for (int j = 1; j < m; j++) { if (!temp[i + j].matches(wordRegex)) { flag = false; break; } else { if (isSplitStart) { stringBuilder.append(splits.get(i + j)); } else { stringBuilder.append(splits.get(i + j - 1)); } stringBuilder.append(temp[i + j]); } } if (flag) { String words = stringBuilder.toString().toLowerCase(); if (!map.containsKey(words)) { map.put(words, 1); } else { int num = map.get(words); map.put(words, num + 1); } }} 将titlemap和abstractmap合并
for (Map.Entry
entry : titlesMap.entrySet()) { map.put(entry.getKey(), entry.getValue() * weight);}for (Map.Entry entry : abstractsMap.entrySet()) { if (!map.containsKey(entry.getKey())) { map.put(entry.getKey(), entry.getValue()); } else { int num = map.get(entry.getKey()); map.put(entry.getKey(), num + entry.getValue()); }} 单元测试
代码覆盖率:没覆盖到的主要是异常部分的
测试文本部分截图
测试结果/时间截图
性能分析
展示性能分析图
通过java虚拟机的javaConsole显示性能分析情况- 结合单元测试,可见Weighttest(权重统计)和wordtest(词组总数统计)所耗费的时间更长,原因出在权重统计时涉及了暴力排序。
Github代码签入记录
遇到的代码模块异常或结对困难及解决方法
关于github多人协作问题
- 问题描述:一开始在做的时候,不知道如何一起协作完成一份代码,并且对github多人协作的功能一头雾水。
- 做过的尝试:一开始我在网上找了资料,资料上采用的方法是添加contributors,然后创建分支,再合并分支到master的方法,但是步骤繁琐,而且我们一般是把代码整合后测试再上传,一般不会发生冲突,然后舒玲也提出了一个方法是让我folk她那边的项目,然后我提交到我本地folk的仓库再提交给她,不过本次尝试以不知道如何同意合并结束……
- 是否解决:已经解决。经过向大佬的请教,我们决定采用在舒玲的github仓库上添加contributor的方式,只用一个master分支,这样方便了我们的提交。只需要git add,git commit,git push即可
- 有何收获:一个人硬着头皮找资料的做的很容易心态爆炸,两个人一起商量显然效率很高(而且开心),掌握了几种github团队协作的开发方式!
关于代码合并问题
- 问题描述:两个人的代码风格不同,我写在接口里,然后在主函数调用。杰婷写在Class里,然后直接调用Class的某个函数。
- 做过的尝试:互相研究对方的代码,统一在Main函数调用。本来杰婷想写在我的接口里,不过引用的时候总是出错,最后直接引用我的类了……
- 是否解决:已经解决。在Main函数里调用我的接口和杰婷的类,不过有些地方需要统一,比如文件读入读出这里。
- 有何收获:统一代码规范很重要
评价你的队友
值得学习的地方
- 有耐心
- 逻辑清晰
- 代码基础好
需要改进的地方
- 反应可以快一点
- 提高效率
心得体会
在这次的作业中,比之前的作业添加了一些功能,我们在做的过程中也遇到了许多问题,我们有的通过自己的讨论解决了,但是有一些我们思路逻辑上不太清楚的,就请教了实验室的大佬,在大佬的帮助下,和我们说了它的大概思路,帮我们理清了思路,我们顺利的解决了问题。觉得自己Java的学习还是不够的,对于很多东西的运用都不太熟悉。
学习进度条
第 N 周 | 新增代码(行) | 累计代码(行) | 本周学习耗时(小时) | 累计学习耗时(小时) |
---|---|---|---|---|
1 | 460 | 460 | 12 | 12 |
2 | 0 | 460 | 6 | 18 |
3 | 800 | 1260 | 10 | 28 |
4 | 500 | 1760 | 8 | 36 |