结对作业——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、描述结对的过程,提供非摆拍的两人在讨论的结对照片

  • 在此次结队过程中,我收获良多。与小伙伴一起的结队编程,带来的不仅是编程思想的对碰融合,更让我们在相互磨合、相互学习中深刻体会到合作带来的好处。我们相互交流,相互学习,分享经验。我看到了小伙伴与我完全不一样的思路和技能。在遇到不懂的地方我能得到小伙伴的讲解,增长知识。

  • 审查题目要求,我们从新增功能的要求入手。针对一个功能函数,我们会先各自思考,阐述彼此的思路,说明这样做的原因和好处。哪一个更好,我们就采取哪一个的。比如在讨论词组时,我们一起琢磨词组的定义,进一步确定获取一个完整词组的方法,再次基础上,会琢磨如何根据用户指定的词组长度去获得一个完整词组。当将基本设计思路确定以后会开始动手编程,一个人先根据讨论好的思路编程,另一个人会在旁边看着,注意她编程过程中出现的如变量命名不规范的问题。当部分代码的测试出现错误时,我们会一起寻找出错原因,并讨论如何修改它。我们发现,两个人找错的效律会大于一个人的效律。同时我们会在实现过程中提出一些改进的意见和建议。比如对用户输入的词组长度的判断处理,在设计时没有考虑到当词组长度大于单词总数的情况,实现的时提了出来,恍然大悟应该注意到这种情况。当无法继续编程下去的时候,我们会互换角色。总之,我们在结对合作的过程中彼此受益。

posted @ 2018-10-07 19:04  番薯不甜  阅读(406)  评论(1编辑  收藏  举报