2018软工实践第五次作业-结对作业(2)

软工实践第五次作业-结对作业(2)

一、结对信息

二、PSP表格

PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
Planning 计划 30 30
· Estimate · 估计这个任务需要多少时间 30 30
Development 开发 940 1420
· Analysis · 需求分析 (包括学习新技术) 120 180
· Design Spec · 生成设计文档 30 30
· Design Review · 设计复审 60 180
· Coding Standard · 代码规范 (为目前的开发制定合适的规范) 10 10
· Design · 具体设计 240 240
· Coding · 具体编码 360 540
· Code Review · 代码复审 60 60
· Test · 测试(自我测试,修改代码,提交修改) 60 180
Reporting 报告 100 160
· Test Repor · 测试报告 60 120
· Size Measurement · 计算工作量 10 10
· Postmortem & Process Improvement Plan · 事后总结, 并提出过程改进计划 30 30
|       | 	合计  |1040 |1580  

三、解题思路及设计说明

运行环境:Python 3.6.6
使用的库:requests, lxml, bs4
通过分析页面源码可以看出每篇论文的主页都是由

<dt class="ptitle"><br><a href="content_cvpr_2018/html/Das_Embodied_Question_Answering_CVPR_2018_paper.html">Embodied Question Answering</a></dt>

@href 属性 和 http://openaccess.thecvf.com/ 拼接而成的。
所以就先获取所有论文的主页,然后再依次爬取所有的论文的信息,利用 lxml, bs4, 正则 提取出
相应位置的信息。 一开始是做单进程的,完整爬一遍要9分钟多,就改了一个多进程的版本。
全部爬完之后再输出到文件。

def scrape(lock, data, url):
    try:
        text = get_page(url)

        pdf_links, abstracts, authors, titles, booktitles, months, years = data
        # print(text)
        html = etree.HTML(text)
        pdf_link = html.xpath('//a[contains(text(), "pdf")]/@href')[0]
        pdf_link = 'http://openaccess.thecvf.com/' + pdf_link[6:]

        abstract = html.xpath('//div[@id="abstract"]/text()')[0]
        abstract = abstract[1:]

        soup = BeautifulSoup(text, 'lxml')
        detail = soup.find(class_="bibref")

        regex = '(\w+) = {([\S\xa0 ]+)}'
        results = re.findall(regex, detail.getText())
        author = results[0][1]
        title = results[1][1]
        booktitle = results[2][1]
        month = results[3][1]
        year = results[4][1]
        lock.acquire()

        pdf_links.append(pdf_link)
        abstracts.append(abstract)
        authors.append(author)
        titles.append(title)
        booktitles.append(booktitle)
        months.append(month)
        years.append(year)

        lock.release()
    except ConnectionError:
        print('Error Occured ', url)
    finally:
        print('URL ', url, ' Scraped')
  • 代码组织与内部实现设计(类图)

根据需求实现了单词/词组的词频统计、加入权重的词频统计、行数统计、单词/词组数统计、字符数
统计、自定义输入输出文件等功能

image

  • 说明算法的关键与关键实现部分流程图

主要就是提取词组这部分, 要求有不能跨title和abstract,
两个部分的统计方式是相同的,所以就先将title和abstract分别存进List,然后再相同方式处理。
词组处理,就使用分隔符将title或者abstract进行分割,因为词组统计的时候要保留分割符,所以
将所有的分隔符匹配出来备用,词组统计的时候需要分隔符的时候再连接上去。当词组数为m时,需要
连续m个为单词才满足条件。

四、附加题设计与展示

附加题及爬虫python代码实现和爬虫数据戳这里

爬虫

爬虫爬取完毕后会将结果输出到两个文件
一个是只包含Title, abstract
另一个是包含所有能爬取到的信息

with open('result.txt', 'w', encoding='utf-8') as file:
    for i in range(len(titles)):
        file.write(str(i)+'\n')
        file.write('Title: ' + titles[i] + '\n')
        file.write('Abstract: ' + abstracts[i] + '\n')
        file.write('\n\n')

