结对编程--四则运算(Java实现)

结对编程--四则运算(Java实现)

四则运算 (JAVA ) By 梅进鹏 欧思良

Github项目地址:https://github.com/MeiJinpen/Arithmetic

功能要求

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

功能(已全部实现)

  1. 使用 -n 参数控制生成题目的个数
  2. 使用 -r 参数控制题目中数值(自然数、真分数和真分数分母)的范围(此处感觉一个参数不太合理,因此改用 -r 参数控制自然数、带分数整数部分的范围,又额外添加了 -d 参数控制分数分母的范围,增加可行性
  3. 生成的题目中计算过程不能产生负数,也就是说算术表达式中如果存在形如e1 ? e2的子表达式,那么e1 ≥ e2
  4. 生成的题目中如果存在形如e1 ÷ e2的子表达式,那么其结果应是真分数
  5. 每道题目中出现的运算符个数不超过3个
  6. 程序一次运行生成的题目不能重复,即任何两道题目不能通过有限次交换+和×左右的算术表达式变换为同一道题目
  7. 在生成题目的同时,计算出所有题目的答案,并存入执行程序的当前目录下的Answers.txt
  8. 程序应能支持一万道题目的生成
  9. 程序支持对给定的题目文件和答案文件,判定答案中的对错并进行数量统计,统计结果输出到文件Grade.txt

设计实现

1. 数值生成

题目要求生成的算式中包含整数,带分数(如 2 ‘3/5 )以及真分数 (如 3/5 ),考虑到整数是特殊的分数,所以随机生成的数值全部先当作分数处理,只需要创建一个Fraction分数类,定义 int 类型成员变量分子分母s , m 即可( 当然,m不能为0 ) 程序默认随机生成整数,真分数,带分数的数量比例为2:1:1 。对于负数的处理,把它当作一个环节放在了算式生成的算法里,这里生成的都是正数

然后在Fraction内部生成一个handle方法,用于分数约分,假分数化带分数,整数等规范化处理,同时简单定义分数类的加减乘除运算方法用于数值运算,其中规定减法的结果不为负数,除法的除数不为0

2. 算式生成

在算式括号的处理和添加,算式的查重处理,以及给定表达式的求值计算等方面,经过长时间的讨论和尝试,最终还是选择了采用二叉树实现,用非叶子节点存放运算符,叶子节点存放数值,二叉树实现的优势在于:

  1. 运算的次序简单明了,孩子结点的运算次序要优先于根结点,单这一特点就能避免很多问题
  2. 给定字符串表达式求值,将中缀表达式转为前,后缀表达式几乎是不可避免的,这时将前,后缀表达式生成树(我们这里用的是前缀),计算结果时会更加高效和统一
  3. 采用二叉树实现,算式的查重就只需要判断树是否相同就可以了,并且,运算出现负数时,只需交换左右子树即可

    在这里就上述第一点展开一下(也是当时遇到的一个问题),对于生成表达式时括号的添加情况,当父母结点的算术符优先级高于右孩子结点算术符的时候,左右孩子树的数值运算都要加括号(下图 1);而当优先级相等,且同为除法或减法时,右孩子树的数值运算要加括号(如下图2 ,3 中 ,生成算术表达式的时候,a 与 b 的运算需要用括号括起来),而由于乘法和加法满足交换律,所以不需要考虑

? ( a , b 不一定要求是叶子节点)

完成算式生成这一块的功能,需要创建Node结点类用于生成二叉树,Exercise题目类用于生成题目,再在Operation操作类里添加详细的生成,查重,判错等方法

3. 其他

  • 程序所用到的常量EXERCISE_NUMBER ,EXERCISE_NUM_MAX ,EXERCISE_DER_MAX等都封装在常量类里
  • 文件读写操作均封装在FileUtil类
  • Main主类里存放main方法,内部的静态方法checkParams用于接收和命令判断

代码说明

分数类Fraction

其中 Fraction(String str),将数值字符串转具体分数实例的构造方法;String toString()用于将分数实例用字符串的形式输出;handle()用于约分等数值处理

/**
 * 分数属性类(包括整数)
 */
public class Fraction {
    private int s;  //分子
    private int m;  //分母
    /**
     * 数值分解成分数对象
     * @param str 表达式
     */
    public Fraction(String str) {
        int a = str.indexOf("‘");
        int b = str.indexOf("/");
        if(a != -1) {
            int z = Integer.valueOf(str.substring(0, a));
            m = Integer.valueOf(str.substring(b + 1));
            s = z * m + Integer.valueOf(str.substring(a + 1, b));
        } else if(b != -1) {
            String[] strs = str.split("/");
            s = Integer.valueOf(strs[0]);
            m = Integer.valueOf(strs[1]);
        } else {
            m = 1;
            s = Integer.valueOf(str);
        }
    }

    /**
     * 通过分子分母组合成分数对象
     * @param s 分子
     * @param m 分母
     */
    public Fraction(int s, int m) {
        this.s = s;
        this.m = m;
        handle();
    }

    /**
     * 约分
     */
    private void handle() {
        int mod = 1;
        int max = s > m ? s : m;
        for (int i = 1; i <= max; i++) {
            if(s % i == 0 && m % i == 0) {
                mod = i;
            }
        }
        this.s = s / mod;
        this.m = m / mod;
    }

    public boolean isZero() {
        return m == 0;
    }

    public Fraction add(Fraction fraction) {
        return new Fraction(this.s * fraction.m + this.m * fraction.s, this.m * fraction.m);
    }

    public Fraction multiply(Fraction fraction) {
        return new Fraction(this.s * fraction.s, this.m * fraction.m);
    }

    public Fraction subtract(Fraction fraction) {
        return new Fraction(this.s * fraction.m - this.m * fraction.s, this.m * fraction.m);
    }

    public Fraction divide(Fraction fraction) {
        return new Fraction(this.s * fraction.m, this.m * fraction.s);
    }

    public boolean isNegative() {
        return s < 0;
    }

    @Override
    public String toString() {
        if(m == 1) {
            return String.valueOf(s);
        } else {
            int z = 0;
            if(m != 0 && s > m) {
                z = s / m;
            }
            if(z == 0) {
                return String.valueOf(s) + "/" + String.valueOf(m);
            } else {
                return String.valueOf(z) + "‘" + String.valueOf(s % m) + "/" + String.valueOf(m);
            }
        }
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Fraction)) return false;
        Fraction fraction = (Fraction) o;
        return s == fraction.s &&
                m == fraction.m;
    }
}

