zoukankan      html  css  js  c++  java
  • 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+) = {([Sxa0 ]+)}'
            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)+'
    ')
            file.write('Title: ' + titles[i] + '
    ')
            file.write('Abstract: ' + abstracts[i] + '
    ')
            file.write('
    
    ')
    
    with open('all_data.txt', 'w', encoding='utf-8') as file:
        for i in range(len(titles)):
            file.write(str(i)+'
    ')
            file.write('Title: ' + titles[i] + '
    ')
            file.write('Authors: ' + authors[i] + '
    ')
            file.write('Abstract: ' + abstracts[i] + '
    ')
            file.write('Booktitle: ' + booktitles[i] + '
    ')
            file.write('PDF Link: ' + pdf_links[i] + '
    ')
            file.write('Time: ' + years[i] + ' ' + months[i])
            file.write('
    
    ')
    

    数据分析

    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进一步学习
  • 相关阅读:
    js实现观察者模式
    磁盘阵列操作实战
    淘宝知名工程师
    Java线程并发控制基础知识
    java多线程总结
    NIO系列1:框架拆解
    Java NIO 系列教程
    Java NIO系列教程(三-十二) Buffer
    Java NIO系列教程(二) Channel
    Java NIO系列教程(一) Java NIO 概述
  • 原文地址:https://www.cnblogs.com/yaozhengyi/p/9769349.html
Copyright © 2011-2022 走看看