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



    格式描述

    • 这个作业属于哪个课程:软件工程实践
    • 这个作业要求在哪里:作业要求
    • 结对学号: 221600434吴何 221500318陈一聪
    • github: 吴何
    • 这个作业的目标:
      一、基本需求:实现一个能够对文本文件中的单词的词频进行统计的控制台程序。
      二、进阶需求:在基本需求实现的基础上,编码实现顶会热词统计器。



    一、WordCount基本需求

    1.思路分析

    • 读取字符采用逐个读取文件流,判断是否符合要求(根据题目要求选择不读CR(ASCII码值为13))再计数。或者按行读取后再转化为chararray数组后逐个判断?(这种方式会省略换行)
    • 因为要读取单词,所以使用正则表达式来匹配单词,并过滤掉非法字符,以单词-频率作为键值对保存在TreeMap中,TreeMap会根据键的字典序自动升序排列,所以后面只要再实现按照频率优先的排序算法即可。
    • 行数统计直接按行读取后使用trim()去掉空白字符后,再进行判断计数即可。

    2.设计过程

      分为多个函数块,各自实现一部分功能,对于统计单词总数和统计单词频率可以合并在一个函数里。
      计划分割为:CharactersNum(file)实现字符统计,linesNum(file)实现行数统计,wordsNum(file)实现单词和频率统计,writeResult(infile,outfile)实现输出。
      对于每个函数模块,首先实现基本的功能,再考虑边缘条件,逐步优化函数。
      在对函数进行接口封装时,发现如果统计单词总数和统计单词频率放在一起,则粒度太大,不好按格式输出,也无法使用其中之一功能,所以后面拆为wordsNum(file)统计单词总数,wordMap(file)保存每个单词的频率,再用writeMostWords(file)按照<word>: num格式输出结果。虽然增加了一次文件读入,但降低了耦合度,是可以接受的。

    2.关键代码

    • 统计文件的字符数
    	public static int charactersNum(String filename) throws IOException  {
    		int num = 0;		
    		BufferedReader br = new BufferedReader(new FileReader(filename));
    		int value = -1;
    		while ((value = br.read()) != -1) {
    			if (value > 0 && value < 128 && value != 13) {
    				num ++;
    			}			
    		}
    		br.close();		
    		return num;
    	}
    
    • 统计文件的有效行数
     	public static int linesNum(String filename) throws IOException  {
    		int num = 0;			
    		BufferedReader br = new BufferedReader(new FileReader(filename));
    		String line = null;
    		while ((line  = br.readLine()) != null) {
    			if (line.trim().length() != 0) {
    				num ++;					
    			}
    		}
    		br.close();		
    		return num;
    	}	
    
    • 统计文件的单词个数
    	public static int wordsNum(String filename) throws IOException  {
    		int num = 0;			
    		BufferedReader br = new BufferedReader(new FileReader(filename));
    		String separator = "[^A-Za-z0-9]";//分隔符
    		String regex = "^[A-Za-z]{4,}[0-9]*$"; // 正则判断每个数组中是否存在有效单词
    		Pattern p = Pattern.compile(regex);
    		Matcher m = null;
    		String line = null;
    		String[] array = null;
    		while ((line  = br.readLine()) != null) {
                line = line.replaceAll("[(\u4e00-\u9fa5)]", "");// 过滤汉字
                line = line.replaceAll(separator, " "); // 用空格替换分隔符
                array = line.split("\s+"); // 按空格分割                     
                for (int i = 0;i<array.length;i++) { 
                	m = p.matcher(array[i]);
                	if (m.matches()) {
                		num++;
                	}
                }
    		}						
    		br.close();		
    		return num;
    	}	
    
    • 统计文件的热词数
            public static TreeMap<String, Integer> wordMap(String filename) throws IOException {	
            // 第一种方法遍历 
            // 使用entrySet()方法生成一个由Map.entry对象组成的Set,  
            // 而Map.entry对象包括了每个元素的"键"和"值".这样就可以用iterator了  
    //		Iterator<Entry<String, Integer>> it = tm.entrySet().iterator();  
    //		while (it.hasNext()) {  
    //			Map.Entry<String, Integer> entry =(Map.Entry<String, Integer>) it.next();  
    //			String key = entry.getKey();  
    //			Integer value=entry.getValue();  
    //
    //			System.out.println("<" + key + ">:" + value); 
    //		}
    		
    		TreeMap<String, Integer> tm = new TreeMap<String, Integer>();				
    		BufferedReader br = new BufferedReader(new FileReader(filename));
    		String separator = "[^A-Za-z0-9]";//分隔符
    		String regex = "^[A-Za-z]{4,}[0-9]*$"; // 正则判断每个数组中是否存在有效单词
    		Pattern p = Pattern.compile(regex);
    		String str = null;
    		Matcher m = null;
    		String line = null;
    		String[] array = null;
    		while ((line  = br.readLine()) != null) {
                line = line.replaceAll("[(\u4e00-\u9fa5)]", "");// 过滤汉字
                line = line.replaceAll(separator, " "); // 用空格替换分隔符
                array = line.split("\s+"); // 按空格分割                     
                for (int i = 0;i<array.length;i++) { 
                	m = p.matcher(array[i]);
                	if (m.matches()) {
                		str = array[i].toLowerCase();                
                        if (!tm.containsKey(str)) {
                            tm.put(str, 1);
                        } else {
                            int count = tm.get(str) + 1;
                            tm.put(str, count);
                        }
                	}
                }
    		}						
    		br.close();		
    		return tm;
    	}
    
    • 单词排序输出
    	public static void writeMostWords(String infilename,String outfilename) throws IOException {
    		String outpath = new File(outfilename).getAbsolutePath();
            FileWriter fw = new FileWriter(outpath, true);
            TreeMap<String, Integer> tm = wordMap(infilename);
    		if(tm != null && tm.size()>=1)
    		{		  
    			List<Map.Entry<String, Integer>> list = new ArrayList<Map.Entry<String, Integer>>(tm.entrySet());
    			// 通过比较器来实现排序
    			Collections.sort(list, new Comparator<Map.Entry<String, Integer>>() {
    				@Override
    				public int compare(Entry<String, Integer> o1, Entry<String, Integer> o2) {
    					//treemap默认按照键的字典序升序排列的,所以list也是排过序的,在值相同的情况下不用再给键升序排列		
    					// 按照值降序排序 
    					return o2.getValue().compareTo(o1.getValue());
    				}
    			});   
    			int i = 1;
    			String key = null;
    			Integer value = null;
    			for (Map.Entry<String, Integer> mapping : list) {
    				key = mapping.getKey();
    				value = mapping.getValue();
    				System.out.print("<" + key + ">: " + value + '
    ');
    				fw.write("<" + key + ">: " + value + '
    ');
    				//只输出前10个
    				if (i == 10) {
    					break;
    				}
    				i++;
    			}
    		}
    		fw.close();
    	}
    

    3.测试分析

    4.困难与解决

    • 统计文件字符数时,原本是打算按行读取后直接用line.Length来计算字符数,后来发现比测试样例要少,原因是readLine()不读取行末换行,采用read()逐个读取后问题解决。
    • 单词排序时,对于TreeMap<String,Integer>类型没有找到对应的排序接口Comparator<Entry<String, Integer>>(),后面将TreeMap保存到list中使用Collections接口调用排序得以解决。
    • 使用命令行编译时,由于一开始的项目在SRC目录下有包,.java文件在包里,所以javac 命令要进入到.java文件目录也就是最里层才可以,生成的.class文件和.java文件在同一目录。而使用java命令时,则要进到SRC目录下,包名作为前缀名来使用,并且名称不带后缀,格式如:java 包名.java文件名 才可以运行。后面项目使用默认包名不需要包名前缀。
    • 如果项目使用了别的.jar包,则要BuildPath->Add to buildPath到Referenced Libraries文件夹,才可以在IDE下编译运行,在命令行下按照上述方式会出现错误: 程序包xxx不存在,查资料后说要使用-classpath 选项添加.jar文件路径或者-cp选项添加添加.jar文件路径才能解决,尝试了多次都不行,后面只好用IDE自带的命令行参数输入工具来模拟输入。


    二、WordCount进阶需求

    1.思路分析

    使用jsoup爬取CVPR2018数据保存下来,然后再进行统计输出。
    使用commons-cli实现任意命令行参数的组合输入以及任意顺序参数输入。对基本需求中的函数进行改进,增加布尔参数值w用以判断是否开启单词权重功能,然后再进行权重统计。开启w后Title中的单词词频以10倍计。统计字符时,用replaceAll("Title: |Abstract: ", "");过滤掉标签后再统计(使用readline()先读取行再过滤时会漏掉行末的换行符,所以每读一行,字符数要自增1)。统计单词时则是先使用正则表达式Title: .* Abstract: .匹配到标签后就从字符串中裁剪标签,再进行相应统计。统计行数时根据要求,匹配到正则表达式[0-9]以及空行时不计算行数。

    2.关键代码

    • 统计文件的字符数
    	public static int charactersNum(String filename) throws IOException  {		
    		int num = 0;			
    		BufferedReader br = new BufferedReader(new FileReader(filename));
    		String regex = "[0-9]*"; // 匹配数字编号行
    		String separator = "Title: |Abstract: ";//过滤Title: 和 Abstract:
    		Pattern p = Pattern.compile(regex);
    		Matcher m = null;
    		String line = null;
    		char[] charArray = null;
    		int value = -1;
    		while ((line  = br.readLine()) != null) {
    			num++;//readLine()漏掉的换行符也统计在内
    			line = line.replaceAll(separator, ""); // 过滤Title: 和 Abstract:
    			m = p.matcher(line);
    			if (line.trim().length() != 0 && !m.matches()) {
    				charArray = line.toCharArray();
    				for (int i = 0;i < line.length();i++) {
    					value = (int)charArray[i];
    					if (value > 0 && value < 128 && value != 13) {
    						num ++;
    					}			
    				}				
    			}
    		}
    		br.close();		
    		return num;	 	
    	}
    
    • 统计文件的有效行数
     	public static int linesNum(String filename) throws IOException  {
    		int num = 0;			
    		BufferedReader br = new BufferedReader(new FileReader(filename));
    		String regex = "[0-9]*"; // 匹配数字编号行
    		Pattern p = Pattern.compile(regex);
    		Matcher m = null;
    		String line = null;
    		while ((line  = br.readLine()) != null) {
    			m = p.matcher(line);
    			if (line.trim().length() != 0 && !m.matches()) {
    				num ++;					
    			}
    		}
    		br.close();		
    		return num;
    	}	
    
    • 统计文件的单词个数
    	public static int wordsNum(String filename,boolean w) throws IOException  {
    		int num = 0;			
    		BufferedReader br = new BufferedReader(new FileReader(filename));
    		String separator = "[^A-Za-z0-9]";//分隔符
    		String regex = "^[A-Za-z]{4,}[0-9]*$"; // 正则判断每个数组中是否存在有效单词
    		String titleRegex = "Title: .*";
    		String abstractRegex = "Abstract: .*";
    		Pattern p = Pattern.compile(regex);
    		Pattern tp = Pattern.compile(titleRegex);
    		Pattern ap = Pattern.compile(abstractRegex);
    		Matcher m = null;
    		Matcher titleMacher = null;
    		Matcher abstractMacher = null;
    		String line = null;
    		String[] array = null;
    		boolean intitle = false;
    		while ((line  = br.readLine()) != null) {
    			titleMacher = tp.matcher(line);
    			abstractMacher = ap.matcher(line);
    			if (titleMacher.matches()) {
    				line = deleteSubString(line,"Title: ");
    				intitle = true;
    			}
    			if (abstractMacher.matches()) {			
    				line = deleteSubString(line,"Abstract: ");
    			}
                line = line.replaceAll("[(\u4e00-\u9fa5)]", "");// 过滤汉字
                line = line.replaceAll(separator, " "); // 用空格替换分隔符
                array = line.split("\s+"); // 按空格分割                     
                for (int i = 0;i<array.length;i++) { 
                	m = p.matcher(array[i]);
                	if (m.matches()) {
                		num = (w && intitle)?(num+10):(num+1);
                	}
                }
                intitle = false;
    		}						
    		br.close();		
    		return num;
    	}	
    
    • 统计文件的热词数
       public static TreeMap<String, Integer> wordMap(String filename,boolean w) throws IOException {		
    		TreeMap<String, Integer> tm = new TreeMap<String, Integer>();				
    		BufferedReader br = new BufferedReader(new FileReader(filename));
    		String separator = "[^A-Za-z0-9]";//分隔符
    		String regex = "^[A-Za-z]{4,}[0-9]*$"; // 正则判断每个数组中是否存在有效单词
    		String titleRegex = "Title: .*";
    		String abstractRegex = "Abstract: .*";
    		Pattern p = Pattern.compile(regex);
    		Pattern tp = Pattern.compile(titleRegex);
    		Pattern ap = Pattern.compile(abstractRegex);
    		Matcher m = null;
    		Matcher titleMacher = null;
    		Matcher abstractMacher = null;
    		String str = null;
    		String line = null;
    		String[] array = null;
    		boolean intitle = false;		
    		while ((line  = br.readLine()) != null) {
    			titleMacher = tp.matcher(line);
    			abstractMacher = ap.matcher(line);
    			if (titleMacher.matches()) {
    				line = deleteSubString(line,"Title: ");
    				intitle = true;
    			}
    			if (abstractMacher.matches()) {			
    				line = deleteSubString(line,"Abstract: ");
    			}
                line = line.replaceAll("[(\u4e00-\u9fa5)]", "");// 用空格替换汉字
                line = line.replaceAll(separator, " "); // 用空格替换分隔符
                array = line.split("\s+"); // 按空格分割                     
                for (int i = 0;i<array.length;i++) { 
                	m = p.matcher(array[i]);
                	if (m.matches()) {
                		str = array[i].toLowerCase();                
                        if (!tm.containsKey(str)) {
                        	tm.put(str, w&&intitle?10:1);
                        } else {
                            int count = tm.get(str) + (w&&intitle?10:1);
                            tm.put(str, count);
                        }
                	}
                }
                intitle = false;
    		}						
    		br.close();		
    		return tm;
    	}
    
    • 单词排序输出
    		public static void writeMostWords(String infilename,String outfilename,boolean w,int n) throws IOException {
    		String outpath = new File(outfilename).getAbsolutePath();
            FileWriter fw = new FileWriter(outpath, true);
            TreeMap<String, Integer> tm = wordMap(infilename,w);
    		if(tm != null && tm.size()>=1)
    		{		  
    			List<Map.Entry<String, Integer>> list = new ArrayList<Map.Entry<String, Integer>>(tm.entrySet());
    			// 通过比较器来实现排序
    			Collections.sort(list, new Comparator<Map.Entry<String, Integer>>() {
    				@Override
    				public int compare(Entry<String, Integer> o1, Entry<String, Integer> o2) {
    					//treemap默认按照键的字典序升序排列的,所以list也是排过序的,在值相同的情况下不用再给键升序排列		
    					// 按照值降序排序 
    					return o2.getValue().compareTo(o1.getValue());
    				}
    			});   
    			int i = 1;
    			String key = null;
    			Integer value = null;
    			for (Map.Entry<String, Integer> mapping : list) {
    				if (n == 0) {
    					break;				
    				}
    				key = mapping.getKey();
    				value = mapping.getValue();
    				System.out.print("<" + key + ">: " + value + '
    ');
    				fw.write("<" + key + ">: " + value + '
    ');
    				//只输出前n个 
    				if (i == n) {
    					break;
    				}
    				i++;
    			}
    		}
    		fw.close();
    	}
    
    • 裁剪字符串
    	public static String deleteSubString(String str1,String str2) {
    		StringBuffer sb = new StringBuffer(str1);
    		while (true) {
    			int index = sb.indexOf(str2);
    			if(index == -1) {
    				break;
    			}
    			sb.delete(index, index+str2.length());		
    		}		
    		return sb.toString();
    	}
    
    • 实现命令行参数任意顺序组合输入
    public static void main(String[] args) throws Exception {		
    			CommandLineParser parser = new GnuParser();
    			Options options = new Options();
    			options.addOption("i",true,"读入文件名");
    			options.addOption("o",true,"输出文件名");
    			options.addOption("w",true,"单词权重");
    			options.addOption("m",true,"词组词频统计");
    			options.addOption("n",true,"频率最高的n行单词或词组");
    			CommandLine commandLine = parser.parse(options, args);
    			
    			if (commandLine.hasOption("i") && commandLine.hasOption("o") && commandLine.hasOption("w")) {
    				String infilename = commandLine.getOptionValue("i");
    				String outfilename = commandLine.getOptionValue("o");
    				String w = commandLine.getOptionValue("w");
    				if (commandLine.hasOption("n")) {
    					String n = commandLine.getOptionValue("n");
    					if (isNumeric(n)) {						
    						if (w.equals("1")) {
    							writeResult(infilename,outfilename,true,Integer.valueOf(n));
    						}
    						else  {
    							writeResult(infilename,outfilename,false,Integer.valueOf(n));
    						}
    					}
    					else {
    						System.out.println("-n [0<=number<=100]");
    					}
    				}
    				else {
    					if (w.equals("1")) {
    						writeResult(infilename,outfilename,true,10);
    					}
    					else  {
    						writeResult(infilename,outfilename,false,10);
    					}
    				}
    			}
    			else {
    				System.out.print("必须有-i -o -w选项和参数");
    			}
    	}
    
    • 其他函数
    	public static boolean isNumeric(String str){ 
    		Pattern pattern = Pattern.compile("[0-9]*"); 
    		return pattern.matcher(str).matches(); 
    	}
    	
    	public static void initTxt(String string) throws IOException {
            String path = new File(string).getAbsolutePath();
            FileWriter fw = new FileWriter(path, false);
            fw.write("");
            fw.flush();
            fw.close();
    	}
    	  public static void writeResult(String infilename,String outfilename,boolean w,int n) throws IOException {
    	        File file = new File(infilename);
    	        if (file.exists()) {	
    	        	initTxt(outfilename);
    	        	String outpath = new File(outfilename).getAbsolutePath();
    	        	FileWriter fw = new FileWriter(outpath, true);
    	        	int charactersNum = charactersNum(infilename);
    	        	int wordsNum = wordsNum(infilename,w);
    	        	int linesNum = linesNum(infilename);
    	        	System.out.print("characters: " + charactersNum + '
    ');
    	        	System.out.print("words: " + wordsNum + '
    ');
    	        	System.out.print("lines: " + linesNum + '
    ');
    	        	fw.write("characters: " + charactersNum + '
    ');
    	        	fw.write("words: " + wordsNum + '
    ');
    	        	fw.write("lines: " + linesNum + '
    ');
    	        	fw.flush();
    	        	writeMostWords(infilename,outfilename,w,n);
    	        	if (fw != null) {
    	        		fw.close();
    	        	}
    	        }
    			else {
    				System.out.println(infilename + "文件不存在!");
    			}
    	    }
    

    3.测试分析

    4.困难与解决

    爬取的论文数据有非ascii码字符导致显示和统计不正确,使用正则表达式[^x00-xff]过滤后解决。使用单线程爬取速度太慢,多线程爬取这位同学已解决。

    三、心得总结和评价

    221600434吴何

      虽然要求实现的东西也简单,但是也花了不少时间,有时候被一些小细节问题打扰,为了解决问题,查了不少资料,从而影响到整个的编码的流畅度,特别是花了不少时间而问题又没有解决时,简直是一种折磨。不过还好,想法最终都得以顺利实现。也学到了额外的知识,比如爬虫工具jsoup的使用,github的代码管理以及单元测试等。
      不得不说的是,感觉自身的理解能力还不太行,花了比较多时间才大致明白了要实现的功能。
      评价队友:有比较强的学习动力,也乐于交流,求知欲强。

    221500318陈一聪

      当最终所有问题顺利解决时,看到自己提交完成的作业也特别有成就感,遇到不懂的问题最后也一一解决,学到了很多知识。
      花了很多时间才最后完成,觉得自己还有很多进步的空间,也比较庆幸有一个队友,帮助我理解和编程。
      评价队友:有比较强的编程能力,有耐心,有进取心。


    PSP

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


  • 相关阅读:
    docker 命令
    php cli命令
    windows 中docker连接使用mysql数据库
    什么是微服务
    PHP7新特性
    Docker Machine 命令
    关于Docker目录挂载的总结(二)
    实验十一 MySQLl备份与恢复1
    实验十--- MySQL过程式数据库对象
    实验九 存储函数和触发器
  • 原文地址:https://www.cnblogs.com/qq705599500/p/10539889.html
Copyright © 2011-2022 走看看