题目类Exercise

构造方法Exercise中定义了参数isAuto,判断是否随机生成题目Node类的 build()方法用于生成题目实例对应的二叉树,减法结果的非负规范,以及是否添加括号统一都在这里处理;出现负数,就交换value值小于0的结点的左右孩子树

/**
 * 四则运算一道题目主要实现类
 */
public class Exercise {

    private Operation operation;
    private Node root;

    public Exercise(Operation operation, boolean isAuto) {
        this.operation = operation;
        if(isAuto) {
            ThreadLocalRandom random = ThreadLocalRandom.current();
            int kind = random.nextInt(4);
            if (kind == 0) kind = 1;
            root = build(kind);
            while (root.getValue().isZero()) {
                root = build(kind);
            }
        }
    }

    public Fraction getResult() {
        return root.getValue();
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Exercise)) return false;
        Exercise exercise = (Exercise) o;
        return root.equals(exercise.root);
    }

    @Override
    public int hashCode() {
        return root.hashCode();
    }

    /**
     * 随机生成一道四则运算题目
     *
     * @param num 运算符个数
     * @return 二叉树
     */
    public Node build(int num) {
        if (num == 0) {
            return new Node(createFraction(), null, null);
        }
        ThreadLocalRandom random = ThreadLocalRandom.current();
        SymbolNode node = new SymbolNode(Constant.SYMBOLS[random.nextInt(4)], null, null);
        int left = random.nextInt(num);   //左子树运算符数量
        int right = num - left - 1;   //右子树运算符数量
        node.setLeft(build(left));
        node.setRight(build(right));
        Fraction value = calculate(node.getSymbol(), node.getLeft().getValue(), node.getRight().getValue());
        if (value.isNegative()) {
            swapNode(node);
            value = calculate(node.getSymbol(), node.getLeft().getValue(), node.getRight().getValue());
        }
        node.setValue(value);
        return node;
    }
    /**
     * 打印题目和答案,例如:“( 1 + 2 ) x 3 = 9”
     */
    public String print() {
        return print(root) + " = " + root.getValue();
    }

    /**
     * 获取表达式,例如“( 1 + 2 ) x 3”
     */
    public String print(Node node) {
        if (node == null) {
            return "";
        }
        String mid = node.toString();
        String left = print(node.getLeft());
        if (node.getLeft() instanceof SymbolNode && node instanceof SymbolNode) {
            if (isNeedBracketsLeft(((SymbolNode) node.getLeft()).getSymbol(), ((SymbolNode) node).getSymbol())) {
                left = Constant.LEFT_BRACKETS + " " + left + " " + Constant.RIGHT_BRACKETS;
            }
        }
        String right = print(node.getRight());
        if (node.getRight() instanceof SymbolNode && node instanceof SymbolNode) {
            if (isNeedBracketsRight(((SymbolNode) node.getRight()).getSymbol(), ((SymbolNode) node).getSymbol())) {
                right = Constant.LEFT_BRACKETS + " " + right + " " + Constant.RIGHT_BRACKETS;
            }
        }
        return left + mid + right;
    }

    //判断是否为加减法
    private boolean isAddOrSub(String symbol) {
        return symbol.equals(Constant.ADDITION) || symbol.equals(Constant.SUBTRACTION);
    }

    //判断是否为乘除法
    private boolean isMulOrDiv(String symbol) {
        return symbol.equals(Constant.MULTIPLICATION) || symbol.equals(Constant.DIVISION);
    }

    //判断是否为减法
    private boolean isSubtract(String symbol) {
        return symbol.equals(Constant.SUBTRACTION);
    }

    private boolean isDivide(String symbol) {
        return symbol.equals(Constant.DIVISION);
    }

    /**
     * 比较两个符号中谁优先级最高,由于子树的符号优先级低需要加括号
     */
    private boolean isNeedBracketsLeft(String left, String mid) {
        return isAddOrSub(left) && isMulOrDiv(mid);
    }
    /*当右边需要加括号时,可以分为几种情况,第一种中间为除号时,右边所有运算都需要加括号,
    第二种中间为减号时,右边的加减法需要加括号,第三种优先级*/
    private boolean isNeedBracketsRight(String right, String mid) {
        return isAddOrSub(right) && isMulOrDiv(mid) || isDivide(mid) || isSubtract(mid) && isAddOrSub(mid);
    }

    /**
     * 单步计算
     */
    private Fraction calculate(String symbol, Fraction left, Fraction right) {
        switch (symbol) {
            case Constant.ADDITION:
                return left.add(right);
            case Constant.MULTIPLICATION:
                return left.multiply(right);
            case Constant.SUBTRACTION:
                return left.subtract(right);
            default:
                return left.divide(right);
        }
    }

    /**
     * 交换左右子树
     */
    private void swapNode(Node node) {
        if (node != null) {
            Node t = node.getLeft();
            node.setLeft(node.getRight());
            node.setRight(t);
        }
    }

    /**
     * 随机生成分数
     */
    private Fraction createFraction() {
        if (randomBoolean()) {
            return new Fraction(random(operation.maxNum), 1);
        } else {
            if (randomBoolean()) {
                int y = random(operation.derBound);
                int x = random(y * operation.maxNum);
                return new Fraction(x, y);
            } else {
                int y = random(operation.derBound);
                return new Fraction(random(y), y);
            }
        }
    }
}

