结对作业——WordCount进阶版
结对作业——WordCount进阶版
1. 地址
2.结对的PSP表格。
PSP2.1 | 开发流程 | 预估耗费时间(分钟) | 实际耗费时间(分钟) |
---|---|---|---|
Planning | 计划 | 30 | 25 |
· Estimate | 明确需求和其他相关因素,估计每个阶段的时间成本 | 10 | 20 |
Development | 开发 | 200 | 240 |
· Analysis | 需求分析 (包括学习新技术) | 20 | 20 |
· Design Spec | 生成设计文档 | 10 | 15 |
· Design Review | 设计复审 | 10 | 45 |
· Coding Standard | 代码规范 | 30 | 25 |
· Design | 具体设计 | 30 | 50 |
· Coding | 具体编码 | 150 | 180 |
· Code Review | 代码复审 | 10 | 15 |
· Test | 测试(自我测试,修改代码,提交修改) | 30 | 55 |
Reporting | 报告 | 60 | 85 |
· | 测试报告 | 30 | 30 |
· | 计算工作量 | 30 | 25 |
· | 并提出过程改进计划 | 30 | 15 |
3、解题思路描述。
关于这次的结对项目,我们仔细阅读并思考了题目的要求,明白了我们要做的是:实现core模块,实现词组统计,实现自定义输出以及程序的gui界面。
-
core模块
该功能的实现难点主要在于对用户一行输入参数的读取分析,不同命令行参数可以随意组合。可以考虑将输入的命令行字符串进行分割,用字符串数组存储每一个字符,if判断进行实现或用switch选择结构进行实现。 -
词组统计
对于指定长度的词组,我们将文章中的单词按序存储(除去分割符),再根据用户指定的长度,顺序输出对应的单词数。 -
自定义输出
能输出用户指定的前n多的单词与其数量。涉及单词的词频,对于词组的词频,在将单词按序存储时建立map集合将单词存入,以单词频数作为value值。
同时考虑到:输入的词组长度不应超过单词组长度,否则输出提示。 -
gui界面
再将项目要求的功能基本实现后,进行gui编程,设计一个简单便捷的图形界面。
4、设计实现过程
在实现过程中,我与小伙伴主要设计了三个类,包括读写文件的FileFunction类,对单词存储、排序、输出等操作的word类,以及完成输入、输出操作的Main函数。
- 以下是新增功能的主要设计实现过程:
1、core模块
读取用户输入的一行有多个命令行参数的命令,利用正则表达式将字符串分割,存入一个字符串数组中。将数组内容存入列表List中。循环读取List中的内容,每读取一个字符串,利用if结构判断其与支持的功能参数是否对应,满足条件就执行相应的功能。
读到-i: 将后一个带文件名/文件路径的字符串赋给变量file。
读到-m:将后一个要读取的字符串进行类型转换,获得设置的词组长度。
读到-n:将后一个要读取的字符串进行类型转换,获得最终要输出的单词的个数。
读到-o:后一个字符串内容即为输出结果的文件名/文件路径。
Scanner sc = new Scanner(System.in);
String file = null;
int numM =0;\\词组长度
int numN =0;\\输出的单词个数
String resultFile= null;\\生成文件路径
String str = sc.nextLine();
String strarr[] = str.split("\\s+");\\空格分割字符串
List<String> list = new ArrayList<>();
for (String word:strarr) {
list.add(word);
}
// 分析对应的命令参数并执行对应功能
for (int i = 0; i <list.size() ;i=i+2) {
if (list.get(i).equals("-i")){
file = list.get(i+1);
}
if (list.get(i).equals("-m")){
numM = Integer.parseInt(list.get(i+1));
}
if (list.get(i).equals("-n")){
numN = Integer.parseInt(list.get(i+1));
}
if (list.get(i).equals("-o")){
resultFile = list.get(i+1);
}
}
2、词组统计
当读取用户指定的词组长度,就调用单词操作类word中的Phrase函数。全局变量list即为单词表。字符串数组wordGroup用于存放词组,数组长度为词组的数量(单词表总数-词组长度+1),利用双重循环进行拼接单词获得词组。考虑到输出的词组应不重复,并统计词组频数,采用集合map进行操作,频数为value值。重写Collections的sort函数将集合进行排序。
public String Phrase(int number) throws Exception{
if(number>lists.size()) {
return "the number is too long";
}
String wordGroup[] = new String[lists.size()-number+1];
for (int i = 0; i <lists.size()-number+1 ; i++) {
String phrase = lists.get(i);
for (int j = i+1; j <i+number ; j++) {
phrase = phrase+" "+lists.get(j);
}
wordGroup[i] = phrase ;
}
Map<String,Integer> treeMap =new TreeMap<String,Integer>();
for (int i = 0; i < wordGroup.length; i++) {
String s = wordGroup[i];
if (!treeMap.containsKey(s)) {
//不存在该词组,存入集合
treeMap.put(s, 1);
} else {
//存在将对应value加一
int n = treeMap.get(s);
treeMap.put(s, n + 1);
}
}
//统计词组频数
List<Map.Entry<String, Integer>> list = new ArrayList<Map.Entry<String, Integer>>(treeMap.entrySet());
Collections.sort(list, ((o1, o2) -> o2.getValue().compareTo(o1.getValue())));
String s[] = new String[wordGroup.length];
int i =0;
for (Map.Entry<String,Integer>entry:list) {
String key = entry.getKey();
Integer value = entry.getValue();
s[i] = key + ": " + value;
i++;
}
String string = s[0]+"\n";
for (int j = 1; j <i ; j++) {
string = string + s[j] + "\n";
}
return string ;
}
3、自定义输出
该功能与个人作业中的“统计词频并输出频数前10的单词”有相似的地方。当对单词完成存储后,利用集合对所有单词进行排序,最后再根据用户指定的输出单词数n,按序输出集合前n个单词与词频。
sortWord函数中增加指定的输出单词数n,作为传入参数,即可完成自定义输出。
public String sortWord(int number) throws Exception{
Map<String, Integer> treeMap = new TreeMap<String, Integer>();
for (int i = 0; i < lists.size(); i++) {
String s = lists.get(i).toLowewerCase();//忽略大小写
if (!treeMap.containsKey(s)) {
//不存在,就存入集合中
treeMap.put(s, 1);
} else {
//存在就将value值加一
int n = treeMap.get(s);
treeMap.put(s, n + 1);
}
}
//单词与频数排序
List<Map.Entry<String, Integer>> list = new ArrayList<Map.Entry<String, Integer>>(treeMap.entrySet());
Collections.sort(list, ((o1, o2) -> o2.getValue().compareTo(o1.getValue())));
// int n = 10;
String s[] = new String[number];
int i =0;
for (Map.Entry<String, Integer> entry : list) {
if (number-- == 0)
break;
String key = entry.getKey();
Integer value = entry.getValue();
s[i] = "<" + key + ">:" + value;
i++;
}//拼接字符串
String string = s[0]+"\n";
for (int j = 1; j <i ; j++) {
string =string+ s[j]+"\n";
}
return string;
}
- FileFunction类
包含读取文件和写出文件操作。将文件路径作为传入参数。
public BufferedReader ReadFromFile(String path) throws IOException {
//读取文件
File file = new File(path);
if (!file.exists() || file.isDirectory()) {
System.out.println("文件路径不存在");
throw new FileNotFoundException();
}
InputStreamReader isr = new InputStreamReader(new FileInputStream(path));
BufferedReader br = new BufferedReader(isr);
return br;
}
public void WriteToFile(String path,String content) throws Exception {
//写出文件
try {
OutputStream out = new FileOutputStream(path);
out.write(content.getBytes());
out.close();
} catch (Exception e) {
e.printStackTrace();
}
}
- 图形界面设计
利用JFrame等简单设计了程序的图像界面。如下图
5、单元测试
对于各个功能模块分别提供一些测试样例进行测试。
- 以下是我们进行的一些单元测试
- FileFunctionTest:读取文件和写出文件操作FileFunction类的测试
class FileFunctionTest {
FileFunction fi=new FileFunction();
@Test
void testReadFromFile() throws IOException {
String path="input.txt";
assertTrue(fi.ReadFromFile(path).equals("Monday Tuesday Wednesday Thursday"));
}
@Test
void testWriteToFile() throws Exception {
String outpath="output.txt";
fi.WriteToFile(outpath, "hello i am test");
fi.ReadFromFile(outpath).equals( "hello i am test");
}
}
测试截图:
- word类的单元测试
6、描述结对的过程,提供非摆拍的两人在讨论的结对照片
-
在此次结队过程中,我收获良多。与小伙伴一起的结队编程,带来的不仅是编程思想的对碰融合,更让我们在相互磨合、相互学习中深刻体会到合作带来的好处。我们相互交流,相互学习,分享经验。我看到了小伙伴与我完全不一样的思路和技能。在遇到不懂的地方我能得到小伙伴的讲解,增长知识。
-
审查题目要求,我们从新增功能的要求入手。针对一个功能函数,我们会先各自思考,阐述彼此的思路,说明这样做的原因和好处。哪一个更好,我们就采取哪一个的。比如在讨论词组时,我们一起琢磨词组的定义,进一步确定获取一个完整词组的方法,再次基础上,会琢磨如何根据用户指定的词组长度去获得一个完整词组。当将基本设计思路确定以后会开始动手编程,一个人先根据讨论好的思路编程,另一个人会在旁边看着,注意她编程过程中出现的如变量命名不规范的问题。当部分代码的测试出现错误时,我们会一起寻找出错原因,并讨论如何修改它。我们发现,两个人找错的效律会大于一个人的效律。同时我们会在实现过程中提出一些改进的意见和建议。比如对用户输入的词组长度的判断处理,在设计时没有考虑到当词组长度大于单词总数的情况,实现的时提了出来,恍然大悟应该注意到这种情况。当无法继续编程下去的时候,我们会互换角色。总之,我们在结对合作的过程中彼此受益。