第1次作业
要求0:作业要求地址【https://edu.cnblogs.com/campus/nenu/2016CS/homework/2110】
要求1:git仓库地址【https://git.coding.net/Jingr98/wf.git】
要求2:
1.PSP阶段表格
SP2.1 |
任务内容 |
计划共完成需要的时间(min) |
实际完成需要的时间(min) |
Planning |
计划 |
40 |
50 |
Estimate |
估计这个任务需要多少时间,并规划大致工作步骤 |
40 |
50 |
Development |
开发 |
810 |
1030 |
Analysis |
需求分析 (包括学习新技术) |
90 |
130 |
Design Spec |
生成设计文档 |
40 |
40 |
Design Review |
设计复审 (和同事审核设计文档) |
0 |
0 |
Coding Standard |
代码规范 (为目前的开发制定合适的规范) |
0 |
40 |
Design |
具体设计 |
90 |
90 |
Coding |
具体编码 |
480 |
600 |
Code Review |
代码复审 |
40 |
50 |
Test |
测试(自我测试,修改代码,提交修改) |
70 |
80 |
Reporting |
报告 |
180 |
210 |
Test Report |
测试报告 |
100 |
120 |
Size Measurement |
计算工作量 |
40 |
40 |
Postmortem & Process Improvement Plan |
事后总结, 提出过程改进计划 |
40 |
50 |
功能模块 |
具体阶段 |
预计时间(min) |
实际时间(min) |
功能1 |
具体设计 具体编码 测试完善 |
20 130 20 |
30 200 25 |
功能2 |
具体设计 具体编码 测试完善 |
30 140 20 |
15 150 25 |
功能3 |
具体设计 具体编码 测试完善 |
40 210 30
|
45 250 30 |
2.分析预估耗时和实际耗时的差距原因:
(1)在分析预估耗时时,没有过多考虑编程中的细节问题。编程过程中不断有新的问题出现。
(2)对Java语言掌握不够熟练,编程中学习的时间较长
(3)最主要的原因就是一开始审题不清,没有考虑到输出样例格式的问题。导致项目将要完工时又要整改很多地方,浪费了好多时间。
要求3:
1.解题思路描述
(1)看到题目后,我想了一下这个程序大致有三个步骤:读取文本、统计单词、(排序)输出。有了这个框架后,我从最简单的功能1尝试编写,在获取到文本内容需要对字符串进行分割时我遇到了一些问题(因为不是很熟悉正则表达式),所以查阅了相关教程,自己尝试写了一下可以达到预期效果,但是需要两次正则表达式的运用(一是对字符串按空格和非字母数字符号进行分割得到字符串数组,二是对得到的字符串数组通过字母开头规则过滤掉那些非法单词),自我感觉这里编写的不是太好。实现了功能1后我开始看功能2,发现只要得到文件夹下的文件名数组,排序后返回指定文件路径后就可以参照功能1的实现。功能3的话只要在前者的基础上传入参数-n,对list进行排序后根据-n输出结果。
(2)最初编写时我是把功能1和功能2写在了一起,即用一个count()函数实现两个功能。功能1直接调用count()就可以实现词频统计,功能2则需要先调用readDir()和setpath()方法得到指定文件路径,然后再调用count()就可以了。功能3则是用count( int n )实现。但是后来我仔细看了题目后发现,两者的输出样例是不一样的!发现了这个问题后,我本来想写两个输出结果的方法分别对应上述两种情况,但是尝试了一下报了很多错误,就不敢大改了,只能选择把两种情况完全分开处理,于是就有了现在的 countFile()和countDir()分别对应功能1和功能2,countNum()对应功能3。这样虽然解决了样例输出的问题,但是代码重复量真的很大。
2.代码介绍
(1)困难点:功能1主要是对字符串的处理(正则表达式的运用),如何得到合法单词;功能2主要是获取某文件夹下的所有文件名,并按照文件名排序后返回指定文件,其余就参照功能1的实现;功能3主要是对词频进行排序,并按照参数进行输出。其实我感觉这三个功能分开来写不是很难,对我来说,最困难的就是如何减少代码的重复。三个功能明显有重复的部分,应该把哪些部分拎出来写成公共的方法,是我应该继续思考的!
(2)代码片段
简单介绍一下我的代码,总体上用Java写了两个类:wf类(包含各种功能方法)和wfTest类(分情况对wf类里的方法进行调用,用来测试)。
1) isLegal()函数:判断是否为合法单词
1 public boolean isLegal(String word) {
2 String regex="^[a-zA-Z][a-zA-Z0-9]*$";
3 Pattern p = Pattern.compile(regex);
4 Matcher m =p.matcher(word);
5 if(m.matches()) {
6 return true;
7 }else {
8 return false;
9 }
10 }
2) readDir()函数:获取文件夹下所有文件的文件名,对数组进行排序并返回第一个元素
1 public String readDir(String filepath){
2 File file = new File(filepath);
3 String[] filelist = file.list();
4 String[] namelist = new String [filelist.length];
5 for(int i=0;i<filelist.length;i++) {
6 File readfile = new File(filepath+"\\"+filelist[i]);
7 namelist[i]=readfile.getName();
8 }
9 List<String> list = (List<String>)Arrays.asList(namelist);
10 Collections.sort(list);
11 String[] paths = list.toArray(new String[0]);
12
13 return paths[0];
14 }
3) countFile()函数:当输入格式为【wf -c 文件名 】时调用,并输出结果
1 public void countFile() {
2 try {
3 FileInputStream inputStream = new FileInputStream(new File(path));
4 BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
5 //将文件内容存入words中
6 while((lineword=bufferedReader.readLine())!=null) {
7 words+=lineword+"\n";
8 }
9 //全部转换成小写,达到不区分大小写的目的
10 String str=words.toString().toLowerCase();
11 //分割字符串并存入数组
12 String[] word = str.split("[^a-zA-Z0-9]|\\ ");
13 int num =0;
14 Map<String,Integer> myMap = new TreeMap<String,Integer>();
15 //遍历数组将其存入Map<String,Integer>中
16 for(int i=0;i<word.length;i++) {
17 //首先判断是否为合法单词,合法则存入map中
18 if(isLegal(word[i])) {
19 if(myMap.containsKey(word[i])) {
20 num = myMap.get(word[i]);
21 myMap.put(word[i], num+1);
22 }
23 else {
24 myMap.put(word[i], 1);
25 }
26 }
27 }
28 //将map.entrySet()转换成list
29 List<Map.Entry<String, Integer>> list =new ArrayList<Map.Entry<String,Integer>>(myMap.entrySet());
30 //输出结果
31 System.out.println("total"+" "+list.size()+"\n");
32 // for(int i=0;i<list.size();i++) {
33 // Map.Entry<String, Integer> e =list.get(i);
34 // System.out.println(e.getKey()+" "+e.getValue());
35 // }
36 for(int i=0;i<word.length;i++) {
37 if(myMap.containsKey(word[i])) {
38 System.out.printf("%-14s%d\n",word[i],myMap.get(word[i]));
39 myMap.remove(word[i]);
40 }
41 }
42 bufferedReader.close();
43 }catch(FileNotFoundException e) {
44 e.printStackTrace();
45 }catch(IOException e) {
46 e.printStackTrace();
47 }
48 }
4) countDir()函数:当输入格式为【wf -f 文件路径 】时调用,并输出结果。(和countFile函数基本一致,只是输出格式上有些不同)
1 public void countDir() {
2 try {
3 FileInputStream inputStream = new FileInputStream(new File(path));
4 BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
5 //将文件内容存入words中
6 while((lineword=bufferedReader.readLine())!=null) {
7 words+=lineword;
8 }
9 //全部转换成小写,达到不区分大小写的目的
10 String str=words.toString().toLowerCase();
11 //分割字符串并存入数组
12 String[] word = str.split("[^a-zA-Z0-9]|\\ ");
13 int num =0;
14 Map<String,Integer> myMap = new TreeMap<String,Integer>();
15 //遍历数组将其存入Map<String,Integer>中
16 for(int i=0;i<word.length;i++) {
17 //首先判断是否为合法单词
18 if(isLegal(word[i])) {
19 if(myMap.containsKey(word[i])) {
20 num = myMap.get(word[i]);
21 myMap.put(word[i], num+1);
22 }
23 else {
24 myMap.put(word[i], 1);
25 }
26 }
27 }
28 //将map.entrySet()转换成list
29 List<Map.Entry<String, Integer>> list =new ArrayList<Map.Entry<String,Integer>>(myMap.entrySet());
30 //输出结果
31 System.out.println("total"+" "+list.size()+" words");
32 for(int i=0;i<list.size();i++) {
33 Map.Entry<String, Integer> e =list.get(i);
34 System.out.println(e.getKey()+" "+e.getValue());
35 }
36 bufferedReader.close();
37 }catch(FileNotFoundException e) {
38 e.printStackTrace();
39 }catch(IOException e) {
40 e.printStackTrace();
41 }
42 }
5) countNum()函数:当输入格式为【wf -f 文件路径 -n 数量】或者【wf -c 文件名 -n 数量】或者【wf -n 数量 -c 文件名】或者【wf -n 数量 -f 文件路径】时调用,并输出结果
1 public void countNum(int n) {
2 try {
3 FileInputStream inputStream = new FileInputStream(new File(path));
4 BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
5 //将文件内容存入words中
6 while((lineword=bufferedReader.readLine())!=null) {
7 words+=lineword+"\n";
8 }
9 //全部转换成小写,达到不区分大小写的目的
10 String str=words.toString().toLowerCase();
11 //分割字符串并存入数组
12 String[] word = str.split("[^a-zA-Z0-9]|\\ ");
13 int num =0;
14 Map<String,Integer> myMap = new TreeMap<String,Integer>();
15 //遍历数组将其存入Map<String,Integer>中
16 for(int i=0;i<word.length;i++) {
17 //首先判断是否为合法单词
18 if(isLegal(word[i])) {
19 if(myMap.containsKey(word[i])) {
20 num = myMap.get(word[i]);
21 myMap.put(word[i], num+1);
22 }
23 else {
24 myMap.put(word[i], 1);
25 }
26 }
27 }
28 //将map.entrySet()转换成list
29 List<Map.Entry<String, Integer>> list =new ArrayList<Map.Entry<String,Integer>>(myMap.entrySet());
30 //通过比较器实现排序
31 Collections.sort(list,new Comparator<Map.Entry<String, Integer>>(){
32 public int compare(Entry<String,Integer> e1,Entry<String,Integer> e2) {
33 return e2.getValue().compareTo(e1.getValue());
34 }
35 });
36 //输出结果
37 System.out.println("Total words is "+list.size());
38 System.out.println("----------");
39 for(int i=0;i<n;i++) {
40 Map.Entry<String, Integer> e =list.get(i);
41 System.out.printf("%-14s%d\n",e.getKey(),e.getValue());
42 }
43 bufferedReader.close();
44 }catch(FileNotFoundException e) {
45 e.printStackTrace();
46 }catch(IOException e) {
47 e.printStackTrace();
48 }
49 }
6)setpath()函数:创建对象时若没有传入路径,则给变量path赋值
public void setpath(String path) { this.path=path; }
7)最后,在wfTest类里,我通过 if else 语句对控制台输入的字符串分情况讨论,调用相应的方法
1 package wf;
2 import java.util.*;
3
4 public class wfTest{
5 public static void main(String[] args) {
6 Scanner input = new Scanner(System.in);
7 String str = "";
8 str = input.nextLine();
9 String[] splt = str.split(" ");
10 String path;
11 int num=splt.length;
12 if(num==3) {
13
14 if(splt[1].equals("-c")) {
15 path=splt[2];
16 wf test = new wf(path);
17 test.countFile();
18 }else if(splt[1].equals("-f")){
19
20 wf test = new wf();
21 path=test.readDir(splt[2]);
22 // System.out.println(splt[2]);
23 test.setpath(splt[2]+"\\"+path);
24 test.countDir();
25 }else {
26 System.out.println("输入格式有错误");
27 }
28
29 }else if(num==5) {
30 if(splt[1].equals("-f")&&splt[3].equals("-n")) {
31 wf test = new wf();
32 path=test.readDir(splt[2]);
33 test.setpath(splt[2]+"\\"+path);
34 test.countNum(Integer.parseInt(splt[4]));
35 }else if(splt[1].equals("-c")&&splt[3].equals("-n")) {
36 path=splt[2];
37 wf test = new wf(path);
38 test.countNum(Integer.parseInt(splt[4]));
39 }else if(splt[1].equals("-n")&&splt[3].equals("-c")) {
40 path=splt[4];
41 wf test = new wf(path);
42 test.countNum(Integer.parseInt(splt[2]));
43 }else if(splt[1].equals("-n")&&splt[3].equals("-f")) {
44 wf test = new wf();
45 path=test.readDir(splt[4]);
46 test.setpath(splt[4]+"\\"+path);
47 test.countNum(Integer.parseInt(splt[2]));
48 }else {
49 System.out.println("输入格式有错误");
50 }
51 }else {
52 System.out.println("输入格式有错误");
53 }
54
55 input.close();
56 }
57 }
8)运行结果展示:
此截图是在eclipse平台上运行的效果,但是项目里已经生成 wf.exe 执行文件,可以在控制台进行测试(这是我第一次用jar包通过exe4j生成可执行文件,因为在exe4j上忘记选择生成控制台程序(默认是gui程序),所以在这里纠结了好久,真的是。。。为了避免大家犯同样的错误,在此诚挚地推荐一篇相关博客:https://blog.csdn.net/u011752272/article/details/80697198)
作业输出样例:
测试输出样例:
3.个人感想
通过此次项目的经历,我觉得代码的规范性很重要。这次作业我相当于写了两个版本,最终的代码是在初期代码的基础上改了很多,这个改代码的时间真的快赶上我写出程序的时间了。一开始自己没想着如何整体对项目结构进行设计,就一步步按照自己的想法写完了,但是回过头去改的时候,发现结构有些乱,改的时候需要兼顾很多东西,真的不太好。这让我想到了《构建之法》第四章所讲的有关代码规范的重要性,现在看自己编写的代码都别扭与复杂,更别说在合作的过程中他人看自己的代码的感受了。希望自己可以通过此次作业,在这方面有所改进。