20162330 结对编程项目-四则运算 第二周 整体总结

本文总结了一个结对编程项目的进展,项目目标是自动生成四则运算题目并支持多级运算、分数运算等功能。文中详细介绍了两种实现思路:使用条件/循环语句实现和使用栈实现。同时分享了代码实现细节、测试方法以及遇到的问题与解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

结对编程项目-四则运算 第二周 输出整体总结博客

主目录


结对对象:

需求分析:

  • 自动生成小学四则运算题目(加、减、乘、除)
  • 支持整数
  • 支持多运算符(比如生成包含100个运算符的题目)(使用栈生成)
  • 支持真分数
  • 统计正确率
  • 扩展需求:
    • 文件:

  • 第一周分析:如果要生成10以内的(包括分数在内)四则运算题目、运算符较少则可以不用栈的方法;如果支持多运算符且范围较大则使用栈的方法。对于以后的拓展需求,主要是能够使得使用者明确运算顺序,所以加括号是一个关键;其次,能够根据使用者的输入结果自动判断对错、生成题目不能通过有限次交换变成同一个题目等细节也不能忽视。

  • 第二周分析:如果添加括号,要考虑各个运算符的位置以及生成题目中数字的索引、有没有多余的半括号等问题,可以使用ArrayList泛型方法解决;使用IO流写入文件则需考虑IO异常抛出的问题,可以用try-catch解决;实现多语言支持则需要添加合理的条件循环语句,可以用while、if等语句解决;除此之外,实现多次测试,选择的级别和题目数量的输入等细节也需要考虑在内。

【返回目录】

设计思路:

【返回目录】

