个人项目实战-四则混合运算

 

项目地址https://git.dev.tencent.com/sunjj942/sunjj942Homework1.git

1、实验目的与要求

1)掌握软件项目个人开发流程。

2)掌握Coding.net上发布软件项目的操作方法。

2、实验内容和步骤

任务1

    使用JAVA编程语言,独立完成一个35个运算符的四则运算练习的软件。

软件基本功能要求如下:

  • 程序可接收一个输入参数n,然后随机产生n道加减乘除(分别使用符号+-*÷来表示)练习题,每个数字在 0 和 100 之间,运算符在3个到5个之间。
  • 每个练习题至少要包含2种运算符。同时,由于小学生没有分数与负数的概念,你所出的练习题在运算过程中不得出现负数与非整数,比如不能出 3÷5+2=2.6,2-5+10=7等算式。
  • 练习题生成好后,将你的学号与生成的n道练习题及其对应的正确答案输出到文件“result.txt”中,不要输出额外信息,文件目录与程序目录一致。
  • 当程序接收的参数为4时,以下为一个输出文件示例。

2018010203

13+17-1=29

11*15-5=160

3+10+4-16=1

15÷5+3-2=4

软件附加功能要求如下:(请有余力的同学完成)

  • 支持有括号的运算式,包括出题与求解正确答案。注意,算式中存在的括号数必须大于2对,且不得超过运算符的个数。
  • 扩展程序功能支持真分数的出题与运算(只需要涵盖加减法即可),例如:1/6 + 1/8 + 2/3= 23/24。注意在实现本功能时,需支持运算时分数的自动化简,比如 1/2+1/6=2/3,而非4/6,且计算过程中与结果都须为真分数

在看到这个项目作业的时候,一开始觉得很简单,估计就是限定一些条件来进行判断,但是当实际编写代码的时候,却发现有非常非常多的问题,其次,由于对java知识的生疏,很多知识都是现看的,还有就是一些基本的算法,也需要自己在学习一下,就像中缀表达式变后缀表达式,根据后缀表达式计算结果。


首先是我项目中类和类中的方法:

public class Process{
public void Arithmetic(int num) //执行运算,这个方法是Main方法直接调用的函数
}

public class Fraction {
 public void Fraction() //这个方法是执行分数运算的一个方法,分数运算跟整数运算没有一起处理
}

public class ProduceExpression {
public LinkedList<String> expression()//这个方法是产生运算式的方法,同时产生的是中缀表达式形式
}

public class ChangeToPost {
public int changeToPostfixExpression(LinkedList<String> statements, int n, String[] ss)
//这个方法是将中缀表达式转换成前缀表达式,并在此方法中调用postCalculate()完成计算
public int postCalculate(int n, String[] ss)//这个方法是对后缀表达式进行计算
}

public class Util {
public static int gcd(int a, int b) //求最大公因数,此处是非递归实现,相当于是辗转相除
public int exactDivision(int x, int y) //递归的方式,解决整除的问题
public int priority(String symbol) //乘法除法跟加减法和括号的优先级
}

public class ArithmeticNeed {
public static int[] produceOperation() //产生运算符,并限制种类大于2种
public int caculate(int num1, int num2, String operator)//传进两个数,进行加减乘除的运算
public boolean isOperator(String operator)//判断是否是运算符
}

下面是类和类之间的调用关系结构图:

 

 

下面是编译器下面的项目的结构图:

其中result.txt生成在项目的根目录下面,还有result.txt中生成的式子:

 

 

 

 

同时经过了编译,这个项目可以在命令行下面运行。截图如下:

 

由于在src下面运行命令行程序的,因此在src下面也生成了一个result.txt文件,通过命令行运行的都会生成在src下面,但是通过IDEA编译器生成的result.txt会输入到项目的根目录下面。


在编写项目的过程中,遇到了很多的问题,

1.在随机生成运算符的时候,我本来是想要直接生成加减乘除,但是尝试了很多次,利用了数组,但是跟数字放在一起,两个数组就不好控制运算符的位置,于是我最终就采用了先是随机生成一个数字,这个数字对应着运算符数组的下角标,然后这个问题就一步步解决了。

