四则运算表达式生成器(java实现)

github地址:https://github.com/wyf973733114/JDHomeWork_QZ_YF

项目合作者:魏宇峰:3118004974 黎其钻:3118004966

一、题目

实现一个自动生成小学四则运算题目的命令行程序

二、需求

  1. 使用 -n 参数控制生成题目的个数(题目不可重复)
  2. 使用 -r 参数控制题目中数值(自然数、真分数和真分数分母)的范围
  3. 对给定的题目文件和答案文件,判定答案中的对错并进行数量统计,输入参数如下:
    Myapp.exe -e .txt -a .txt
    注意:
    • 生成的题目存入执行程序的当前目录下的Exercises.txt文件,格式如下:(已实现)
      四则运算1
      四则运算2
    • 计算出所有题目的答案,并存入执行程序的当前目录下的Answers.txt文件,格式如下:(已实现)
      答案1
      答案2
    • 统计结果输出到文件Grade.txt,格式如下:(已实现)
      Correct: 5 (1, 3, 5, 7, 9)
      Wrong: 5 (2, 4, 6, 8, 10)
    • d. 程序能支持一万道题目以上的生成 (已实现)

三、git版本迭代

四、PSP表格

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

五、流程图

六、效能分析

  1. 代码覆盖率
  2. 展示你程序中消耗最大的函数
    生成不重复的公式(其中生成三参数的公式消耗是二参数的两倍多)

七、代码调用关系图

八、关键代码实现

生成不重复的二参数公式

    Formula2 generator() {
        // 生成两个随机数num1、num2 和符号symbol
        String symbol = RandomSymbol.randomSymbol();
        int num1 = random.nextInt(scope);  // 第一个数
        int num2;    // 第二个数小于或等于第一个数
        if (symbol == "÷"){
            // 除法除数不为0
            num2 = (int) (1 + Math.random()*( num1 -1 +1));
        }else{
            num2 = random.nextInt(num1 + 1);
        }

        // 判断是否包含第一个参数
        if (recordMapTwo.containsKey(num1)){
            Map<String, Set<Integer>> symToNum2 = recordMapTwo.get(num1);

            // 包含符号则继续查询
            if (symToNum2.containsKey(symbol)){
                Set<Integer> num2s = symToNum2.get(symbol);

                // 判断是否包含第二个参数
                if (num2s.contains(num2)){
                    // 和已有记录重复,重新生成
                    //System.out.println("生成失败!"+ num1 + symbol + num2);
                    return null;
                }else{
                    num2s.add(num2);    // 不存在则添加
                }

            }else{
                // 不存在则添加
                Set<Integer> num2s = new HashSet<>();    // 第二个数的集合
                num2s.add(num2);

                symToNum2.put(symbol, num2s); // 添加联系
            }

        }else{
            // 不含有则增加记录
            Set<Integer> num2s = new HashSet<>();    // 第二个数的集合
            num2s.add(num2);

            Map<String, Set<Integer>> symToNum2 = new HashMap<>();  // 符号到第二个数集合的映射
            symToNum2.put(symbol, num2s);

            // 第一个数到符号的映射
            recordMapTwo.put(num1,symToNum2);
        }
        //System.out.println("生成成功!"+ num1 + symbol + num2);
        Boolean randomSwap = Boolean.FALSE;
        if((!Objects.equals(symbol, "-") || !((symbol == "÷") && (num2 == 0))) && random.nextBoolean()){
            randomSwap = Boolean.TRUE;
        }

        Formula2 formula2 = new Formula2(num1, symbol, num2, randomSwap);
        if ((MathMethon.translateResult(formula2.result) < 0) || (MathMethon.translateResult(formula2.result) >= scope)){
            return null;    // 参数超过范围
        }

        return formula2;
    }

检查答案是否正确

    static boolean BooleanAnswer(ArrayList<String> exercise, ArrayList<String> result ) {
		int resultInBrackets = 0;	// 括号内计算的结果
		int left = 0, right = 0, i = 0, index = 0;	// 左右括号的索引
		String item = "";
		Formula2 formula2;
		
		// 获取左右括号的位置
		for(i = 0; i < exercise.size(); i++) {
			if(exercise.get(i).equalsIgnoreCase("(")) 
				left = i;
			if(exercise.get(i).equalsIgnoreCase(")"))
				right = i;
		}
		
		// 如果没有括号,说明是二元运算
		if(left == 0 && right == 0) {
			formula2 = new Formula2(Integer.parseInt(exercise.get(0)), exercise.get(1), Integer.parseInt(exercise.get(2)), false);
			return formula2.result.equalsIgnoreCase(result.get(0));
		}
		
		// 否则就是三元运算,先计算出括号内的结果
		formula2 = new Formula2(Integer.parseInt(exercise.get(left + 1)), exercise.get(left + 2), Integer.parseInt(exercise.get(right - 1)), false);
		resultInBrackets = Integer.parseInt(formula2.result);
		// 将括号内结果与另一数值进行运算
		for(int j = 0; j < exercise.size(); j++) {
			item = exercise.get(j);
			if(j >= left && j <= right)	// 跳过括号区域
				continue;
			if(!isSymbol(item))	// 让索引到达运算符处
				continue;
			
			if((j > exercise.size() / 2)) {
				index = j + 1;
				formula2 = new Formula2(resultInBrackets, item, Integer.parseInt(exercise.get(index)), false);
			}
			else {
				index = j - 1;
				formula2 = new Formula2(Integer.parseInt(exercise.get(index)), item, resultInBrackets, false);
			}
			return formula2.result.equalsIgnoreCase(result.get(0));
		}
		return false;
	}

九、测试运行

1. -n,-r生成不重复代码

2. -e校验答案


将Answer.txt中第3题的答案改为2333后

3. 生成10000道题目

4. 错误输入

十、项目总结

魏宇峰:

  1. 第一次合作沟通成本比较高,写代码的时间和讨论的时间大概对半分。关于实现思路进行讨论时会有突然迸发的灵感,有个大概的实现思路,知道整个项目的大概方向,但是具体实现细节需要沉淀下来好好想想。
  2. 利用哈希表来记录曾生成的公式,避免由遍历产生的性能浪费,加深了对数据结构的理解。第一次写JAVA,语法还没有很熟练,没能熟练利用语言特性,代码里还有许多可优化的地方。

黎其钻:

  1. 沟通最重要。每个人对于题目的理解是不一样的,要创建什么样的函数,返回怎样的数据类型,哪个功能需要单独抽离出来,都需要花时间去沟通。
  2. 权衡好开发效率与学习成本。由于我们两人的技术栈并不相同,两个都会的只有C语言,但是C语言的开发效率并不理想,因此决定共同学习一门新语言JAVA。这意味着学习成本大幅提升,综合来看开发效率提升并不明显。
  3. 由于是第一次使用Java,很多功能都是一边查文档一边写,对于性能的考虑很少,代码耦合度较高,很多地方还可以进行优化。
  4. 由于是团队协作,我们基本把git的坑全部踩了一遍,花费了大量时间在解决分支冲突上。对于git项目管理有了很深的认识。
posted @ 2020-04-13 10:28  Caines  阅读(737)  评论(0编辑  收藏  举报