四则运算(Java)--温铭淇,付夏阳

GitHub项目地址

https://github.com/fxyJAVA/Calculation

四则运算项目要求:

程序处理用户需求的模式为:

Myapp.exe -n num -r size

Myapp.exe -e .txt -a .txt

  • 基本功能列表:

    (1)【实现】使用 -n 参数控制生成题目的个数。

    (2)【实现】使用 -r 参数控制题目中数值(自然数、真分数和真分数分母)的范围。

    (3)【实现】生成的题目中计算过程不能产生负数,也就是说算术表达式中如果存在形如e1 − e2的子表达式,那么e1 ≥ e2。

    (4)【实现】生成的题目中如果存在形如e1 ÷ e2的子表达式,那么其结果应是真分数。

    (5)【实现】每道题目中出现的运算符个数不超过3个。

    (6)【实现】程序一次运行生成的题目不能重复,即任何两道题目不能通过有限次交换+和×左右的算术表达式变换为同一道题目。生成的题目存入执行程序的当前目录下的Exercises.txt文件

    (7)【实现】在生成题目的同时,计算出所有题目的答案,并存入执行程序的当前目录下的Answers.txt文件。

    (8)【实现】程序应能支持一万道题目的生成。

    (9)【实现】程序支持对给定的题目文件和答案文件,判定答案中的对错并进行数量统计。

PSP表:

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

  

解题思路:

初看题时,眼前的一道难关就是分数的问题,真分数,假分数,分数计算等。刚开始我和搭档温,对这一步的实现,首先我和温是分开实现的,我是单纯用字符串,字符串切分进行结果的计算,十分繁杂,且不利于后续的功能实现:加括号,顺序随机化等。后来发现温将数字写成一个类,十分惊喜于这个巧妙的设计,java本身就是面向对象的语言,我却忽略了这个重要的点,纯用面向过程思维来考虑了。取长补短走过了这一道难关,将数字定义为一个类极大便利了我们后续功能的实现。后续遇到的判重问题,我想的是用HashMap的特性,一个key对应一个value,key是唯一key。如果我将算式的结果作为key,那么value就是算式本身。我们可知,结果不同的算式一定不相同,因此仅这一步我们就过滤掉了重复的问题,但因为此方法有一定的误判可能,于是在后续补上了判重的方法,来降低误判的可能。

设计实现过程

以main()为中心,通过cmd传入参数到main(String[] args)获取操作和文件路径,根据参数来判断进行何种操作 。生成算式则调用Utils的静态方法生成,要写文件和检查则调用FileUtils的静态方法生成,而Fraction类则贯穿其中,是功能实现的核心。

效能分析

因为测试的时候未发现生成算式和写文件效率并未出很低的情况,在生成算式和写文件时,以10000条数据为例,测试仅用了383毫秒,最坏情况也不超过2秒,暂无思路做到进一步的方法优化。

代码说明

Main.java,通过args获取要执行的操作:

根据程序处理用户需求的模式

1)Myapp.exe -n num -r size

生成题目,其中num为题数,size为生成数的大小

调用FileUtils.creatFile(Utils.createSuanShi(num,size))

2)Myapp.exe -e exercisefile.txt -a answerfile.txt

判断答案的正确率,其中exercisefile.txt为生成练习题的路径 、answerfile.txt为答案的路径

调用FileUtils.check(exercisePath,answerPath);

import java.io.IOException;

public class main {