2.在计算分数的方法中,我对分数的约分本来是,对每一个分数都进行处理的,这样就要处理至少三个数,于是我就在util中定义的一个gcd方法,计算出公因子,就减少了代码量。

3.处理括号问题其实整的我挺崩溃的,这个差不多花了一天的时间(哈哈,当然是有水分的啦),我先是尝试用数组解决(貌似我对数组情有独钟啊),我先是以对的形式进行往数组里面放,然后尝试定义swich,左右括号依次插入数组,试了这些方法反正都不行,最后采用额外开了两个数组(一个左括号数组,一个右括号数组),来记录左右括号的位置。。虽然括号的事情解决了,但是新的问题又来了,有些括号括住的是非常没有必要的,比如只括住单独的一个数字,这也是我在运行多次以后发现的问题,,之后又对括号的位置加以限制,让如果括号生成的位置,如果左括号在一个数字前,而这个有括号在这个数字后,就重新循环一次(i--);但是事情没有这么简单~又出了这样类似的问题,思考了一下,问题出在两对括号的位置可能会有重叠,从而又出现了括住单独一个数字的情况。又对此进行限制,才使得问题得以解决。

4.这是一个小的教训:我之前所有的代码都是定义在一个类里面的,再代码写的差不多的时候,我就进行将这个类里面的方法放到不同的类里面,由于没有实现进行代码备份,自己将这些方法分开的时候,出了大问题,就是整式的结果一直出不来。

5.运行结束以后,出现的结果会存在错误的情况甚至会在编译器的控制台报错的情况,对比了运算式很久,发现是整除有问题,又一步步在考虑哪里整除没有考虑到,,最后意识到是括号的问题,因为括号的存在,可能会使括号内的结果与外面进行整除的时候出现运算上的问题。

修改后的代码如下:

expression.add(String.valueOf(operator1[operatorNum[i]]));
                if (operatorNum[i] == 3) {//3代表着除号
                    number[i + 1] = util.exactDivision(number[i], number[i + 1]);
//重新判断是因为在加上括号以后,整体的运算可能会存在无法整除的情况。因此需要二次进行判断
                }
            }

 6.我本来是将中缀变后缀,根据后缀进行运算这两个方法放在不同的类里面,但是会出不来结果的情况,,在修改的时候,我进行在一些循环和方法里面加入system.out.println("=="),来判断有没有运行。解决办法,后缀表达式没有传入,,放在一个类下面,在定义一个StringBuilder 存放式子,解决了问题。

7.......................(好多好多问题)

 



PSP2.1

任务内容

计划共完成需要的时间(h)

实际完成需要的时间(h)

Planning

计划

 

less than0.5

Estimate

估计这个任务需要多少时间,并规划大致工作步骤

 

less than0.5

Development

开发

24

43

Analysis

需求分析 (包括学习新技术)

6

8

Coding Standard

代码规范 (为目前的开发制定合适的规范)

 

(编写的时候就注意了)

Design

具体设计

 

3

Coding

具体编码

 

13

Code Review

代码复审

 

4

Test

测试(自我测试,修改代码,提交修改)

 

15

Reporting

报告

 

 

Size Measurement

计算工作量

 

 

Postmortem & Process Improvement Plan

事后总结并提出过程改进计划

 

2

 

写博客

 

5


 

部分代码展示:

 

import java.util.LinkedList;
import java.util.Random;

public class ProduceExpression {