with open('all_data.txt', 'w', encoding='utf-8') as file:
    for i in range(len(titles)):
        file.write(str(i)+'\n')
        file.write('Title: ' + titles[i] + '\n')
        file.write('Authors: ' + authors[i] + '\n')
        file.write('Abstract: ' + abstracts[i] + '\n')
        file.write('Booktitle: ' + booktitles[i] + '\n')
        file.write('PDF Link: ' + pdf_links[i] + '\n')
        file.write('Time: ' + years[i] + ' ' + months[i])
        file.write('\n\n')

数据分析

render.html下载后可直接打开
以下截图是在Jypyter Notebook中截取


通过python的pyecharts库调用echarts,实现关系图,......,数据太多了...,所以


放大看看..., 论文数发表越多的人的点越大


移动鼠标到要了解的人上面, 与他共同发表过论文的人会亮起

五、关键代码解释

主要部分就是实现词组统计这一块

private HashMap<String, Integer> countContent(List<String> contents, int m) {
    HashMap<String, Integer> map = new HashMap<>();
    String splitRegex = "[\\s+\\p{Punct}]+";
    String splitStartRegex = "^[\\s+\\p{Punct}]+";
    String wordRegex = "^[a-zA-Z]{4,}.*";
    Pattern pattern = Pattern.compile(splitRegex);

    for (String content : contents) {
        String[] temp = content.split(splitRegex);

        List<String> 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 isContinue = true;
            for (int j = 1; j < m; j++) {
                if (!temp[i + j].matches(wordRegex)) {
                    isContinue = 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 (isContinue) {
                String words = stringBuilder.toString().toLowerCase();
                if (!map.containsKey(words)) {
                    map.put(words, 1);
                } else {
                    int num = map.get(words);
                    map.put(words, num + 1);
                }
            }

        }

    }
    return map;
}

一开始有想过通过写正则,直接匹配出符合条件的,emmmm....还是有点难处理。
所以先将句子通过分割符分割,分割出每个词。匹配出所有的分割符(词组统计时候,要求带分隔符连接起来)。
然后直接暴力求解...,判断出符合条件的词组。将结果存入map中,并记录出现的次数。

六、性能分析与改进

测试时使用爬取CVPR的979篇论文作为输入,命令行参数为-i test.txt -n 15 -m 3 -o output.txt,
使用权重统计词组词频,每三个单词为一个词组,输出词频前15的词组存放到output.txt
循环运行100次,性能分析结果如下

iamge

iamge

代码覆盖率如下
iamge

使用VisualVM进行性能分析发现,Main中统计输入文件词组词频的WordsCount.countContent和统
计输入文件单词总数的WordsCount.getWordsSum占用时间最多,占用了90%左右的时间

七、单元测试

在这列出三个单元测试并给出中文注释,所有的单元测试代码可以在github中的test项目中看到。

  • 1.对CalMost的单元测试
    测试统计前十个单词及频数的函数和统计前n个单词及频数的函数
import org.junit.Test;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static org.junit.Assert.*;

public class CalMostTest {
    String path = "input.txt";
    HandleContent handleContent = new HandleContent(path);
    // -m 词组单词数设为1
    // -w 权重设为0
    WordsCount wordsCount = new WordsCount(handleContent, 1, 0);
    HashMap<String, Integer> map = wordsCount.getMap();

    CalMost calMost = new CalMost();

    @Test
    //无 -n 参数输入时
    //单词数超过十个则输出前十个单词及词频
    //不足则输出所有单词及词频
    public void mostWords() {

        List<Map.Entry<String, Integer>> list = calMost.mostWords(map);
        list.forEach(System.out::println);
    }

    @Test
    //有 -n 参数输入时
    //单词数超过n个则输出前n个单词及词频
    //不足则输出所有单词及词频
    public void mostWords1() {

        List<Map.Entry<String, Integer>> list = calMost.mostWords(map,9);
        list.forEach(System.out::println);
    }
}
  • 2.对WordsCount的单元测试
    测试获取单词数和单词、词频的函数
import org.junit.Test;

import java.util.HashMap;
import java.util.Map;

import static org.junit.Assert.*;

public class WordsCountTest {

    String path = "input.txt";
    HandleContent handleContent = new HandleContent(path);
    // -m 词组单词数设为1
    // -w 权重设为0
    WordsCount wordsCount = new WordsCount(handleContent, 1, 0);

    @Test
    //获取单词总数
    public void getSum() {
        System.out.println(wordsCount.getSum());
    }

    @Test
    //获取map内容,单词及其词频数
    public void getMap() {
        System.out.println(wordsCount.getMap().toString());
    }
}
  • 3.对HandleContent的单元测试
    测试获取论文title、论文abstract和所有论文内容的函数
import org.junit.Test;

public class HandleContentTest {
    String path = "test.txt";
    //声明一个handleContent对输入内容进行分类
    //将所有的title和所有的abstract各自分到一起
    HandleContent handleContent = new HandleContent(path);

    @Test
    //输出应为所有的title内容
    public void getTitles() {

        System.out.println(handleContent.getTitles().toString());
    }

    @Test
    //输出应为所有的abstract内容
    public void getAbstracts() {

        System.out.println(handleContent.getAbstracts().toString());
    }

    @Test
    //输出应为title + abstract 内容
    public void getHandledContent() {

        System.out.println(handleContent.getHandledContent());
    }
}

八、GitHub代码签入记录

iamge
iamge
iamge

九、遇到的困难及解决方法

  1. 对于题意的理解存在问题,题目要求标点符号也要算成分隔符,例如question(“orange就可以
    分割成question和orange两个单词。但是我们对连字符"-"产生了误解,认为像Super-Resolution
    这样的从语义上来看,只能算一个单词,所以产生了错误。经过和助教还有其他同学的沟通后,才明
    白了自己的错误。

  2. 粗心导致的问题,在测试时发现统计单词数时经常会漏掉许多单词,我们一开始认为是正则出现了
    问题,但是实际上导致这个bug的是循环语句的错误,在循环体中本应该使用continue结束本次循环
    但是错误地使用了break结束了整个循环,所以导致结果的错误。

for (int j = 1; j < m; j++) {
    if (!temp[i + j].matches(wordRegex)) {
    isContinue = false;
        break;
    } else {
        if (isSplitStart) {
            stringBuilder.append(splits.get(i + j));
        } else {
            stringBuilder.append(splits.get(i + j - 1));
        }
        stringBuilder.append(temp[i + j]);
    }
}
  1. 玄学bug, 出现了一次玄学bug,在测试时,读取文件中的序号后,正则匹配不出来,按行读取查
    看输出时没问题,单独测试正则也是没问题的,???,就完全找不到问题,删除了这个txt文件,重
    新建了一个txt文件,内容完全一样,再次测试就没问题了,???。

十、评价队友

志炜大佬精通Java、Python,项目经验丰富,号称数计最强的男人,本次的结对作业让我实实在在地体会到了这位巨佬的惊人实力。这次的结对作业对于他来说并没有什么难度,但是像我这种刚学Java的人就不一样了。但是每次在我遇到问题时,他都能够一眼看出我的问题出在哪里,告诉我遇到这种问题该如何解决并提出一些新的思路。而且志炜大佬总是会非常严格认真地纠正我的代码习惯(我之前对这个并不在意)。在他的帮助下,我对Java的学习和理解又得到了更进一步的加深。和志炜大佬一起合作完成本次作业,让我的Java学习之旅轻松又愉快。为了感谢志炜大佬一段时间的帮助,我决定送一套自己的亲笔签名照给他,希望他能够喜欢(必须喜欢)~~

十一、学习进度条

第N周 新增代码行 累计代码行 本周学习耗时(小时) 累计学习耗时(小时) 重要成长
1 200 200 15 15 学习Java以及IDEA的使用
2 10 25 阅读构建之法,了解了NABCD模型,学会了原型工具的使用
3 600 800 20 45 阅读《第一行代码》学习Android开发,Java进一步学习
posted @ 2018-10-10 21:47  yao正义  阅读(144)  评论(0编辑  收藏  举报