另一个build(String)方法将题目字符串生成二叉树,用于结果计算。利用栈的特点,先将中缀表达式转前缀表达式,再计算算式的结果

/**
     * 中缀表达式生成树
     * @param exercise 中缀表达式
     * @return 二叉树
     */
    public Node build(String exercise) {
        String[] strs = exercise.trim().split(" ");
        Stack<Node> nodeStack = new Stack<>();
        StringStack symbolStack = new StringStack();
        //中缀表达式转换成前缀表达式,然后再用前序遍历生成数
        for (int i = strs.length - 1; i >= 0; i--) {
            String str = strs[i];
            if (!str.matches("[()+\\u00F7\\-x]")) {
                nodeStack.push(new Node(new Fraction(str)));
            } else {
                while (!symbolStack.empty() && (isMulOrDiv(symbolStack.peekString()) && isAddOrSub(str)
                        || str.equals(Constant.LEFT_BRACKETS))) {
                    String symbol = symbolStack.popString();
                    if(symbol.equals(Constant.RIGHT_BRACKETS)) {
                        break;
                    }
                    push(symbol, nodeStack);
                }
                if(str.equals(Constant.LEFT_BRACKETS)) {
                    continue;
                }
                symbolStack.pushString(str);
            }
        }
        while (!symbolStack.empty()) {
            push(symbolStack.popString(), nodeStack);
        }
        this.root = nodeStack.pop();
        return root;
    }

    /**
     * 将符号压入节点栈且计算结果
     */
    private void push(String symbol, Stack<Node> nodeStack) {
        Node left = nodeStack.pop();
        Node right = nodeStack.pop();
        SymbolNode node = new SymbolNode(symbol, left, right);
        node.setValue(calculate(symbol, left.getValue(), right.getValue()));
        nodeStack.push(node);
    }