    Util util = new Util();
    public LinkedList<String> expression() {
        char[] operator1 = new char[]{'+', '-', '*', '÷'};
        Random random = new Random();
        LinkedList<String> expression = new LinkedList<String>();
        int[] operatorNum = ArithmeticNeed.produceOperation(); //产生的运算符的个数
        int[] number = new int[operatorNum.length + 1]; //运算数的个数,该数组存储运算数
        for (int i = 0; i <= operatorNum.length; i++) {
            number[i] = random.nextInt(100) + 1; //4-5个数字,产生数字
        }
        int bracketNum = random.nextInt(operatorNum.length - 1);//随机产生括号的个数,题目限定括号的个数小于运算符的个数
        if (bracketNum > 0) {//当随机产生的括号数大于零时,接下来就是产生括号了
            int[] leftBracketLocation = new int[bracketNum];
            int[] rightBracketLocation = new int[bracketNum];
            int[] leftNum = new int[operatorNum.length + 1];//记录每个数前左括号的数目
            int[] rightNum = new int[operatorNum.length + 1];//记录每个数前右括号的数目
            for (int i = 0; i < bracketNum; i++) {
                leftBracketLocation[i] = random.nextInt(operatorNum.length);//随机产生左括号的位置,此括号在第几个运算数的前面,运算数不包括最后一个数
                rightBracketLocation[i] = random.nextInt(operatorNum.length) + 1;//随机产生右括号的位置,此右括号在第几个运算数后面,运算数不包括第一个数
                if (rightBracketLocation[i] <= leftBracketLocation[i]) {
                    i--;//若右括号的位置在左括号前面或者左右括号只括住一个数字,则去掉本次产生的括号
                }
            }
            for (int i = 0; i < bracketNum; i++) {//记录每个运算数对应的括号的数目
                leftNum[leftBracketLocation[i]]++;
                rightNum[rightBracketLocation[i]]++;
            }
            for (int i = 0; i < operatorNum.length + 1; i++) {//再次进行限制
                if (!(leftNum[i] == 0 || rightNum[i] == 0)) {
                    while (!(leftNum[i] == 0 || rightNum[i] == 0)) {
                        leftNum[i]--;
                        rightNum[i]--;
                    }
                }
            }
            int right = 0;//记录加进式子的右括号数目
            int left = 0;//记录加进式子的左括号数目
            for (int i = 0; i < operatorNum.length; i++) {
                for (int j = 0; j < leftNum[i]; j++) {
                    expression.add("(");
                    left++;
                }
                expression.add(String.valueOf(number[i]));
                for (int k = 0; k < rightNum[i]; k++) {
                    expression.add(")");
                    right++;
                }
                expression.add(String.valueOf(operator1[operatorNum[i]]));
                if (operatorNum[i] == 3) {
                    number[i + 1] = util.exactDivision(number[i], number[i + 1]);//重新判断是因为在加上括号以后,整体的运算就变了。因此需要二次进行判断
                }
            }
            expression.add(String.valueOf(number[operatorNum.length]));
            for (int i = right; i < left; i++) {
                expression.add(")");
            }
        } else {//此时没有括号
            for (int i = 0; i < operatorNum.length; i++) {
                expression.add(String.valueOf(number[i]));
                expression.add(String.valueOf(operator1[operatorNum[i]]));
                if (operatorNum[i] == 3) {
                    number[i + 1] = util.exactDivision(number[i], number[i + 1]);
                }
            }
            expression.add(String.valueOf(number[operatorNum.length]));
        }
        return expression;
    }
}

 

import java.util.Iterator;
import java.util.LinkedList;