代码实现(关键代码解释)

  • 源代码链接(ED1ED2

  • 关键代码解释:

  • 思路一:(和第一周一样)

for (int i = 0; i < A; i++) {
            int B = ran.nextInt(2);
            int C = ran.nextInt(4);

            in1 = IntNumber.obj();
            in2 = IntNumber.obj();
            score1 = Score.obj();
            score2 = Score.obj();

            if (B == 0) {
                switch (C) {
                    case 0:
                        num = in1.add(in2);
                        ...
                        CorrectJudgment.judgment(N == num,num1);
                        break;
                    case 1:
                        num = in1.subtract(in2);
                        ...
                        CorrectJudgment.judgment(N == num,num1);
                        break;
                    case 2:
                        num = in1.multiply(in2);
                        ...
                        CorrectJudgment.judgment(N == num,num1);
                        break;
                    case 3:
                        num1 = in1.divide(score1);
                        ...
                        CorrectJudgment.judgment(Q.equals(num1),num1);
                        break;
                }
            } 
            else {
                switch (C) {
                    case 0:
                        num2 = score1.add(score2);
                        num1 = num2.toString();
                        ...
                        CorrectJudgment.judgment(Q.equals(num1),num1);
                        break;
                    case 1:
                        num2 = score1.subtract(score2);
                        num1 = num2.toString();
                        ...
                        CorrectJudgment.judgment(Q.equals(num1),num1);
                        break;
                    case 2:
                        num2 = score1.multiply(score2);
                        num1 = num2.toString();
                        ...
                        CorrectJudgment.judgment(Q.equals(num1),num1);
                        break;
                    case 3:
                        num2 = score1.divide(score2);
                        num1 = num2.toString();
                        ...
                        CorrectJudgment.judgment(Q.equals(num1),num1);
                        break;
                }
            }
        }
  • 以上的代码来自思路一(有删减),第一个关键点是设置了B,C两个变量,并根据输出转到相应的条件语句,尤其注意B,C的返回数值范围;第二个关键点将整数计算与分数计算划分开,并实现随机生成题目;第三点是增加了判断类中方法的调用,从而可以给出正误的输出。

  • 思路二:

    关键代码一(和第一周一样)

public class Original {
    private Stack<String> stack1;
    private List<String> list1;
    private String message,Message="";

    public Original() {
        stack1 = new Stack<String>();
        list1 = new ArrayList<String>();
    }

    public void evaluate(String expr) {
        int op1, op2, result = 0;
        String token;
        StringTokenizer tokenizer = new StringTokenizer(expr);

        while (tokenizer.hasMoreTokens()) {
            token = tokenizer.nextToken();

            if (token.equals("("))
                stack1.push(token);
            else if (token.equals("+") || token.equals("-")) {
                if(!stack1.empty()) {
                    if (stack1.peek().equals("*") || stack1.peek().equals("/")){
                        list1.add(stack1.pop());
                        stack1.push(token);
                    } else
                        stack1.push(token);
                }
                else {
                    stack1.push(token);
                }
            }
            else if (token.equals("*") || token.equals("/")) {
                if(!stack1.empty()){
                    if(stack1.peek().equals("*")||stack1.peek().equals("/")) {
                        list1.add(stack1.pop());
                    }
                }
                stack1.push(token);
            }
            else if (token.equals(")")) {
                while (true) {
                    String A = stack1.pop();
                    if (!A.equals( "("))
                        list1.add(A);
                    else break;
                }
            }else list1.add(token);
        }
        while (!stack1.empty()) {
            list1.add(stack1.pop());
        }
        ListIterator<String > li = list1.listIterator();
        while (li.hasNext()) {
            Message += li.next() + " ";
            li.remove();
        }
        message = Message;
    }

    public String getMessage(){
        return message;
    }
}
  • 这串思路二中的代码实现了中缀转后缀的表达式转换。注意其中的几个关键方法:
    • hasMoreTokens()
      这个方法可以测试此 tokenizer 的字符串中是否还有更多的可用标记。如果此方法返回 true,那么后续调用无参数的 nextToken 方法将成功地返回一个标记。
    • hasNext()
      这是一种遍历栈中每个对象的方法,如果此扫描器的输入中有另一个标记,则返回 true。注意:当且仅当此扫描器有另一个标记时才返回 true 。
  • 思路二:

    关键代码二

    public void Ti(int number, int many,String language) {
        if(language.equalsIgnoreCase("A"))
            many = TiclassC(many);
        else if(language.equalsIgnoreCase("B"))
            many = TiclassE(many);
        else many = TiclassF(many);
        for (int j = 0; j < number; j++) {
            String ti = "";
            //开始进入题目生成
            for (int i = 0; i < many; i++) {
                int A = ran.nextInt(20) + 1;
                int D = ran.nextInt(20) + 1;
                int B = ran.nextInt(5);
                int C = ran.nextInt(5);
                RationalNumber si = new RationalNumber(A, D);
                //正式进入生成题目
                if (parity(i)) {
                    //判断前面是否有左括号
                    if (list1.indexOf("( ") == -1)
                        list1.add(getSym() + " ");
                        //判断将加的右括号和上一个左括号的距离
                    else if (list1.size() - list1.lastIndexOf("( ") > 4) {
                        //判断前面的左括号是否已经有了相对应的右括号了
                        if (list1.lastIndexOf(") ") - list1.lastIndexOf("( ") < 0 && B == 0) {
                            list1.add(") ");
                            list1.add(getSym() + " ");
                        } else list1.add(getSym() + " ");
                    } else list1.add(getSym() + " ");
                } else if (i == many - 1) {
                    //循环结束时判断前面是否还有一个没有加右括号的左括号
                    if (list1.lastIndexOf("( ") - list1.lastIndexOf(") ") > 0) {
                        if (C == 0) {
                            list1.add(si.toString() + " ");
                            list1.add(") ");
                        } else {
                            list1.add(A + " ");
                            list1.add(") ");
                        }
                    } else if (C != 0)
                        list1.add(A + " ");
                    else list1.add(si.toString() + " ");
                } else if (i == 0) {
                    if (C != 0)
                        list1.add(A + " ");
                    else list1.add(si.toString() + " ");
                } else if (list1.lastIndexOf(") ") != -1) {
                    if (list1.lastIndexOf(") ") - list1.lastIndexOf("( ") > 0 && B == 0) {
                        list1.add("( ");
                        if (C != 0)
                            list1.add(A + " ");
                        else list1.add(si.toString() + " ");
                    } else if (C != 0)
                        list1.add(A + " ");
                    else list1.add(si.toString() + " ");
                } else if (list1.indexOf("( ") == -1 && B == 0) {
                    list1.add("( ");
                    if (C != 0)
                        list1.add(A + " ");
                    else list1.add(si.toString() + " ");
                } else if (C != 0)
                    list1.add(A + " ");
                else list1.add(si.toString() + " ");
            }
            for (String i : list1)
                ti += i;
            list1.clear();
            list.add(ti);
        }
    }

    public String getSym() {
        int A = ran.nextInt(4);
        switch (A) {
            case 0:
                sym = "+";
                break;
            case 1:
                sym = "-";
                break;
            case 2:
                sym = "*";
                break;
            case 3:
                sym = "/";
                break;
        }
        return sym;
    }

    public boolean parity(int num) {
        if (num % 2 == 1)
            return true;
        else
            return false;
    }

    public int TiclassC(int many) {
        Scanner scan = new Scanner(System.in);
        int A = 1;
        while (true) {
            try {
                if (many > 0) {
                    for (int i = 0; i < many; i++) {
                        A += 2;
                    }
                    break;
                } else throw new Exception();
            }
            catch (Exception e) {
                System.out.println("级别输入错误,请重新输入(要求级别至少为1)");
                many = scan.nextInt();
            }
        }
        return A;
    }

    public int TiclassE(int many) {
        Scanner scan = new Scanner(System.in);
        int A = 1;
        while (true) {
            try {
                if (many > 0) {
                    for (int i = 0; i < many; i++) {
                        A += 2;
                    }
                    break;
                } else throw new Exception();
            } catch (Exception e) {
                System.out.println("The level of questions is incorrect, Please re-enter it (at least 1)");
                many = scan.nextInt();
            }
        }
        return A;
    }
  • 解释:首先在用户输入题目数量number和题目等级manymany会在Ticlass方法中转换为相应等级的长度,根据输入的两个参数建立两个循环,具体思路为:生成的题目都是在双数位为数字,但数位为符号,在此基础上,在加数字时可能会在数字的左边加一个“(”,在加符号时会在符号的左边加一个“)”,但在加“)”时需要进行判断:
    • 1.前面是否存在一个“(”
    • 2.前面的“(”后面是否已经有了“)”
    • 3.前的“(”距离现在要加的“)”是否存在一定距离(以此保证括号中至少扩入一个两个数的计算)
  • 为解决这个问题,张旭升使用了ArrayList中的IndexoflastIndexof方法,将每一个生成的符号或数字都顺序加入到list中用最后一个“)”位置与最后一个“(”位置做差后所的数的大小来判断和保证括号生成都是一对的。

【返回目录】

测试方法(使用Junit测试):

  • 1062725-20170514182721738-805274519.png

运行过程截图:

  • 思路一:

    1062725-20170520184452666-620866673.png

  • 思路二:

    • 直接生成题目测试:

      1062725-20170520184615416-242967871.png
    • 写入IO并作答测试:

      1062725-20170520184627025-2082148612.png
      1062725-20170520184634432-1328078622.png

【返回目录】

代码托管地址:

  • 源代码链接(ED1ED2

  • commit提交信息(两周一共的提交):

    1062725-20170520184718447-627378413.png
    1062725-20170520184725666-475958642.png
    1062725-20170520184733010-1709029074.png
    1062725-20170520184741057-1735960515.png

【返回目录】

遇到的困难及解决方法:

  • 1.在级别重新输入时出现错误,直接退出程序。

    1062725-20170520232218353-843035795.png
    1062725-20170520232241385-2025514916.png

  • 解决方法:使用debug调试,发现了错误来源,是在Ticlass类中的循环出了问题。
    这个问题比较简单,主要是因为在使用Ticlass中的对象时嵌套了太多条件循环,导致难以分辨哪一个出了问题,所以使用debug是一种很好的方法。
    1062725-20170520232255057-1796448900.png
    经过调试,我发现在传入变量值0的时候,执行了一遍循环直接返回A了,所以while应该加在try的外层,这样才能确保循环体包含catch后面的语句,从而输出正常。
    1062725-20170520232306603-967159651.png
    1062725-20170520232353650-1598531546.png

  • 2.如图,在等号后面直接写答案,有些不美观,想要在等号后面加一个空格再写入答案。

    1062725-20170520232459775-1966323870.png
  • 解决方法:自己尝试,与队友讨论。
    首先,在IO写入题目的文件中不能随意添加括号,不然结果输出会出错误。
    之后又尝试了两种修改传入字符串的方法:
    尝试一:
    1062725-20170520232606853-2080493525.png
    由于在判断正误时加上了等号后的空格,所以判断结果全部是错误的。
    尝试二:
    1062725-20170520232523010-738709091.png
    由于修改了写入文件的题目,导致判断时直接将题目中第一个随机生成的数字当成结果,所以结果仍然错误。
    1062725-20170520232933353-317519938.png
    经过一些和队友的讨论后,我们在if的条件中加上了含空格的判断形式,最后成功输出并正确判定结果。
    1062725-20170520233214197-1186427070.png
    1062725-20170520233227041-997217231.png

  • 3.仍然是一个嵌套循环的问题,以下的外层循环在级别输入错误时,显示重新输入题目数量的错误。

    1062725-20170520233404775-199216163.png

  • 解决方法:与队友讨论,使用debug检查一遍。
    由于调用了另一个类当中的变量,其中包括了级别输入错误的循环语句,所以在重新输错一次题目数量或者级别后,弹出的就又会是输入需要题目的数量,因此只需要修改while为输入需要题目的数量下的内层循环就好。
    1062725-20170520233419963-1289661812.png

  • 4.在测试IO写入时,经常在运行后IDEA下方显示`Hot Swap failed xxx :changes to method modifiers not implemented xxx : Operation not supported by VM`。

    1062725-20170521091343525-84595379.png

  • 解决方法:(搜索相关文档)
    开始出现这个问题时我并没有太在意,直到反复出现这种类型的语句,语句的大意为:私有属性或者方法在热部署的时候不被VM支持。
    我大致了解了一些关于热部署的内容,网上说:热部署就是容器状态在运行的情况下部署或者重新部署整个项目。在这种情况下一般整个内存会清空,重新加载。
    虽然没怎么看懂,不过我觉得之所以反复弹出这类问题就源于在热部署一直不被VM支持,而网上的几个提问和资料并没有给出热部署失败的明确的解决方法。
    1062725-20170520233706650-1803949858.png
    直到在我查看了一篇英文资料之后恍然大悟!
    1062725-20170520233826650-602727043.png
    这篇资料中有提到很多class,意思大致是说每进行一次热部署都会重新布置class文件,热部署失败就表明不能更新class。然而,我查看了一下我的class文件集合,之前设置的excluded的文件夹中并没有class文件,于是在环境配置中新建了一个文件夹,并设置为excluded,注意要在路径中设置成绝对路径(我之前克隆新项目时使用的是继承路径,所以热部署失败),之后就可以了。
    1062725-20170521091355525-2060645232.png

  • 5.在使用另一个类中的变量时,出现调用错误导致程序运行时异常退出。

    1062725-20170521092717510-1376809796.png
  • 解决方法:与队友讨论
    原来我在一个类中创建了一个公有变量,在另一个类中使用class.类名方式调用,结果运行异常退出,讨论之后尝试在另一个类中直接定义一个相同类型的变量,并直接在最初创建公有变量的类中增加调用方法时的变量即可运行正常。
    1062725-20170521092734916-1425361198.png
    1062725-20170521092741197-1246808955.png

  • 6.运行程序再次测试时正确率计算错误。

    1062725-20170521094050213-1506146053.png

  • 解决方法:与队友讨论
    在与张旭升的讨论中,我了解到这是因为测试类中的嵌套循环太多导致在另一个类中调用累积测试从而出现问题,所以需要修改给trues赋值的位置,原来是在定义类型时直接赋值,
    1062725-20170521094103947-569927437.png
    现在只需要在不同的语言对应的判断正误类中赋值即可,这样每次调用不会累积,之后运行正常。
    1062725-20170521094117088-252190141.png
    1062725-20170521094122494-838943920.png

【返回目录】

对结对的小伙伴做出评价:

  • 我的结对小伙伴本周依然保持着较好的状态,在星期二早晨上课时就已经完成了所有主要需求,不得不说我在代码实现上一直没有跟上他的节奏,在他加完IO写入之后,我仔细查看他的代码,并且进行了多次测试,直到修改后没有什么问题,我就开始实现一些拓展需求,比如说实现多语言选择、给题目数量和级别增加输入错误的循环、多次可选择测试、以及一些输出细节问题的处理,并适当修改了张旭升以前的代码,遇到不懂的代码也及时向张旭升请教,他使用泛型方法将题目写入文件,而且在给题目加括号时花费了很多精力,更值得学习的是:他设计的类封装性很强,访问权限尽可能地小对此我没有做一点修改。不过我对他写的添加括号的代码还有一些疑问。

  • 由于我们觉得第二个版本(ED2)具有更强的拓展性,所以本周是对第二个版本进行加强的,第一个版本(ED1)与第一周一样。在本周适当的角色交换下,我充分了解了自己的不足,以及自己在什么方面需要加强,在什么方面比较擅长。结对编程的意义大概就是让我们互相弥补,共同进步。我会尽量向他靠拢,在代码上赶上他的节奏,不局限于修改。

  • 我们结对项目仍然存在不足之处,唯一没有实现的拓展需求是生成题目不能重复,其次嵌套循环有些多,其余已实现的需求我觉得比较完美。

压力测试:

  • 对张旭升的题目进行多运算级数压力测试,结果如下:

  • 等级10:0.5秒以下;
    等级100:1秒以下;
    等级1000:1秒以下;
    等级10000: 3秒左右;
    等级100000: 4分钟左右;

【返回目录】

PSP时间统计:

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

【返回目录】

感想:

  • 这两周的结对编程有利有弊,两个人互相帮助、分工合作可以增加代码的质量,而在代码上我总是在张旭升将框架结构写好之后才做一些添加和修改,大体上我还需要及时赶上他的节奏。另外,在这两周的时间里,我和张旭升在第一周讨论的时间并不多,直到我们开始交换角色时才进行一些讨论,所以作为结对编程的一员,及时与队友交换角色至关重要,以后要经常交换角色才能取得更大的收获。

附加:改进情况

  • 根据上周谢涛老师及娄老师的建议,本周我将大部分时间用在了代码实现上,但是语法问题仍然需要练习。我也没有因为环境配置的问题占用太多时间,我会继续保持这种状态并尽快赶上张旭升的进度。

【返回目录】

参考资料:

【返回目录】

转载于:https://www.cnblogs.com/super925/p/6882176.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值