    public static void main(String[] args) {
        int num = 0;
        int size = 0;
        String exercisePath = null;
        String answerPath = null;
        if(args.length != 0){
            for(int i = 0; i < args.length; i++){
                //"-n指令"
                if(args[i].equals("-n")) {
                    try {
                        num = Integer.parseInt(args[i+1]);
                    }catch (NumberFormatException e) {
                        System.out.println("参数有误!");
                    }catch (ArrayIndexOutOfBoundsException e){
                        System.out.println("参数有误!");
                    }
                }
                //"-r指令"
                if(args[i].equals("-r")) {
                    try {
                        size = Integer.parseInt(args[i+1]);
                    }catch (NumberFormatException e) {
                        System.out.println("参数有误!");
                    }catch (ArrayIndexOutOfBoundsException e){
                        System.out.println("参数有误!");
                    }
                }
                if(args[i].equals("-e")){
                    try{
                        exercisePath = args[i+1];
                    }catch (ArrayIndexOutOfBoundsException e){
                        System.out.println("参数有误!");
                    }
                }
                if(args[i].equals("-a")){
                    try{
                        answerPath = args[i+1];
                    }catch (ArrayIndexOutOfBoundsException e){
                        System.out.println("参数有误!");
                    }
                }
            }
            if(num != 0 && size != 0){
                FileUtils.creatFile(Utils.createSuanShi(num,size));
            }
            if(exercisePath != null && answerPath != null){
                try {
                    FileUtils.check(exercisePath,answerPath);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}


以下是相应的util类:
首先是创造算式集合方法 createSuanShi,在这里传入决定生成的算式数目,算式取值范围;
createFun则是生成算式方法,根据传过来的要求数目,while循环生成算式,放入HashMap。
这里算式结果作为key,算式本身作为value。这样做的目的是判重,当我生成的结果在hashmap中存在value,我就判定该算式不成立,算入重复,但这样做会有错误过滤。因而在该方法中又重新定义了判重的方法,思路为:先判断结果是否相同->再判断运算符数目是否相同且相等->最后判断运算数是否相等,相等则我认为是重复算式,不计入map中。
createFra为生成算数,同时判断是否要生成分数
whoBig为判断算数a,b的大小,以防出现结果为负数
最后简要说说加入()的时机,因为传参是数组的缘故,所以根据计算符号的先后位置来决定是否加入括号,例如:我传入了运算符顺序是:+,—,* 在运算到乘法的时候,因为我已经计算了+,-的结果,所以我在这里需要判断前两位操作符是什么,若为+,—,则代表着我需要加入乘法前,给已生成的算式加上()。其他情况皆以此类推。

import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashMap;

public class Utils {
    public static HashMap<String, String> createSuanShi(int createNum, int size) {
        if (size == 0) {
            size = 100;
        }
        HashMap<String, String> weWant = new LinkedHashMap<>();
        while (weWant.size() < createNum) {
            int opNum = (int) (Math.random() * 3) + 1;
            String[] op = new String[opNum];
            for (int j = 0; j < opNum; j++) {
                int temp = (int) (Math.random() * 4);
                switch (temp) {
                    case 0: {
                        op[j] = "+";
                        break;
                    }
                    case 1: {
                        op[j] = "-";
                        break;
                    }
                    case 2: {
                        op[j] = "*";
                        break;
                    }
                    case 3: {
                        op[j] = "÷";
                        break;
                    }
                }
            }
            String[] result = createFun(op, size).split("=");

            //判重
            if ((weWant.get(result[1]))!=null) {
                String whoAreYou = weWant.get(result[1]);
                int calChar =0;
                for (int a=0;a<op.length;a++) {
                    if (whoAreYou.contains(op[a]))
                        calChar++;
                }
                if(calChar == op.length) {
                    StringBuilder sb = new StringBuilder(whoAreYou);
                    while(sb.toString().contains("(")) {
                        sb.deleteCharAt(sb.lastIndexOf("("));
                    }
                    while(sb.toString().contains(")")) {
                        sb.deleteCharAt(sb.lastIndexOf(")"));
                    }

                    StringBuilder sb2 = new StringBuilder(result[0]);
                    while(sb2.toString().contains("(")) {
                        sb2.deleteCharAt(sb2.lastIndexOf("("));
                    }
                    while(sb2.toString().contains(")")) {
                        sb2.deleteCharAt(sb2.lastIndexOf(")"));
                    }

                    String[] numArr1 = sb.toString().split("[\\+\\-\\*\\÷]");
                    String numArr2 = Arrays.toString(sb2.toString().split("[\\+\\-\\*\\÷]"));
                    int xxx=0;
                    for (int b = 0;b<numArr1.length;b++) {
                        if(numArr2.contains(numArr1[b])) {
                            xxx++;
                        }
                    }
                    if(xxx==numArr1.length) {
                        continue;
                    }
                }
            }
            weWant.put(result[1], result[0]);
        }
        return weWant;
    }

    public static String createFun(String[] op, int size) {
        StringBuffer suanShi = new StringBuffer();
        //确定符号数
        int length = op.length;
        //初始化结果为0
        Fraction result = new Fraction(0, 1);
        for (String c : op) {
            switch (c) {
                case "+": {
                    if (suanShi.length() > 0 && suanShi != null) {
                        Fraction c1 = createFra(size);
                        result = result.add(c1);
                        if ((suanShi.toString().contains("*") || suanShi.toString().contains("÷")) && (int) (Math.random() * 2) == 0 && length == 2) {
                            suanShi.insert(0, c1.getFraction() + "+");
                        } else if (suanShi.toString().contains("*") && suanShi.toString().contains("÷") && (int) Math.random() * 3 == 0) {
                            suanShi.insert(0, c1.getFraction() + "+");
                        } else {
                            if (length == 3 && suanShi.toString().contains("÷") && op[0] == "-") {
                                suanShi.insert(0, "(");
                                suanShi.insert(suanShi.length(), ")");
                            }
                            suanShi.append("+" + c1.getFraction());
                        }
                    } else {
                        Fraction a = createFra(size);
                        Fraction b = createFra(size);
                        result = a.add(b);
                        suanShi.append(a.getFraction() + "+" + b.getFraction());
                    }
                    break;
                }
                case "-": {
                    if (suanShi.length() > 0 && suanShi != null) {
                        Fraction c1 = createFra(size);
                        boolean flag = whoBig(result, c1);
                        if (flag) {
                            result = c1.sub(result);
                        } else {
                            result = result.sub(c1);
                        }

                        if ((flag && suanShi.toString().contains("*") || suanShi.toString().contains("÷")) && (int) (Math.random() * 3) == 0 && (length == 2 || length == 3)) {
                            if (length == 3 && (suanShi.toString().contains("*") || suanShi.toString().contains("÷")) && op[1] == "+") {
                                suanShi.insert(0, "(");
                                suanShi.insert(suanShi.length(), ")");
                            }
                            suanShi.insert(0, c1.getFraction() + "-");
                        } else if (flag && suanShi.toString().contains("*") && suanShi.toString().contains("÷") && (int) (Math.random() * 3) == 0) {
                            if (length == 3 && (suanShi.toString().contains("*") || suanShi.toString().contains("÷")) && op[1].equals("+")) {
                                suanShi.insert(0, "(");
                                suanShi.insert(suanShi.length(), ")");
                            }
                            suanShi.insert(0, c1.getFraction() + "-");
                        } else {
                            if (flag) {
                                suanShi.insert(0, "(");
                                suanShi.insert(suanShi.length(), ")");
                                suanShi.insert(0, c1.getFraction() + "-");
                            } else {
                                suanShi.append("-" + c1.getFraction());
                            }
                        }
                        if (length == 3 && op[1].equals("-") && (op[2].equals("*") || op[2].equals("÷"))) {
                            suanShi.insert(0, "(");
                            suanShi.insert(suanShi.length(), ")");
                        }
                    } else {
                        Fraction a = createFra(size);
                        Fraction b = createFra(size);
                        if (whoBig(a, b)) {
                            result = b.sub(a);
                            suanShi.append(b.getFraction() + "-" + a.getFraction());
                        } else {
                            result = a.sub(b);
                            suanShi.append(a.getFraction() + "-" + b.getFraction());
                        }
                    }
                    break;
                }
                case "*": {
                    if (suanShi.length() > 0 && suanShi != null) {
                        Fraction c1 = createFra(size);
                        result = result.mul(c1);
                        if (length == 3 && (op[0].equals("+") || op[0].equals("-")) && (op[1].equals("+") || op[1].equals("-")) && !suanShi.toString().contains("(")) {
                            suanShi.insert(0, "(");
                            suanShi.insert(suanShi.length(), ")");
                        } else {
                            if ((length == 2 || length == 3) && ((suanShi.toString().contains("+") || suanShi.toString().contains("-"))) && !suanShi.toString().contains("(")) {
                                suanShi.insert(0, "(");
                                suanShi.insert(suanShi.length(), ")");
                            }
                            if (length == 3 && (suanShi.toString().contains("÷") && (suanShi.toString().contains("+") || suanShi.toString().contains("-"))) && !suanShi.toString().contains("((") && op[0] != "*") {
                                suanShi.insert(0, "(");
                                suanShi.insert(suanShi.length(), ")");
                            }
                        }
                        suanShi.append("*" + c1.getFraction());
                    } else {
                        Fraction a = createFra(size);
                        Fraction b = createFra(size);
                        result = a.mul(b);
                        suanShi.append(a.getFraction() + "*" + b.getFraction());
                    }
                    break;
                }
                case "÷": {
                    if (suanShi.length() > 0 && suanShi != null) {
                        Fraction c1 = createFra(size);
                        while (c1.getNumerator() == 0) {
                            c1 = createFra(size);
                        }
                        result = result.div(c1);
                        if ((length == 3 && (op[0].equals("+") || op[0].equals("-")) && (op[1].equals("+") || op[1].equals("-")) && !suanShi.toString().contains("("))) {
                            suanShi.insert(0, "(");
                            suanShi.insert(suanShi.length(), ")");
                        }

                        if ((length == 2 || length == 3) && (suanShi.toString().contains("+") || suanShi.toString().contains("-")) && (suanShi.toString().contains("*") && suanShi.toString().contains("÷"))) {
                            suanShi.insert(0, "(");
                            suanShi.insert(suanShi.length(), ")");
                        }
                        if (length == 3 && (suanShi.toString().contains("*")) && ((suanShi.toString().contains("+") || suanShi.toString().contains("-"))) && !suanShi.toString().contains("((") && !op[0].equals("*")) {
                            suanShi.insert(0, "(");
                            suanShi.insert(suanShi.length(), ")");
                        }
                        if (length == 3 && (suanShi.toString().contains("+") || suanShi.toString().contains("-")) && (op[2].equals("-") || op[2].equals("+"))) {
                            suanShi.insert(0, "(");
                            suanShi.insert(suanShi.length(), ")");
                        }
                        if (length == 3 && op[1].equals("÷") && (op[0].equals("+") || op[0].equals("-"))) {
                            suanShi.insert(0, "(");
                            suanShi.insert(suanShi.length(), ")");
                        }
                        suanShi.append("÷" + c1.getFraction());
                    } else {
                        Fraction a = createFra(size);
                        Fraction b = createFra(size);
                        while (b.getNumerator() == 0) {
                            b = createFra(size);
                        }
                        result = a.div(b);
                        suanShi.append(a.getFraction() + "÷" + b.getFraction());
                    }
                    break;
                }
            }
        }
        return suanShi.append("=" + (result.getNumerator() == 0 ? 0 : result.getFraction())).toString();
    }

    private static boolean whoBig(Fraction l, Fraction r) {

        boolean flag = false;
        if (l.getNumerator() * r.getDenominator() < l.getDenominator() * r.getNumerator()) {
            flag = true;
        }
        return flag;
    }

    private static Fraction createFra(int size) {
        if ((int) (Math.random() * 3) == 0) {
            return new Fraction((int) (Math.random() * size + 1), (int) (Math.random() * size) + 2);
        } else {
            return new Fraction((int) (Math.random() * size + 1), 1);
        }
    }
}

数字类,得意于将数字定义为一个类,且集合了加减乘除,约分等对分数的处理,使得后面的流程可以专注于业务需求上,计算方面只需丢给该类的方法即可完成运算。

public class Fraction {
	private int numerator;//分子
	private int denominator;//分母

	public Fraction(int a,int b){
		setNumeratorAndDenominator(a,b);
	}
	public void setNumeratorAndDenominator(int a, int b){  // 设置分子和分母
		int c = 1;
		if(a!=0) {
			c = largestCommonDivisor(a, b);// 计算最大公约数
		}
		numerator = a / c;
		denominator = b / c;
	}
	public int largestCommonDivisor(int a,int b){  // 求a和b的最大公约数
		if(a < b){
			int c = a;
			a = b;
			b = c;
		}
		int r = a % b;
		while(r != 0){
			a = b;
			b = r;
			r = a % b;
		}
		return b;
	}
	public int getNumerator(){
		return numerator;
	}
	public int getDenominator(){
		return denominator;
	}
	public String getFraction(){  //获得分数的表达,根据分子分母的大小关系是否化为带分数
		String str;
		if(denominator == 1) {  //分母为1,只看分子的大小
			str = String.valueOf(this.numerator);
		}else if(numerator == denominator){  //分子分母相等,输出1
			str = "1";
		}else if(numerator > denominator){  //分子大于分母,转化为带分数
			int roundNumber = numerator / denominator;
			int newNumerator = numerator - denominator * roundNumber;
			str = roundNumber + "'" + newNumerator + "/" + this.denominator;
		}else{  //否则按正常的分数输出
			str = this.numerator + "/" + this.denominator;
		}
		return str;
	}

	public Fraction add(Fraction r){  // 加法运算,通分后再运算
		int a = r.getNumerator();
		int b = r.getDenominator();
		int newNumerator = this.numerator * b + this.denominator * a;
		int newDenominator = this.denominator * b;
		Fraction result = new Fraction(newNumerator,newDenominator);
		return result;
	}

	public Fraction sub(Fraction r){  // 减法运算
		int a = r.getNumerator();
		int b = r.getDenominator();
		int newNumerator = numerator * b - denominator * a;
		int newDenominator = denominator * b;
		Fraction result = new Fraction(newNumerator,newDenominator);
		return result;
	}

	public Fraction mul(Fraction r){ // 乘法运算
		int a = r.getNumerator();
		int b = r.getDenominator();
		int newNumerator = this.numerator * a;
		int newDenominator = this.denominator * b;
		Fraction result = new Fraction(newNumerator,newDenominator);
		return result;
	}

	public Fraction div(Fraction r){  // 除法运算
		int a = r.getNumerator();
		int b = r.getDenominator();
		int newNumerator = numerator * b;
		int newDenominator = denominator * a;
		Fraction result = new Fraction(newNumerator,newDenominator);
		return result;
	}
}

FileUtils类主要用于生成文件,和读取文件
createFile为生成文件方法,前面通过utils方法获得算式集,迭代获取算式和结果值,通过字符流(PrintWriter)分别写入到两个文件:Exercises@日期.txt Answers@日期
check方法主要用于比对答案是否正确,通过获取传进来的question获取对应的答案文件读取答案与读取到的yourAnswer文件匹配,匹配结果写入到grade文件中,以及输出到控股台。

import java.io.*;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Map;

public class FileUtils {
    public static void creatFile(HashMap map) {
        LocalDateTime time = LocalDateTime.now();
        String timeFor = time.format(DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm-ss"));
        File question = new File("D:\\Exercises@"+timeFor+".txt");
        File answer = new File("D:\\Answers@"+timeFor +".txt");
        try {
            question.createNewFile();
            answer.createNewFile();
        } catch (IOException e) {
            e.printStackTrace();
        }

        try (PrintWriter questionWrite = new PrintWriter(new FileWriter(question, true));
             PrintWriter answerWrite = new PrintWriter(new FileWriter(answer, true));) {
            int jiShu = 0;
            for (Object entry : map.entrySet()) {
                Map.Entry<String, String> temp = (Map.Entry<String, String>) entry;
                String key = temp.getKey();
                String value = temp.getValue();
                questionWrite.write(jiShu+1+".    "+value+"= ");
                questionWrite.write("\r\n");
                questionWrite.write("\r\n");
                answerWrite.write(jiShu+1+".    "+key);
                answerWrite.write("\r\n");
                answerWrite.write("\r\n");
                jiShu++;
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void check(String question,String yourAnswer) throws IOException {
        String answers = question.split("@")[1];
        File answersOff =  new File("D:\\Answers@"+answers);
        File yourAnswers = new File(yourAnswer);
        if(!yourAnswers.exists()) {
            System.out.println("未找到文件,请重新确认文件路径");
            System.exit(0);
        }
        if(!answersOff.exists()) {
            System.out.println("答案文件不存在,请确认");
            System.exit(0);
        }
        LocalDateTime time = LocalDateTime.now();
        String timeFor = time.format(DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm-ss"));
        File grade = new File("D:\\Grade@"+timeFor+".txt");
        grade.createNewFile();
        try(BufferedReader answersOffReader = new BufferedReader(new FileReader(answersOff));
            BufferedReader yourAnswersReader = new BufferedReader(new FileReader(yourAnswers));
            PrintWriter gradeWrite = new PrintWriter(new FileWriter(grade))) {
            String temp ;
            String ans ;
            StringBuilder r = new StringBuilder();
            r.append("(");
            StringBuilder w = new StringBuilder();
            w.append("(");
            int right = 0;
            int wrong = 0;
            ans=answersOffReader.readLine();
            while ((temp=yourAnswersReader.readLine())!=null ) {
                if(temp.equals("\r\n") || temp.equals("")) {
                    continue;
                }
                while(ans==null || ans.equals("")) {
                    ans=answersOffReader.readLine();
                }
                String xuHao =ans.split("\\.")[0];
                if(temp.split("\\.").length>1) {

                    ans =ans.split("\\.")[1].trim();
                    String tempAnswer = temp.split("\\.")[1].trim();
                    if(tempAnswer.equals(ans)) {
                        r.append(xuHao+",");
                        right++;
                    }else {
                        w.append(xuHao+",");
                        wrong++;
                    }
                }else {
                    w.append(xuHao+",");
                    wrong++;
                    continue;
                }
                ans = answersOffReader.readLine();
            }
            if(r.toString().contains(",")) {
                r.deleteCharAt(r.lastIndexOf(","));
            }
            if(w.toString().contains(",")) {
                w.deleteCharAt(w.lastIndexOf(","));
            }
            r.insert(r.length(),")");
            w.insert(w.length(),")");
            gradeWrite.write("Correct:"+right+r.toString());
            gradeWrite.write("\r\n");
            gradeWrite.write("Wrong:"+wrong+w.toString());
            System.out.println("Correct:"+right+r.toString());
            System.out.println("Wrong:"+wrong+w.toString());
        }
    }

}

测试运行

答题者的答案需另起一个文档,格式参照正确答案
命令行:

题目:

答案:

判断答案:

生成文件:

覆盖率:

项目小结

通过这次项目实战,我们又复习了javaSE的一些知识,比如java的集合类HashMap,文件的输入输出流,面向对象等知识,还有些细节性的一些知识点,比如为什么用StringBuilder而不用STring和StringBuffer等。又因为是两人合作开发,在开发时就不能
一意孤行,要结合队友的思路,彼此交流,言语表达要使对方能理解你的思路,探讨不足和取长补短,这样才能进行下一步的深入研究,也才有了我们现在完成的项目。通过此次开发,我们都体会到了合作开发的优势,开始有了团队意识,写代码时,会留
意注释是否到位等,另外一项收获就是如何进行项目管理,版本控制,防止开发时出现版本冲突等问题,较好的进行版本迭代。

posted @ 2018-09-30 16:47  happyOwen  阅读(269)  评论(0编辑  收藏  举报