public class ChangeToPost {
    ArithmeticNeed arithmeticNeed = new ArithmeticNeed();
    Util util = new Util();
//    PostCaculate postCaculate = new PostCaculate();
    //用于记录操作符的栈
    public LinkedList<String> operators = new LinkedList<String>();
    //用于记录输出的栈
    private LinkedList<String> output = new LinkedList<String>();
    //用于展示后缀表达式
    private StringBuilder stringBuilder = new StringBuilder();
    //中缀表达式转化为后缀表达式,并且调用后缀表达式计算出整式式子的结果
    public int changeToPostfixExpression(LinkedList<String> statements, int n, String[] ss) {
        Iterator<String> iterator = statements.iterator();
        while (iterator.hasNext()) {
            /*System.out.println("------------------------------------st1");*/
            String string1 = iterator.next();
            if (arithmeticNeed.isOperator(string1)) {//当迭代器的第一个是运算符时
                if (operators.isEmpty()) {//当储存运算符的栈为空时
                    operators.push(string1);
                } else {
                    if ((!string1.equals(")")) && util.priority(operators.peek()) < util.priority(string1)) {//如果读入的操作符不是“)”而且栈顶元素小于要判断的运算符的优先级
                        operators.push(string1);//将这个运算符入栈
                    } else if ((!string1.equals(")")) && util.priority(operators.peek()) >= util.priority(string1)) {//如果读入的操作符不是“)”,而且栈顶元素大于等于要判断的运算符的优先级

                        while ((!operators.isEmpty()) &&util.priority(operators.peek()) >= util.priority(string1) && (!operators.peek().equals("("))) {
                            if (!operators.peek().equals("(")) {
                                String operator = operators.pop();
                                stringBuilder.append(operator).append(" ");
                                output.push(operator);
                            }
                        }
                        operators.push(string1);
                    }
                    //如果读入的操作符是“)”,则弹出从栈顶开始第一个“(”及其之前的所有操作符
                    else if (string1.equals(")")) {
                        while (!operators.peek().equals("(")) {
                            String operator = operators.pop();
                            stringBuilder.append(operator).append(" ");
                            output.push(operator);
                        }
                        operators.pop();//弹出“(”
                    }
                }
            } else {
                //读入的为非操作符
                stringBuilder.append(string1).append(" ");
                output.push(string1);
            }

            /*System.out.println("-----------------------------end");*/
        }
        if (!operators.isEmpty()) {
            Iterator<String> iterator1 = operators.iterator();
            while (iterator1.hasNext()) {
                String oprator = iterator1.next();
                stringBuilder.append(oprator).append(" ");
                output.push(oprator);
                iterator1.remove();
            }
        }
//        for (int i =0;i<ss.length;i++){
//            System.out.println(ss[i]);
//        }
        int m =postCalculate(n, ss);

//        System.out.println(m);
        stringBuilder.delete(0, stringBuilder.length());//进行清空一下
        return m;
    }
    //根据后缀表达式计算表达式
    public int postCalculate(int n, String[] ss) {
        LinkedList<String> list = new LinkedList<>();
        String[] middle = stringBuilder.toString().split(" ");
        for (String s : middle) {
            if (arithmeticNeed.isOperator(s)) {
                if (!list.isEmpty()) {
                    int num1 = Integer.valueOf(list.pop());
                    int num2 = Integer.valueOf(list.pop());
                    if (s.equals("÷") && (num1 == 0 || num2 % num1 != 0)) {//除数不能为零或者不能整除的时候
                        n--;
                        return n;
                    }
                    if (s.equals("-") && num2 < num1) {//存在负数产生的情况
                        n--;
                        return n;
                    }
                    int newNum = arithmeticNeed.caculate(num2, num1, s);
//                    System.out.println(newNum);
//                    System.out.println("============");
                    list.push(String.valueOf(newNum));//转换成字符串以后添加进list中
                }
            } else {//如果不是运算符的时候
//              System.out.println(s);
                list.push(s);//将数字压入栈中
            }
//            int result = Integer.valueOf(list.pop());
//            System.out.println(result);
//            if (result>100){
//                n--;
//            }
        }
        String content = "";
        if (!list.isEmpty()) {
            for (int i = 0; i < ss.length; i++) {
                content += ss[i];
            }
            int result = Integer.valueOf(list.pop());
           // System.out.println(result);
            if (result>100){
                n--;
//                System.out.println(n);
//                System.out.println("junjie加油");
            }else {
                String jieguo = String.valueOf(result);
                content += "=" + jieguo;
                System.out.println(content);
            }
        }
   //     System.out.println(content);
        return n;
    }
}

 

 

public class Fraction {
    Util util = new Util();

