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


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

    课程名称:软件工程1916|W

    作业链接:结对第二次—文献摘要热词统计及进阶需求

    结对学号:221600421-孔伟民 | 221600422-李东权

    作业正文

    效能分析与 PSP

    PSP 2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
    Planning 计划
    • Estimate • 估计这个任务需要多少时间
    Development 开发
    • Analysis • 需求分析 (包括学习新技术) 200 240
    • Design Spec • 生成设计文档 60 60
    • Design Review • 设计复审 30 40
    • Coding Standard • 代码规范 (为目前的开发制定合适的规范)
    • Design • 具体设计 60 60
    • Coding • 具体编码 400 600
    • Code Review • 代码复审 100 200
    • Test • 测试(自我测试,修改代码,提交修改) 60 300
    Reporting 报告
    • Test Report • 测试报告 60 100
    • Size Measurement • 计算工作量 30 30
    • Postmortem & Process Improvement Plan • 事后总结, 并提出过程改进计划 20 20
    总计 1020 1650

    分工

    • 221600422 李东权

      • 主要代码实现

      • 需求分析讨论

      • 辅助博客撰写

      • 单元测试

    • 221600421 孔伟民

      • 博客撰写
      • 爬虫程序编写
      • 需求分析讨论
      • 单元测试

    基本需求

    我们看到题目之后首先思考的时单词、字符、分隔符的定义分别是什么,经过了群里大家的多天讨论后还是没有得出特别准确的结论,就开始编写初版程序,具体思路是每一行读取后根据正则表达式匹配并且分割成数组,对数组进行遍历然后看具体的单词情况,行数和字符的实现只需要读取一遍数据就可以得出了。

    其中主要的功能全都放在 WordCount 类中,而 Main 中则是对命令行参数的一些处理,类图如下:

    WordCount 中有三个主要的方法,即字符、单词、行数统计,对于关键函数 WordCout 单词统计的实现过程是这样的:

    爬虫部分

    爬虫的部分使用了「Jsoup」库,首先到 CVPR2018 的网站获取到论文的列表 ,可以看到列表的 HTML 的结构如下:

    具体的论文链接都在类为 ptitle 的 dt 标签下,通过 Elements elements = doc.select(".ptitle a") 就可以选择到所有的链接。注意:这里的链接都是相对路径,不包含主机名,所有我们在抓具体的论文时要加上前缀http://openaccess.thecvf.com/

    具体的论文详情页:

    HTML 结构依然很简单,其中框出来的地方就是我们需要的内容,分别是标题、作者、摘要,通过 doc.select("#papertitle").text() 等就可以获取到具体的信息。

    在抓取的过程中发现一个个顺序爬太慢了,于是就使用了多线程加快爬取的速度,ExecutorService pool = Executors.newScheduledThreadPool(8); 创建一个线程池,遍历时就新建一个对应的线程加入到线程池中,可以加快爬取的速度。

    进阶需求

    我们在主程序中先获取到了输入的各个参数,然后把参数传入 CountArchieve 类构造对象,CountArchieve 类实现了行数统计、字符数统计、单词数统计以及进阶需求中的词组统计和权重要求,具体实现流程图如下:

    在基础的功能上加入了词组统计以及权重的计算。其中在排序这个方面,即要求按照字典序输出,我们使用了TreeMap ,它具有按照字典序自动排序的功能。

    具体代码分析

    charCount 和 lineCount 的实现比较简单,从文件流的开头开始遍历,一边遍历一边数就可以了

    public int LineCount() throws IOException {
            int count=0;
            bufferedReader.reset();
            String line;
            while ((line=bufferedReader.readLine())!=null){
                if(!line.isEmpty())
                    count++;
            }
            return count;
        }
    
    public int CharCount() throws IOException {   //不能区分回车和/r/n
            int count=0;
            bufferedReader.reset();
            int temp;
            while ((temp=bufferedReader.read())!=-1){
                count++;
                if(temp==13)
                    bufferedReader.read();
            }
            return count;
        }
    

    最为核心的函数就是 WordCount

    @Override
        public int WordCount() throws IOException {
            int count=0;
            String line;
            bufferedReader.reset();
            StringBuffer stringBuffer=new StringBuffer();
            while ((line=bufferedReader.readLine())!=null)
                stringBuffer.append(line+"
    ");
            String content=stringBuffer.toString();
    
            //分割文本,分别以分隔符划分和字母数字划分,得到分隔符数组和字母数字数组
            String [] words=content.split("([^a-zA-Z0-9]|
    )+");//1
            String [] division=content.split("[a-zA-Z0-9]+");//2
    
            //判断文本是数字字母先出现还是分隔符先出现,用于M的词组统计中的分隔符位置
            int whofirst=1;
            if(words.length>0&&division.length>0){
                if(content.indexOf(words[0])<content.indexOf(division[0]))
                    whofirst=1;
                else
                    whofirst=2;
            }
            else if(words.length>0)
                whofirst=1;
            else if(division.length>0)
                whofirst=2;
    
    
            //用于存放长度为M的词组
            List<String> wordgroup=new ArrayList<>();
    
            //单词的正则表达式
            Pattern pattern=Pattern.compile("^[a-zA-Z]{4,}[0-9]*[a-zA-Z]*");
            Integer value=0;
            String temp="";
            int weight=1; //权重
    
    //        for(int i=0;i<words.length;i++)
    //            System.out.println(words[i].toLowerCase());
    
            for(int i=0,record=0;i<words.length;i++){
                //System.out.println(words[i].toLowerCase());
                /*
                关于权重的判断,因为Title和Abstract相当于两部分需要清空积累量
                变量解释:
                    temp 用于存放获取到的单词组 当M=2时,可能存放为 [A+]B ,即单词分隔符单词
                    wordgroup与temp类似,唯一的区别是[A+][B+],即B后面还要存放紧跟的换行符
                    record记录当前有多少个单词满足了
                 */
    
                if(weightjudge){
                    if(words[i].equals("Title")){
                        weight=10;
                        temp="";
                        record=0;
                        wordgroup.clear();
                        continue;
                    }
                    else if(words[i].equals("Abstract")){
                        weight=1;
                        temp="";
                        record=0;
                        wordgroup.clear();
                        continue;
                    }
                }
                else{
                    if(words[i].equals("Title")||words[i].equals("Abstract")){
                        temp="";
                        record=0;
                        wordgroup.clear();
                        continue;
                    }
                }
    
                //匹配单词
                if(pattern.matcher(words[i]).matches()){
                    count++;   //单词数+1
                    words[i]=words[i].toLowerCase(); //转化为小写
                    temp+=words[i];
                    record++;    //满足词组长度+1
    
                    //这个判断是用来判断词组问题即,temp=单词+换行符,中换行符的位置,是否需要换行符,1为单词先,2为单词后
                    if(this.wordlength>1&&record<this.wordlength){  //输出第m个字符后temp不需要分隔符
                        if((whofirst==1)&&(i<division.length)){
                            temp+=division[i];
                            wordgroup.add((words[i]+division[i]));
                        }
                        else if((whofirst==2)&&((i+1)<division.length)){
                            temp+=division[i+1];
                            wordgroup.add((words[i]+division[i+1]));
                        }
                        else
                            wordgroup.add((words[i]));
                    }
                    else if (this.wordlength>1&&record==this.wordlength){ //wordgroup后需要temp+分隔符
                        if((whofirst==1)&&(i<division.length))
                            wordgroup.add((words[i]+division[i]));
                        else if((whofirst==2)&&((i+1)<division.length))
                            wordgroup.add((words[i]+division[i+1]));
                        else
                            wordgroup.add((words[i]));
                    }
                    if(record==this.wordlength){   //满足词组长度
                        if(treeMap.containsKey(temp)) {
                            value = treeMap.get(temp) + weight;   //查找是否存在
                            treeMap.put(temp, value);
                        }
                        else{
                            treeMap.put(temp,weight);
                        }
                        temp="";
                        if(this.wordlength>1){
                            //由于 a b c d,当M=3时有 <abc> <bcd>两个词组,这时候就要依靠
                            //wordgroup保存bc两个单词,此时wordgroup弹出a,留下bc,
                            //temp修改为b+c+,这就是前面group比temp多保存一个换行符的原因
                            for(int x=1;x<wordgroup.size();x++)
                                temp+=wordgroup.get(x);
                            wordgroup.remove(0);
                        }
                        record--;
                    }
                    else;
                }
                else{
                    temp="";
                    record=0;
                    wordgroup.clear();
                }
            }
            return count;
        }
    

    性能分析

    使用了 JProfiler 性能测试工具,可以看到程序的主要时间花费都用在了字符串的分割和正则的匹配,即 split 和 match 函数上,wordcount 函数是程序中主要的函数,运行时间占到了 15%。

    单元测试

    我们构造了若干组测试数据,利用 idea 已有的 junit 进行单元测试,主要是测试 charCount、wordCount、lineCount 这三个函数的输出符不符合我们的预期输出,其中单元测试类如下:

    package Test;
    
    import demo.CountAchieve;
    import org.junit.Assert;
    import org.junit.Test;
    
    import static org.junit.Assert.*;
    
    public class CountAchieveTest {
    
        private String load = "D:\program\IntellijIdeaProjects\WordCount\src\Test\";
    
        // 测试文件列表
        private String[] files = {
                "test1.txt", "test2.txt", "test3.txt", "test4.txt", "test5.txt",
                "test6.txt", "test7.txt", "test8.txt", "test9.txt", "test10.txt"
        };
        // 以下是预期输出
        private int[] chars = {
                33, 34,0, 10, 67,
                40, 32, 56,55, 19
        };
        private int[] lines = {
                2, 2,0, 0, 3,
                3, 5, 3,2, 5
        };
        private int[] words = {
                1, 1,0, 0, 5,
                4, 3,6,6, 0
        };
    
    
        @Test
        public void charCount() throws Exception {
            CountAchieve t;
            for (int i = 0; i < files.length; i++) {
                t = new CountAchieve(load + files[i], "1.txt", 1, 10, false);
                Assert.assertEquals("字符统计错误"+files[i],chars[i],t.CharCount());
                t.CloseFile();
            }
        }
    
        @Test
        public void wordCount() throws Exception {
            CountAchieve t;
            for (int i = 0; i < files.length; i++) {
                t = new CountAchieve(load + files[i], "1.txt", 1, 10, false);
                Assert.assertEquals("单词统计错误"+files[i],words[i],t.WordCount());
                t.CloseFile();
            }
        }
    
        @Test
        public void lineCount() throws Exception {
            CountAchieve t;
            for (int i = 0; i < files.length; i++) {
                t = new CountAchieve(load + files[i], "1.txt", 1, 10, false);
                Assert.assertEquals("行数统计错误"+files[i],lines[i],t.LineCount());
                t.CloseFile();
            }
        }
    }
    

    我们通过一个测试文件集的列表输入测试文件,然后在每一个测试方法中循环统计这些测试文件的行数、符号、单词数等。

    部分测试文件实例如下:

    blank line
    java is awesome!!!

    sp#ec(ial ch*arac>ters

    期望输出:characters:56 words:6 lines:3

    123ABC<>?abc123)(=abcd111
    *&^%$#@

    期望输出:characters:34 words:2 lines:1

    心得与体会

    刚刚开始需要对两个人的分工进行统一,以及后面每个人负责的部分交付给另外一个人时要做好代码的管理,否则可能会出现代码不统一的情况,了解两人协作需要磨合。对于git的操作方面也有一些新的了解,单元测试以及性能分析是我们之前没有接触过的,在这次作业中有了初步的接触。

  • 相关阅读:
    大数据基础文献综述
    牛客网数据库SQL实战
    Leetcode with Python
    tinyMCE
    HTTP LVS
    采坑大全
    Hadoop 解除 NameNode is in safe mode
    R语言采坑系列——Warning message: In validDetails.polygon(x) : 强制改变过程中产生了NA
    .Net中的异步编程
    知识点4
  • 原文地址:https://www.cnblogs.com/kwm42/p/10538902.html
Copyright © 2020-2023  润新知