zoukankan      html  css  js  c++  java
  • 结对第二次—文献摘要热词统计及进阶需求


    前言


    一、时间预估

    PSP

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

    二、初始解题思路

    刚看到基本题目时,首先想到的是用python写,毕竟写爬虫什么的,第一选择往往是python。但后来看到题目要求语言使用c++或java,于是了对比c++与java。由于本次的作业是要写文词统计+爬虫两个简单的程序,考虑到文词统计可能需要对字符串进行大量的处理,又因为java已经有封装好的Pattern、Matcher的字符串正则匹配和处理的函数,而且之前上网查到过使用jsoup(Java 的HTML解析器)写爬虫也很方便,于是两人直接敲定统一使用java编写。对文词统计基本设计思路是写两个类,一个专门用来测试,另一个包含了基本的统计字符、行数、单词等方法。爬虫因为之前未曾写过,因此基本上是边看教程边写。

    三、设计实现过程(java实现)

    (一)WordCount基本

    代码如何组织

    一共两个类:Main类(测试类)、WordCount类(集合了所有统计函数的类),
    类图:

    因为WordCount类只负责对外界的输入做统计功能,因此我把每个函数写成静态方法,外界可直接通过类名调用。countChar、countWord、countLine函数分别统计字符数、单词数、有效行数;isChar、isWord、isLine函数分别判断是否为有效字符、单词、有效行数;toWordMap函数用来将从txt文件读取到的文本转化为单词—>词频(key—>value)的一个hashmap结构;outPutTop10函数将这个hashmap结构转化为动态数组结构,进行排序,输出top10关键词;outCount函数做综合输出,即调用此函数一次将字符数、单词数、有效行数、top10单词输出到result.txt文件中。

    单元测试

    关键代码,流程图

    最关键的算法在于如何正确匹配各种需要处理的字符串,还有转化hashmap结构和对hashmap中的单词进行排序等。总体没有什么特别复杂的函数,这里展示将输入的文本转为hashmap结构存储的流程图:

    关键代码:

    public static int countChar(File file) throws IOException {//计算字符数
    		int charnum=0;
    		BufferedReader reader=new BufferedReader(new FileReader(file));
    		int c;
    		while((c=reader.read())!=-1) {
    			if (isChar(c)) {
    				charnum++;
    			}	
    		}
    		reader.close();
    		return charnum;
    	}
    public static int countLine(File file) throws IOException {//计算行数
    		int linenum=0;
    		BufferedReader reader=new BufferedReader(new FileReader(file));
    		String s=null;
    		while((s=reader.readLine())!=null) {
    			if (isLine(s)) {
    				linenum++;	
    			}
    		}
    		reader.close();
    		return linenum;
    	}
    public static int countWord(File file) throws IOException{//计算单词数
    		int wordnum=0;
    		BufferedReader reader=new BufferedReader(new FileReader(file));
    		String s=null;
    		while((s=reader.readLine())!=null) {
    			String[] strings=toWordList(s);//分隔
    			for(int i=0;i<strings.length;i++) {
    				if (isWord(strings[i])) {
    					wordnum++;
    				}
    			}
    		}
    		return wordnum;
    	}
    public static String[] outPutTop10(File file) throws IOException {//输出top10单词
    		HashMap<String, Integer> wordMap=toWordMap(file);
    		
            ArrayList<Map.Entry<String,Integer>> list = new ArrayList<Map.Entry<String,Integer>>(wordMap.entrySet());
            Collections.sort(list,new Comparator<Map.Entry<String,Integer>>() {
                //升序排序
    			@Override
    			public int compare(Entry<String, Integer> o1, Entry<String, Integer> o2) {
    				// TODO Auto-generated method stub
    				if (o1.getValue().compareTo(o2.getValue())==0) {//如果词频相同				
    					return -1;
    				}
    				 return o2.getValue().compareTo(o1.getValue());
    			}
            });
            int num=0;
            String[] top10=new String[10];//保存TOP10
            for(Map.Entry<String,Integer> mapping:list){ 
            	if (num==10) {
    				break;
    			}
            	top10[num]="<"+mapping.getKey()+">"+":"+mapping.getValue();
            	num++;
            } 
            return top10;
        }
    

    toWordMap():

    public static HashMap<String, Integer> toWordMap(File file) throws IOException {//把txt文件内容构建为一个HashMap结构
    		HashMap<String, Integer> hashMap=new HashMap<>();
    		BufferedReader reader=new BufferedReader(new FileReader(file));
    		String s=null;
    		int index;
    		while((s=reader.readLine())!=null) {
    			index=0;
    			String[] strings=toWordList(s);//分隔为准单词数组
    			while(index<strings.length) {
    				if (isWord(strings[index])) {
    					String lowerstring=strings[index].toLowerCase();//单词转为小写
    					if (hashMap.containsKey(lowerstring)) {//判断是否重复
    						hashMap.put(lowerstring, hashMap.get(lowerstring)+1);//如果重复词频加1
    					}
    					else {
    						hashMap.put(lowerstring, 1);//如果单词不重复初值为1
    					}
    				}
    				index++;
    			}
    		}
    		reader.close();
    		return hashMap;
    	}
    

    (二)WordCount进阶

    代码如何组织

    仍然是两个类:Main类(测试类)、WordCount类(集合了所有统计函数的类)
    类图:

    由于需求的变更,现在需要为这些统计函数增加一些参数,以及对函数的一些修改。主要修改为:由于新增了命令行可输入参数,因此用switch对参数输入进行判断,赋值给相应变量;增加了一个inputProcess函数,消除编号行以及Abstract: ,Title: 这两个字符串,以便与之后的统计;把toWordMap函数改写为toPhraseMap函数——在基本需求中只考虑往hashmap存入单个单词的情况,现在增加了词组词频统计功能,于是把之前的单词也看作词组,写成一个存储词组的hashmap函数;剩下的就是一些正则表达式的修改,这个8谈。

    单元测试

    关键代码,流程图

    进阶需求中最复杂的代码实现就是对词组词频的统计,流程图:

    关键代码,统计函数与基本需求相差不大,这里只展示toPhraseMap函数:

    public static HashMap<String, Integer> toPhraseMap(File file,int w,int m) throws IOException {//把txt文件内容构建为一个HashMap结构
    		HashMap<String, Integer> hashMap=new HashMap<>();
    		BufferedReader reader=new BufferedReader(new FileReader(file));
    		String s=null;
    		int weight=1;
    		while((s=reader.readLine())!=null) {
    			if (Pattern.matches("[0-9]+", s)) {//跳过编号行
    				continue;
    			}
    			weight=1;
    			if (Pattern.matches(".*Title: .*",s)&&w==1) {//遇到title行并且w=1,权值设为10
    				weight=10;
    			}		
    			String ns=inputProcess(s);
    			
    			String[] strings=toWordList(ns);//分隔出准单词数组
    			String[] strings2=toBreakList(ns);//分隔出分隔符数组
    			String phrase="";
    			String lowerstring=null;
    			boolean find=true;//find为true代表找到符合规则的词组
    			for(int i=0;i<strings.length-m+1;i++) {
    				find=true;
    				phrase="";
    				for(int j=i;j<i+m;j++) {//循环m次
    					if (isWord(strings[j])) {
    						lowerstring=strings[j].toLowerCase();//转小写
    						phrase=phrase+lowerstring;//拼接为词组
    						if (m!=1&&j!=i+m-1) {
    							if (strings[0].equals("")) {
    								phrase=phrase+strings2[j];//拼接为词组
    							}
    							else if (strings2[0].equals("")) {
    								phrase=phrase+strings2[j+1];//拼接为词组
    							}
    						}
    					}
    					else {
    						find=false;
    						break;
    					}
    				}
    				if (find) {//如果找到一个词组
    					if (hashMap.containsKey(phrase)) {//如果重复词组则对应词频加weight
    						hashMap.put(phrase, hashMap.get(phrase)+weight);
    					}
    					else {
    						hashMap.put(phrase, weight);//否则初始化为weight
    					}
    				}
    			}
    		}
    		reader.close();
    		return hashMap;
    	}
    

    (三)爬虫(Java)

    Java实现方式

    • Java中可以对html文本进行抓取和处理的第三方库有许多,但从本次项目来看,只需要抓取一部分数据,并没有过多复杂的操作,因此选择简单并且易上手的Jsoup。
    • 使用第三方库Jsoup可以对目标网站(CVPR)的HTML内容进行,抓取,并使用jsoup.nodes.Document对内容的DOM树进行处理,便可以获取相应内容。
      查看网页HTML:
    • 对获取的Document使用select定位到想要获取数据的节点,获取数据同时储存在一个ArrayList中。
      获取的关键数据:
    • 待爬取结束后按照要求将数据格式化保存在文件中。
      保存:
    • 流程图

    关键代码

    1.多线程
    //创建100个线程同时工作,并让当前线程等待这些线程
    		ArrayList<Thread> threads = new ArrayList<Thread>();
    		for (int i = 0; i < threadnum; i++) {
    				Thread newThread = new Thread(new absThread(i, dealnum, paperdt.size(), paperdt, pList));
    				newThread.start();
    				threads.add(newThread);
    		}
    		for (int i = 0; i < threads.size(); i++) {
    				threads.get(i).join();
    		}
    2.加锁保护数据
    		lock.lock();
    		pList.add(title, abs);
    		lock.unlock();
    

    四、改进

    改进方案

    • 刚开始编写代码时由于没有好好考虑封装,因此改需求效率很低。改进方法是尽量让一个函数仅实现一个比较小而具体的功能,冗余的代码都写成一个函数供需要的函数调用。
    • 词组一开始以为词组仅包含单词,不需要考虑相隔的分隔符。后来知道需要词组需要加入分隔符后,考虑将准单词和分隔符分为两个数组,单词拼接为词组时将分隔符也加入拼接成的字符串,最后结果是成功的。
    • 通过使用第三方库可以很容易的获取页面的HTML内容,因此只需要对爬取到的内容进行逻辑处理即可,此处没有太多难题,但是因为爬取的论文有一定数量,而且此工程中所需的摘要内容还需要进入新页面进行抓取,导致1000条的内容就需要1000次的页面访问,因此爬取的效率极为低下(经测试大致需要4-5min),这显然是不行的。改进:第一反应就是使用多线程技术进行优化(毕竟用的第三方库的API进行内容抓取,此处我也不知道怎么进行优化)
      爬虫改进:
    • 思路:暂且先将线程数定为100,使用这100个线程同时对DOM进行处理,根据总共的论文篇数为每个线程制定工作任务(例如这里有979篇论文,那么每个线程大概就爬取10篇论文的内容)。这样 100个线程同时工作,就能极大提高爬虫的效率,减少爬取总时间。同时因为100个线程同时进行数据爬取和储存,因此如果不进行任何处理,极有可能会出现数据丢失(实际过程中也确实出现了数据丢失的情况),因此需要在保存数据时进行同步或者加锁(我选择加锁),从而保证存储过程中的稳定性。
    • 结果:成功将爬取时长从4-5min优化至20-30s。
    • :极大地提高了爬虫的效率。
    • :爬取并保存下来的论文列表顺序和网站上的不一致(个人认为这没什么所谓就懒得处理了)。
    • 性能分析图(工具使用JProfiler
      优化前:

      优化后:

    五、总结

    通过本次实践,我们意识到团队间的交流是十分重要的,因为一项工程是需要共同协作的,而每个人对工程需求的理解还有个人的目标都是不一样的,这些都会在工作的进行中暴露出来,而这些差异有可能就会成为团队中的矛盾,如果不能合理地解决这些矛盾,不仅无法共同合作,甚至会引起更大的冲突(适当的争论是可以的),最糟糕的情况下整个项目停止也不是不可能,因此我们认为有效的队内交流是十分重要的。

    困难及体会

    • 在编写WordCount时,对需求不明确,因此不断地修改代码。但在这过程中我也体会到函数封装的重要性,如果代码有许多冗余,需求一变更就要做大量修改。
    • 刚使用github,有些不太习惯,相信以后体会到它的好处后会克服。
    • 在对爬虫进行优化时,由于先前没有考虑到效率会如此差,因此在准备使用多线程优化时,需要大幅度修改代码,给自己添加了许多不必要的工作,如果能在之前考虑到效率的问题,就能在编写代码时提高重用性,以便在之后的处理中更简便。

    对队友的评价

    221600424

    队友对待作业十分认真,而且善于精益求精,在代码运行效率不够满意时查找方法,进行优化。
    

    221600427

    队友编程能力很强,而且乐于爆肝,每每出现BUG都不厌其烦的调试、查错。
    
  • 相关阅读:
    Prometheus学习系列(九)之Prometheus 存储
    Prometheus学习系列(八)之Prometheus API说明
    SSE图像算法优化系列七:基于SSE实现的极速的矩形核腐蚀和膨胀(最大值和最小值)算法。
    Crimm Imageshop 2.3。
    【短道速滑一】OpenCV中cvResize函数使用双线性插值缩小图像到长宽大小一半时速度飞快(比最近邻还快)之异象解析和自我实现。
    【算法随记七】巧用SIMD指令实现急速的字节流按位反转算法。
    【算法随记六】一段Matlab版本的Total Variation(TV)去噪算法的C语言翻译。
    SSE图像算法优化系列三十:GIMP中的Noise Reduction算法原理及快速实现。
    一种快速简便优秀的全局曲线调整与局部信息想结合的非线性彩色增强算法(多图深度分析和探索)
    【算法随记五】使用FFT变换自动去除图像中严重的网纹。
  • 原文地址:https://www.cnblogs.com/lbwblog/p/10530455.html
Copyright © 2011-2022 走看看