还写了几个随机算法用于生成合适的随机数

private int random(int factor) {
        ThreadLocalRandom random = ThreadLocalRandom.current();
        int x = random.nextInt(factor);
        if (x == 0) x = 1;
        return x;
    }

    private boolean randomBoolean() {
        ThreadLocalRandom random = ThreadLocalRandom.current();
        return random.nextBoolean();
    }

题目存放实现类Node 及其子类

定义value表示结点的值(如果结点是数值结点则为本身,如果是符号则是子树运算后的结果),leftright指向左,右孩子,然后是一些简单的功能方法

/**
 * 记录每个节点的信息
 */
public class Node {

    private Fraction value;  //如果节点是数值则为本身,如果是符号则是运算后的结果

    private Node left;

    private Node right;

    public Node(Fraction value, Node left, Node right) {
        this.value = value;
        this.left = left;
        this.right = right;
    }

    public Node(Fraction value) {
        this.value = value;
    }
          .............

    @Override
    public String toString() {
        return value.toString();
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Node)) return false;
        Node node = (Node) o;
        return Objects.equals(value, node.value) &&
                Objects.equals(left, node.left) &&
                Objects.equals(right, node.right);
    }

    @Override
    public int hashCode() {
        return Objects.hash(value, left, right);
    }
}

子类SymbolNode用于记录符号结点,重写equals方法,用于判断两棵树是否相同

/**
 * 记录符号的节点
 */
public class SymbolNode extends Node {

    private String symbol;

    public SymbolNode(String symbol, Node left, Node right) {
        super(null, left, right);
        this.symbol = symbol;
    }

   ...........

    @Override
    public String toString() {
        return " " + symbol + " ";
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof SymbolNode)) return false;
        SymbolNode that = (SymbolNode) o;

        boolean flag = this.symbol != null && symbol.equals(that.symbol);
        if(!flag) return false;

        boolean left = this.getLeft() != null && getLeft().equals(that.getLeft());
        boolean right = this.getRight() != null && getRight().equals(that.getRight());
        //左右子树相同
        if(left && right) {
            return true;
        }
        if(left ^ right) {
            return false;
        }
        //如果是加法或乘法由于满足交换律所以要判断
        if(this.symbol.equals(Constant.ADDITION) || this.symbol.equals(Constant.MULTIPLICATION)) {
            left = this.getLeft() != null && getLeft().equals(that.getRight());
            right = this.getRight() != null && getRight().equals(that.getLeft());
        }
        return left && right;
    }

    @Override
    public int hashCode() {
        return Objects.hash(super.hashCode(), symbol);
    }
}

题目操作类Operation

Operation(Map<String, String> params)匹配功能,拿到参数;同时初始化线程池,同时写两份文件,提高读写效率

/**
 * 四则运算题目操作类
 */
public class Operation {

    int maxCount = 100;  //生成题目数量,默认100条
    int maxNum = 100;   //生成题目的数值最大值,默认为100
    int derBound = 20; //分母的范围,默认为20
    String exerciseFileName;  // 题目文件名
    String answerFileName;  // 答案文件名
    int correctCount;  //正确数目
    int wrongCount;    //错误数目