    //接下来实现分数的出题以及运算,分子:numerator (二)分母:denominator
    public void Fraction() {
        String[] op2 = {"+", "-"};// 存储连接分数的操作符的数组
        int denominator = 1;//分子
        int numerator = 1;//分母
        int denominator1 = (int) (Math.random() * 9) + 1;// 生成分母
        int numerator1 = (int) (Math.random() * 10);// 生成分子
        if (numerator1 != 0) {
            if (numerator1 > denominator1) {// 如果分子大于分母,也就是不是真分数时,交换分子分母,使其变成真分数
                int temp = numerator1;//如果分子大于分母,也就是不是真分数是,交换分子分母,使其变成真分数
                numerator1 = denominator1;
                denominator1 = temp;
            }
            if (numerator1 == denominator1) {// 如果分子刚好等于分母,重新生成分子,如果分子正好等于分母,则重新生成分子
                numerator1 = (int) (Math.random() * 10);
            }
            int gcd1 = util.gcd(numerator1, denominator1);// 求分子分母最大公因数,保证分数形式最简
            denominator1 = denominator1 / gcd1;// 化简
            numerator1 = numerator1 / gcd1;// 化简
        }
        String question1 = numerator1 + "/" + denominator1;// 存储题目
        int count = (int) (Math.random() * 2) + 3;// 随机产生运算符的数目
        for (int u = 0; u < count; u++) {// 小于运算符数量时不断产生分数,不断计算
            int denominator2 = (int) (Math.random() * 9) + 1;// 生成分母
            int numerator2 = (int) (Math.random() * 10);// 生成分子
            if (numerator2 != 0) {
                if (numerator2 > denominator2) {// 避免不是真分数
                    int temp = numerator2;
                    numerator2 = denominator2;
                    denominator2 = temp;

                }
                if (numerator2 == denominator2) {// 如果分子等于分母,重新生成分子
                    numerator2 = (int) (Math.random() * 10);
                }
                int gcd2 = util.gcd(numerator2, denominator2);// 化简分式,使其最简
                denominator2 = denominator2 / gcd2;
                numerator2 = numerator2 / gcd2;
            }
            int symbol = (int) (Math.random() * 2);// 随机产生运算符
            if (op2[symbol].equals("+")) {// 如果是加号,实现分数加法
                if (denominator1 == denominator2) {// 如果两个分母相同,直接将分子相加
                    numerator = numerator1 + numerator2;
                } else {// 通分,相加
                    denominator = denominator1 * denominator2;
                    numerator = numerator1 * denominator2 + numerator2 * denominator1;
                }
                if (denominator < numerator) {// 如果运算结果不是真分数
                    u--;// 计数的u减一,也就是重新生成重新计算
                } else {// 在给定范围内的话,通分运算结果
                    int gcd = util.gcd(numerator, denominator);
                    denominator = denominator / gcd;
                    numerator = numerator / gcd;
                    if (denominator>100){
                        u--;
                    }else {
                        question1 += op2[symbol] + numerator2 + "/" + denominator2;// 把题目进行完善
                        denominator1 = denominator;// 储存运算结果到denominator1和numerator1
                        numerator1 = numerator;
                    }

                }
            } else {// 如果是减号,实现减法操作
                if (denominator1 == denominator2) {// 分母相同直接分子相减
                    numerator = numerator1 - numerator2;
                } else {// 其他情况,先通分再相减
                    denominator = denominator1 * denominator2;
                    numerator = numerator1 * denominator2 - numerator2 * denominator1;
                }
                if (numerator < 0) {// 如果导致结果小于0了,就重新生成
                    u--;
                } else {// 通分结果化简
                    int gcd = util.gcd(numerator, denominator);
                    denominator = denominator / gcd;
                    numerator = numerator / gcd;
                    if (denominator>100){
                        u--;
                    }else {
                        question1 += op2[symbol] + numerator2 + "/" + denominator2;
                        denominator1 = denominator;// 储存通分结果
                        numerator1 = numerator;
                    }

                }
            }
        }
        System.out.println(question1 + " = " + numerator + "/" + denominator);// 输出题目和答案
    }
}

 



总结反思:通过这次作业,让我充分认识到了自己基础知识(java语言)和基本算法的薄弱和欠缺,但是经过一周多的查漏补缺,这个四则运算的项目已经按照作业要求完成,也增添了两个附加功能,自己的附加功能就自己测试而言还是有一处小问题的,就是括号括住的可能是乘除法,也就是像(2*3)+3-8=1 这样的式子,但是实际上这样的括号是没有必要假的,我本来想要尝试使用运算符的优先级来判断的,但是由于我括号插入的方式是根据运算数字进行确定的,所以自己想要改没有改成功,最后也就放弃了这类式子的判断,有一点点小小的遗憾,希望自己以后可以实现这类式子的判断。加油加油!

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值