zoukankan      html  css  js  c++  java
  • 【补充】第一次个人项目出现的bug

    新程序包下载(密码:4kp6)

    >>>>>直接上代码,问题出在随机分数的生成上,确实出现了一些非常鱼唇的错误,不过已经提交了就没办法了,在这里发出来仅供参考吧:

    修改前:

     1 public static Fraction nextFrac(int width, Random seed)
     2         {
     3             Random rd = seed;
     4             int down1 = (int)(rd.NextDouble() * (width - 1) + 1);//随机生成低于width的分母
     5             int quo1 = (int)(rd.NextDouble() * (width - 2) + 1);//随机生成低于width-1的带分数整数部分
     6             long up1 = (long)(rd.NextDouble() * (width - 1) + 1);//随机生成低于width的分子 8             up1 += quo1*down1;
     9             return new Fraction(up1, down1);
    10         }

    修改后:

    1 public static Fraction nextFrac(int width, Random seed)
    2         {
    3             Random rd = seed;
    4             int down1 = (int)(rd.NextDouble() * (width - 1) + 1);//随机生成低于width的分母
    5             long up1 = (long)(rd.NextDouble() * (width *down1-1) + 1);//随机生成低于分母*width的分子7             return new Fraction(up1, down1);
    8         }

    对于随机数元素的生成范围,第一次要求作业中有如下规定:

    使用 -r 参数控制题目中数值(自然数、真分数和真分数分母)的范围,例如

     

    Myapp.exe -r 10

     

    将生成10以内(不包括10)的四则运算题目

    对于我之前的处理方法,即 分数 = 整数[1,width-1)+ 分子[1,width)/分母[1,width)

    其值域实际上为[1,2*width-2),而且导致生成的分数至少大于1,实际上少了很多种情况...............................................

    特别的,当width取2时,由于width-2=0,随机数失去意义,这样取值范围就固定变成了[1,2],这就与老师在课程要求上的说明冲突了;

    同理可得:

    1 int down1 = (int)(rd.NextDouble() * (width - 1) + 1);
    2 long up1 = (long)(rd.NextDouble() * (width *width-1) + 1);

    这样的处理方式也是存在问题的,值域实际上变成了[1,width^2)

    值得说明的一点是,仍然保留了random*(width-1)+1这一设定,以规避分母或分子为0的情况,主要是为了减少无意义操作数(0)的出现频率和规避一些不合法情况


    程序设计过程中出现的其他Bug汇总:

    1.  当范围较大,读取表达式计算结果时,容易出现分子超int范围的情况,笔者在之前的博客中已经提到过,为了支持足够的范围,需要采用Long整型存储分子分母,因为计算过程中可能出现超int范围的分子分母结果,因此相应的,读取答案的时候,我们也要采用Int64.Parse来录入结果,并用相应的Long型变量存储,而非常用的Int32.Parse(感谢乾麻提醒)

    2.  求最大公约数,也即约分的方法一定要考虑传入参数含0的情况,以及,需要考虑,对于公约数为0的情况,约分时应当怎样处理,是否需要报异常等

    3.  对于计算模块,自栈顶向下逐步运算时(从中缀表达式的角度来看就是从右往左算),一定要考虑前一个操作符是否为“-”号,对于没有添加括号的情况,盲目按序运算会导致错误 如 (3-2-1),当计算完一个括号内的值后,需要取前一个运算符判断是否为×或÷,因为在这两个符号后面跟括号的情况下,其运算会被延后,一旦其后所跟的括号内式子得出结果,该运算应当被首先考虑。

    需要注意的是,如果不对每个生成的子表达式加上括号,其实际运算优先顺序可能与表达式建立过程不符。 如e=>e1÷e2=>e3÷e4÷e2=>3÷2÷1 (减法同)

    非常建议生成一些不加括号,运算符也多于3的中缀表达式,对于检查计算模块的正确性很有帮助

    例如:6×5+8÷(3-2) ; 

    213'3/5 - 65×(3+5-(2)-4)

    为了批量检查计算模块的正确性,可以使用excel,这里分享一下具体的用法:

    S1:

    我们需要对程序做一点点扩展,使其通过命令行的控制,能够输出同时被excel和计算模块解析的式子

    也即:将生成带分数全部转为假分数形式,并括起来(避免除法过程的二义性)

    笔者做法如下:

     1 class user {
     2     ...
     3         public static bool mixed = false;
     4     ...
     5 class program {
     6     ...
     7         if (args[i] == "-m")
     8                 {
     9                     user.mixed = true;//命令行参数控制是否以假分数形式输出
    10                 }
    11     ...
    12         ExerciseWriter.Write((j + 1).ToString() + ". 	" +         expStr.getExpStr() + "
    ");
    13          AnswerWriter.Write((j + 1).ToString() + ". 	"  + answer1 + "
    ");//输出制表符,这样直接粘到Excel里会变成两列,表达式和序号就直接分离了
    14                     
    15 class Fraction { public String express()
    16         ...
    17             long quo = up / down;
    18             long res = up % down;
    19             String s = "";
    20             if (user.mixed) return (s = "(" + up + "/" + down + ")");
    21             else ...//正常输出 
    22         ...        

    需要注意的是,因为有制表符的存在,所以读取式子用replace去除空白符应当用“\s+”匹配而不是空格“ ”

    S2

    将式子粘到excel里,会发现自动分成两列了(比如A、B两列吧),然后我们用excel自带的查找替换工具,将B列表达式出现的×÷替换为*/;

    在C列输入 =”=“&B1,并拖动格式手柄应用到其余行(如果数字太大,就拖滚动条到表格底部,用shift选中底部到头部的所有单元格,按ctrl+d即可)

    再按ctrl+c复制,粘贴选择左上角粘贴选项里的 ”粘贴数值——值“,使用查找替换工具,将”=“替换为”=“,所有表达式的值就悉数计算出来了;

    S3

    采用同样的办法把答案复制到D、E列,并用同样的办法求其值(答案实际上多为分数,可以看成一个简单表达式),用S2的办法求其值;

    再在F列中计算C列,E列两列值之差并用sum求和,如sum为0,则基本可以确保程序的计算模块已经完全没有问题了

     

    4.  在用Dictionary<String Answer ,List<String> usedCase>这一数据结构时发现索键求值的过程存在很多数据错误,经过仔细的检查,发现问题在于将List对象Add到Dictionary里的时候,直接传引用作为值放进去了,因为笔者的程序设计的时候,只有一个usedCase随表达式生成函数传入,clear,add,传出,那么无疑所有键的键值都是这一个List,它被修改的话相应的Dictionary里的所有键键值就会被修改,所以实际上作为value传入的时候是需要通过拷贝构造的,至于中间出现的数据混乱情况,可能是在修改这一模块时只修改了一部分所致吧。

    5.  计算表达式时,为了避免栈空取值的情况,需要时刻关注栈的容量,但有一个简洁的办法可以改善性能,那就是预置栈底元素(比如#之类的作为标记),这样只需访问栈中的一个值而不是全部

    6.  对于负分数的表示形式,应规范成仅分子为负或仅整数部分为负(读入78/-4,转为-20'1/2或-39/2),假分数转换成带分数的时候,因为分子由取余操作产生,其值可能为负,这种情况下,需要手动让分子加上分母的值,然后整数部分-1来简约形式,避免-5’-39/78这种情况的产生,恰恰因为我们的项目要求里对减法做了约定来规避负数的产生,这一点才不容易被发觉,试着生成一些负分数的String表示形式,或者让分子,分母,整数部分取0,看看会有怎样的结果

    7.  养成好习惯,随手关门,随手close

    Ps:如果输入文件路径无效试着转绝对地址吧,方法为System.IO.Path.GetFullPath(YourAddress),另外输出文件时需统一编码(再次感谢乾麻提醒),如:

    StreamWriter GradeWrite = new StreamWriter(Grade, System.Text.Encoding.Unicode);

    8.   通过Opnum和OpLim控制表达式的推导生成过程,显然,当Opnum = 0的时候应规避 e=>n的文法规则,当Opnum = OpLim时应规避二元式的生成,并在当前表达式已经被括号括起来的情况下,规避e=>(e)的文法规则来避免括号冗余,以避免增加计算模块负担

    9.  一个比较常见的情况是生成表达式的时候,明明用了random却出现了大规模的重复情况,这是因为random生成随机数是基于当前系统时间的,由此生成一个伪随机序列,如果程序执行的效率高,同时又有多个random对象被新建用于生成随机数的话就会出现重复,对于这样的情况,最好的办法是全程只用一个random对象,让其作为随机种子传入即可

    10.  暂时只注意到这么多,有新的会补充

  • 相关阅读:
    python进程和线程(五)
    java开发常用Linux命令
    IntelliJ IDEA中Maven插件无法更新索引之解决办法
    Spring事务配置的五种方式
    思考,快与慢(读书笔记)
    freemarker,jsp,velocity比较
    java学习之路经验总结
    iBATIS源码分析
    freemarker的使用心得
    javaScript中的关键字和保留字
  • 原文地址:https://www.cnblogs.com/kibbon/p/4832024.html
Copyright © 2011-2022 走看看