    //初始化
    public Operation(Map<String, String> params) {
        for (String str : params.keySet()) {
            if (str.equals(Constant.EXERCISE_NUMBER)) {
                maxCount = Integer.valueOf(params.get(str));
            } else if (str.equals(Constant.EXERCISE_NUM_MAX)) {
                maxNum = Integer.valueOf(params.get(str));
            } else if (str.equals(Constant.EXERCISE_FILE_NAME)) {
                exerciseFileName = params.get(str);
            } else if (str.equals(Constant.ANSWER_FILE_NAME)) {
                answerFileName = params.get(str);
            } else if (str.equals(Constant.EXERCISE_DER_MAX)) {
                derBound = Integer.valueOf(params.get(str));
            }
        }
    }

    private ExecutorService executor = Executors.newCachedThreadPool();

     public void generateExercisesAnswers() {...}
     public void checkExercises() {...}
     private String printResult(List<String> correctNums, List<String> wrongNums) {...}

}

generateExercisesAnswers()生成随机题目并查重,将已生成的题目(已判定无重复)加入题目链表list里,调用contains方法检测新生成的题目是否已重复直到生成给定数目的算式,并显示生成耗时

 /**
     * 生成随机题目
     */
    public void generateExercisesAnswers() {
        StringBuilder exercises = new StringBuilder();
        StringBuilder answers = new StringBuilder();
        List<Exercise> list = new ArrayList<>();
        long start = System.currentTimeMillis();
        for (int i = 1; i <= maxCount;) {
            Exercise exercise = new Exercise(this, true);
            if (!list.contains(exercise)) {
                String[] strs = exercise.print().split("=");
                exercises.append(i).append(". ").append(strs[0]).append("\n");
                answers.append(i).append(".").append(strs[1]).append("\n");
                list.add(exercise);
                i++;
            }
            long end = System.currentTimeMillis();
            if(end - start > 10000) {
                throw new RuntimeException("生成题目时间过长,可能传入参数范围过大或过小,请重试");
            }
        }
        executor.execute(() -> FileUtil.writeFile(exercises.toString(), Constant.EXERCISE_FILE_DEFAULT));
        executor.execute(() -> FileUtil.writeFile(answers.toString(), Constant.ANSWER_FILE_DEFAULT));
        //开启线程池执行任务后,关闭线程池释放资源
        executor.shutdown();
        try {
            boolean loop = true;
            while (loop) {
                loop = !executor.awaitTermination(30, TimeUnit.SECONDS);  //超时等待阻塞,直到线程池里所有任务结束
            } //等待所有任务完成
            long end = System.currentTimeMillis();
            System.out.println("生成的" + maxCount + "道题和答案存放在当前目录下的Exercises.txt和Answers.txt,耗时为" + (end - start) + "ms");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

public void checkExercises()结果判错,同时显示判错耗时

 public void checkExercises() {
        long start = System.currentTimeMillis();
        List<String> correctNums = new ArrayList<>();
        List<String> wrongNums = new ArrayList<>();
        FileUtil.readFile((exercise, answer) -> {
            String[] strs1 = exercise.split("\\.");
            String[] strs2 = answer.split("\\.");
            if(strs1[0].equals(strs2[0])) {
                Exercise exes = new Exercise(this, false);
                exes.build(strs1[1].trim());
                if(exes.getResult().equals(new Fraction(strs2[1].trim()))) {
                    correctNums.add(strs1[0]);
                    correctCount++;
                } else {
                    wrongNums.add(strs1[0]);
                    wrongCount++;
                }
            }
        }, exerciseFileName, answerFileName);
        FileUtil.writeFile(printResult(correctNums, wrongNums), Constant.GRADE_FILE_DEFAULT);
        long end = System.currentTimeMillis();
        System.out.println("题目答案对错统计存在当前目录下的Grade.txt文件下,耗时为:" + (end - start) + "ms");
    }

private String printResult()将检错结果写入文件

private String printResult(List<String> correctNums, List<String> wrongNums) {
        StringBuilder builder = new StringBuilder();
        builder.append("Correct: ").append(correctCount).append(" (");
        for (int i = 0 ;i < correctNums.size(); i++) {
            if (i == correctNums.size() - 1) {
                builder.append(correctNums.get(i));
                break;
            }
            builder.append(correctNums.get(i)).append(", ");
        }
        builder.append(")").append("\n");
        builder.append("Wrong: ").append(wrongCount).append(" (");
        for (int i = 0; i < wrongNums.size(); i++) {
            if(i == wrongNums.size() - 1) {
                builder.append(wrongNums.get(i));
                break;
            }
            builder.append(wrongNums.get(i)).append(", ");
        }
        builder.append(")").append("\n");
        return builder.toString();
    }

其他

Constant常量类

public class Constant {

    //参数
    public static final String EXERCISE_NUMBER = "-n";    //生成的题目数量
    public static final String EXERCISE_NUM_MAX = "-r";   //运算的数值大小, 2以上
    public static final String EXERCISE_DER_MAX = "-d";   //运算分数的分母范围
    public static final String EXERCISE_FILE_NAME = "-e"; //指定的题目文件名
    public static final String ANSWER_FILE_NAME = "-a";   //指定的答案文件名

    //默认生成的文件名
    public static final String ANSWER_FILE_DEFAULT = "Answers.txt";
    public static final String EXERCISE_FILE_DEFAULT = "Exercises.txt";
    public static final String GRADE_FILE_DEFAULT = "Grade.txt";

    public static final String ADDITION = "+";
    public static final String SUBTRACTION = "-";
    public static final String MULTIPLICATION= "x";
    public static final String DIVISION = "\u00F7";

    public static final String LEFT_BRACKETS = "(";
    public static final String RIGHT_BRACKETS = ")";

    public static final String[] SYMBOLS = {
            ADDITION, SUBTRACTION, MULTIPLICATION, DIVISION
    };
}

FileUtil文件类

public class FileUtil {

    /**
     * 写入文件中
     * @param content 写入内容
     * @param fileName 写入文件名
     */
    public static void writeFile(String content, String fileName) {
        File file = new File(fileName);
        try (BufferedWriter bw = new BufferedWriter(new FileWriter(file))){
            if(!file.exists()){
                file.createNewFile();
            }
            bw.write(content);
            bw.flush();
        } catch (IOException e) {
            System.out.println("文件操作失败...");
        }
    }

    /**
     * 读文件内容
     * @param callBack 回调接口,分别处理每一行
     * @param exerciseFileName 题目文件
     * @param answerFileName 答案文件
     */
    public static void readFile(ReaderCallBack callBack, String exerciseFileName, String answerFileName) {
        File exerciseFile = new File(exerciseFileName);
        File answerFile = new File(answerFileName);
        if(!exerciseFile.exists() || !answerFile.exists()) {
            System.out.println("文件不存在,请重试");
            return;
        }
        try (BufferedReader br1 = new BufferedReader(new FileReader(exerciseFileName));
            BufferedReader br2 = new BufferedReader(new FileReader(answerFileName))) {
            String line1, line2;
            while ((line1 = br1.readLine()) != null && (line2 = br2.readLine()) != null) {
                callBack.deal(line1, line2);
            }
        } catch (IOException e) {
            System.out.println("读取文件失败...");
        }
    }

    public interface ReaderCallBack {
        void deal(String exercise, String answer) throws IOException;
    }
}

Main主类

public class Main {

    public static void main(String[] args) {
        Map<String, String> params = checkParams(args);
        Operation operation = new Operation(params);
        if(params.containsKey("-e") && params.containsKey("-a")) {
            operation.checkExercises();
        } else if(params.containsKey("-n") || params.containsKey("-r") || params.containsKey("-d")) {
            operation.generateExercisesAnswers();
        } else {
            System.out.println("参数输入错误,请重试\n" + "必须输入参数:\n" + "-n: 生成题目的个数\n" +
                    "-r: 生成题目的数值大小\n" + "-e <exercisefile>.txt -a <answerfile>.txt: " +
                    "对给定的题目文件和答案文件,判定答案中的对错并进行数量统计");
        }
    }

    private static Map<String, String> checkParams(String... args) {
        Map<String, String> params = new HashMap<>();
        if(args.length == 0)  {
            throw new RuntimeException("必须输入参数:\n" + "-n: 生成题目的个数\n" +
                    "-r: 生成题目的数值大小\n" + "-e <exercisefile>.txt -a <answerfile>.txt: " +
                    "对给定的题目文件和答案文件,判定答案中的对错并进行数量统计");
        } else {
            if(args.length % 2 != 0) {
                throw new RuntimeException("参数格式输入错误...");
            }
            for (int i = 0; i < args.length; i = i + 2) {
                params.put(args[i], args[i+1]);
            }
        }
        return params;
    }
}

测试运行

生成10000道题目

部分题目和结果

9959. 3‘1/4 - 4/7
9960. 9‘7/8 - 1
9961. 11‘1/9 + 9 x 1
9962. 19 + 3/8
9963. 17 - 17
9964. 3/5 ÷ ( 18 + 16 )
9965. 4 + 2/5
9966. 7‘1/3 + 11
9967. ( 12 - 6 ) ÷ ( 14‘7/9 + 14 )
9968. 18 - ( 11 - 5 + 1 )
9969. 11‘3/4 ÷ ( 1 x 3 )
9959. 2‘19/28
9960. 8‘7/8
9961. 20‘1/9
9962. 19‘3/8
9963. 0
9964. 3/170
9965. 4‘2/5
9966. 18‘1/3
9967. 54/259
9968. 11
9969. 3‘11/12

完整题目答案链接:

点我查看完整题目 点我查看完整答案

将序号为1 ,3,5,9996,9998,10000的答案改成错误值,运行 -e -a

Grade.txt:

代码覆盖率:

PSP

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

项目小结

? 此次的结对编程,由梅进鹏同学负责前期编码,我主要负责后期的代码测试和问题修改,虽然是这样说,但是其实两个人每边都多少有所涉及。从一开始的讨论项目基本构思,到基础数值的定义和生成,再到采用二叉树存放算式,最后一步步完成项目,可以说这次的结对编程,通过两个人的讨论,使得项目的整体思路变得很清晰,编程也很有条理。事实证明,多讨论交流意见对编程的帮助很大,就如何查重这一点上,我起先是打算先检查结果,再一一比较算式的符号,而梅进鹏同学觉得这样效率不高,反复讨论后,最终选择用二叉树存放四则算式。而后的判错功能,起先我们都没有头绪,只好通过上网查阅资料,然后一起搞懂了一个栈的应用实例:中缀表达式转前缀表达式,可以说是收获颇多。而且,梅进鹏同学的编码经验很丰富,逻辑思维能力也很强,很多时候都是他在帮我讲解算法。

原文地址:https://www.cnblogs.com/osliang/p/9710705.html

时间: 2024-09-28 16:16:21

结对编程--四则运算(Java实现)的相关文章

结对编程 四则运算Java实现by 姚建东,李晓阳

GitHub地址:https://github.com/YJOED/Code/tree/master/Arithmetic/src 结对人:姚建东,李晓阳. 一.项目简介: 实现一个自动生成小学四则运算题目的命令行程序 二.项目要求: 使用 -n 参数控制生成题目的个数(完成) 使用 -r 参数控制题目中数值(自然数.真分数和真分数分母)的范围.该参数可以设置为1或其他自然数.该参数必须给定,否则程序报错并给出帮助信息.(完成) 生成的题目中计算过程不能产生负数,也就是说算术表达式中如果存在形如

结对编程——四则运算(第一周)

结对编程--四则运算(第一周) 需求分析 题目要求 一个命令行程序实现: 自动生成小学四则运算题目(加.减.乘.除) 支持整数 支持多运算符(比如生成包含100个运算符的题目) 支持真分数 统计正确率 需求理解 输入:需要计算的式子数.计算结果 输出:判断后的答案(true/false) 满足真分数的运算如:1/3 + 1/4 = 7/12 处理的运算符为 +, ?, ×, ÷ 能处理用户的输入,并判断对错,统计正确率. 设计思路 自动生成四则运算,需要用到随机数,用随机数随机生成数字,以及随机

作业四:结对编程——四则运算

项目名称:结对编程——四则运算(GUI) 同学小伙伴:张富彬 博客园地址:http://www.cnblogs.com/legend07/ GitHub代码地址:https://github.com/8561549/Arithmetic2.0/tree/master/Arithmetic2.0 需求分析: 首先是实现GUI的界面设计,主要有两个窗体,一个窗体进行参数设计,另一个窗体负责显示题目.两个窗体之间实现自由跳转.在参数设置窗口可以获取各个控件中的信息,能对出题窗口的出题情况进行限制,其中

结对编程-四则运算 第一周总结

结对编程-四则运算 第一周总结 小组成员 20172313 余坤澎 20172332 于欣月 20172326 康皓越 需求分析 可以生成不同种类的题目 根据用户的需求来生成不同的题目 计算时生成真分数和整数,且结果为分数形式输出 可以输出括号 可以判断用户的正确 设计思路 第一种 设计一个level1类,只负责加减 设计一个level2类,只负责乘除 设计一个level3类,只负责加减乘除 设计一个转化类,将上述的类生成的表达式转换成计算式 比对结果类 缺点 在将括号进行插入时出现问题,难以将

20172316 结对编程-四则运算 第一周 阶段总结

20172316 结对编程-四则运算 第一周 阶段总结 1.项目内容 以结对小组形式编写代码,实现以下功能: 1.自动生成题目 可独立使用(能实现自己编写测试类单独生成题目的功能) 可生成不同等级题目,类似于: 1级题目:2 + 5 = 10 - 5 = 之类的两个数,一个运算符的题目 2.题目运算(判题) 可独立使用 实现中缀表达式转为后缀表达式并计算 判断用户答题正误,并输出正确结果 3.支持真分数 可独立使用 实现分数算式的计算 4.题目去重(扩展需求,加分项) 可独立使用 实现对自动生成

20172316 结对编程-四则运算 第二周 阶段总结

20172316 结对编程-四则运算 第二周 阶段总结 1.项目内容 以结对小组形式编写代码,实现以下功能: 1.自动生成题目 2.题目运算(判题) 3.支持真分数 4.生成题目添加括号 5.题目去重(扩展需求,加分项) 6.文件处理(扩展需求,自行学习,加分项) 相关过程截图 最开始的代码名称十分不规范,有的使用拼音,有的使用英文,有的不知所云,其他组员第一次看见名称时不容易理解和衔接起来: 出现了两个程序之间符号表示的差异,对于式子的值的计算有很大影响: 结对评价 唐才铭 王文彬 关键代码解

结对编程-四则运算生成器(java实现)

 结对伙伴:陈振华  项目要求 1.题目:实现一个自动生成小学四则运算题目的命令行程序. 2.需求: 1. 使用 -n 参数控制生成题目的个数 2. 使用 -r 参数控制题目中数值(自然数.真分数和真分数分母)的范围.该参数可以设置为1或其他自然数.该参数必须给定,否则程序报错并给出帮助信息. 3. 生成的题目中计算过程不能产生负数,也就是说算术表达式中如果存在形如e1 ? e2的子表达式,那么e1 ≥ e2. 4. 生成的题目中如果存在形如e1 ÷ e2的子表达式,那么其结果应是真分数. 5.

【week2】结对编程-四则运算 及感想

首先我要说一下,我得作业我尽力了,但是能力有限,还需练习. 四则运算,改进代码流程: 1.手动输入算式(属于中缀表达式) 2.将中缀表达式转化成后缀表达式 生成out数组 3.一个操作数栈,一个运算符栈,按照后缀表达式计算方法将out中的字符分别放入两个栈 4.当操作数栈只有一个数时结束计算. 代码分段解析如下: 中缀变后缀: public List<String> SolveOrder(String[] in,HashMap<String,Integer>precedence){

结对编程-基于JAVA的GUI程序

在结队编程的开发中,为了更方便和队友的合作编程,我放弃了Golang选择使用Java进行项目的编写.另外使用Java还有两个好处就是Java自带GUI的包,另外在短信发送的程序编写上也很成熟. 这次的结对编程需求如下: 1.用户注册功能.用户提供手机号码,点击注册将收到一个注册码,用户可使用该注册码创建账户完成注册: 2.用户登录功能.将用户信息存储在txt文件中: 3.修改密码功能.输入两次密码后即可修改密码: 4.选择难度功能.用户点击选择题目难度和数量: 5.答题功能.用户输入题目数量后,