四则运算个人项目反思总结

项目规划与实际



本题的分析与解答基于以下个人项目需求:
个人项目链接

我的PSP2.1的表格如下,其中计算工作量这一步我不是很懂。我觉得是属于团队合作里的一项步骤,所以没有填写。

PSP2.1 Personal Software Process Stages Time
Planning 计划  
Estimate 估计这个任务需要多少时间 30
Development 开发  
Analysis 需求分析(包括学习新技术) 10
Design Spec 生成设计文档 10
Design Review 设计复审 4
Coding Standard 代码规范(为目前的开发制定合适的规范) 1
Design 具体设计 20
Coding 具体编码 8
Code Review 代码复审 5
Test 测试(自我测试,修改代码,提交修改) 5
Reporting 报告  
Test Report 测试报告 0.5
Size Measurement 计算工作量(这是?) ?
Postmortem & Process Improvement Plan 事后总结, 并提出过程改进计划 5
  合计 68.5

从上面的计划与实现表格也能看出来,实际上我的开发时间是预估时间的两倍之久。并且投入在设计上的时间非常之长(需求分析+生成设计文档+设计复审+具体设计=44)。这样的时间投入主要是因为我在设计过程中遇到的两个很大的困惑:
1. 高效率地构造交换律下满足不重复的表达式2. 能够承受足够大数据的测试

这两个问题困惑了我很久,我的大部分时间在解决这两项看起来不简单,实际做起来更难的问题:因为这两个问题实际上是互相制约的。

如果要能够承受足够大范围的数据测试,那么我们就必须了解一点:生成数量足够大带来的是重复率的急速上升。

假设我们是通过随机化的方法生成操作数和操作符的,那么一开始我们可能重复的概率是很小的。比如某个值域范围内我们全部排列的个数大约为10万个,只要求生成1000个,那么重复的概率平均下来要比1%低得多。这样按随机化处理的话,前后表达式重复的可能很低,最后也不会浪费太多的时间在随机的处理上。

但是,如果我们要求生成5万个表达式呢?那这时候我们前后“随机化”生成的表达式碰撞的概率会变得非常之高。那么我们可能会陷入一个困境,即生成前百分之80的表达式所用的时间可能没有生成后百分之20的表达式所用的时间多。

但是这也是没有办法的,高效率地构造满足交换律下不重复的表达式本身就要求随机化后重复的概率偏低。遍历查询是否重复是有必要的,所以瓶颈在于随机产生的时间以及遍历时是否能快速查询所采用的算法。

以下是我在项目过程中遇到的一些问题和一些自己取巧的方案。之前并不清楚(树的最小表示法)这种神奇的判断树同构的方法,也没有史神那么扎实的算法基础,所以给各位献丑了。

项目难点分析



我觉得从我的角度来说,这次项目有几个比较难以解决的问题:

  • 如何构造表达式可以实现不重复?
  • 如何能够支持尽可能大范围内的表达式构造?
  • 如何实现分数与整数的混合运算?
  • 如何能保证减法与除法结果的有效性?
  • 如何能够计算中缀表达式的值?

按我的想法,第一个问题和第二个问题结合起来是最让人为难的。之前在罗老师的博客讨论区里跟罗老师确认了“结合律”构造出的同构不属于重复的范畴,心里稍稍放轻松了点。但是这个问题依旧不好解决。我先阐述一下其他问题的解决方案。

分数与整数的混合运算

我一开始曾经想,是重新定义输入,比如

public static operator +(int a,Fraction b)

还是增加一个ParseFrac来将整数显式地变成分数?这个函数实现的功能应当是:可以将一个正整数转换为一个分母为1,分子为该正整数的分数。
我最后找到了一个更好的方法来解决这个问题,解决方案在于C#的自定义隐式转换!使用关键字 implicit 可以自定义隐性类型转换,我举一段代码作为例子。

 public static implicit operator Fraction(int input)
 {
      //implicit means obscure
      //code to convert from int to Fraction
      Fraction output = new Fraction(input, 1);
      return output;
}

这个自定义类型转换的使用方式也很好用,比如以下的代码

int a = 3;
Fraction b = new Fraction(3,4);
// b = 3/4
Fraction c = a + b;
// Here ‘a‘ convert to Fraction class automaticlly

当然为了实现上面的运算,我们是需要将操作符重载的,这也是我学到的第二点知识。

public static Fraction operator +(Fraction lhs,Fraction rhs)...

上面是关于分数类的‘+‘加法操作符重载。重载之后我们就可以使用+在两个Fraction对象中间来直接计算其和式。
当然为了分数计算过程中的简洁性我所使用的过程中的运算涉及到的分数有了如下规定:
1. 不使用带余真分数作为计算单元,带余真分数只是在展示的时候会打印,其余时候都以假分数形式存在从而利于计算。
2. 不带余的真分数在打印时依旧是原样。
3. 整数统一定制为分母为1的假分数形式。

当然还需要有一个考虑,即分子分母有公约数时的约分问题,这个最简式在打印、计算以及最重要的相等比较中尤为重要。

但是问题这时候就又出现了,如果我们需要化简的话,我之前使用的方法就是从分子和分母中较小的数字开始向下遍历寻找两个数的最大公约数,然后上下同时除最大公约数从而化为最简式。但是这样会带来一个很大的问题在于,我的化简是在每次运算后都需要的,因为每种运算都可能使得分母和分子产生公约数。比如下面的例子

  • 1/2 - 1/6 = 2/6
  • 1/9 + 2/9 = 3/9
  • 1/9 * 3/4 = 3/36
  • 1/9 ÷ 1/3 = 3/9

但是有个问题会随着值域(r)的扩大而产生,由于分子的范围为(0,r^2),所以实际上我们在分数计算式中产生的最大的数字可能高达r^4,甚至可能为r^8!当值域为100时,我们的分子和分母最大可能为几千亿的数量级。
这样的话在化简时耗时是惊人的,尤其是分子分母互质时,每次运算后对程序的影响都非常大。
所以后来我改进了算法,将求最大公约数的算法改进为欧几里得算法,果然在值域较大的范围内优化后的代码效果大大提升。
但是我们同样有另一个问题存在,这个问题同样是十分令人害怕的。
我们刚才提到了值域的扩大对于最终结果的最大值的影响是十分之大的,其能高达r^8倍。比如下面这个例子,如果我们的值域是20:

( 16‘11/15 ÷ 17‘4/19 ) × ( 6‘1/2 ÷ 12‘1/15 )

这里还有一个坑的地方,在于值域的上限。上面提到分子中最高有可能到r^8的水准是有计算依据的。我们设想存在这样一个数字,其分子接近r^2,分母接近r,但是分子与分母互质。设这个数为x,那么
x * x * x * x 完全可以达到r^8的数量级。按这样计算,如果使用int定义分子分母的类型,那么只要 r 达到 15 ,就可能出现超过 int 型范围的数字,最后导致结果为 负数

为了解决这个问题,我们首先要将分子分母定义为long类型,这样的话,按粗略计算来讲,我们可以知道这样可以最高定义到 2^63-1 开8次根号的值,即最高可以支持到200左右的值域,这个值域已经足够了。

但是这时候坑出现了,C#里的Random函数不支持long类型的生成,不过这个问题在StackOverflow上已经有了答案

于是乎我采纳了这位小哥的意见,最终使用了一个long的Random函数:

public static long LongRandom(long min, long max, Random rand) {
    byte[] buf = new byte[8];
    rand.NextBytes(buf);
    long longRand = BitConverter.ToInt64(buf, 0);

    return (Math.Abs(longRand % (max - min)) + min);
}

最终成功解决了这个问题。

保证减法与除法结果的有效性

根据题目需求,本题目里要求减法的结果不可以出现负数。之前我想了三种算法:
1. 随机生成两个数,如果他们俩相减结果小于0,则舍弃这个算式。(实际上第二个数比第一个数大的概率有百分之(1-1/n)/2 )
2. 随机生成一个数作为被减数,按这个数字为新的范围重新生成一个随机数作减数,但是这样产生随机数会浪费一定时间。
3. 如果a-b<0,则交换ab。这个算法只是交换了一下ab的位置,所以非常简单便捷!

最终我采用了第三种算法来保证减法结果的有效性。

对于除法来说,如果随机到的除数为0的话,则将其加1,这样可以造出一个整数。

计算中缀表达式的值

在大二的数据结构课上学到了中缀表达式转后缀表达式的算法,并且进行了练习。所以在计算中缀表达式的算法中并没有太大的困难。

构造表达式的方法

我原先的想法也比较平凡朴素,随机化处理构造一个表达式。随机化处理的含义是随机生成操作数和操作符之后写进字符串里,然后随机加括号进字符串里,然后对字符串表达式进行运算,求值。
但是如果是随机加括号进字符串,则对于括号前后的匹配会比较麻烦。所以我就有了两种想法:
1、后缀表达式--->中缀表达式
2、中缀表达式--->后缀表达式
即先构造后缀表达式,计算值,然后转成中缀表达式显示呢?
还是直接构造中缀表达式,显示,再变成后缀表达式进行计算。
开始时考虑到括号匹配与合理(即括号存在都是必要)的问题,我原本准备选择随机化构造后缀表达式,但是随机化这样一套下来表达式合法的概率太小,同时在生成大量表达式的时候会有很严重的重复问题。同时在随机化处理方面由于生成两个随机数的时间间隔太小很容易造成随机数的重复问题。
所以我决定直接构造一个合法的中缀表达式。

那么生成中缀表达式该怎样生成又快又能满足一定量的需求呢?我在思考再三后想到了一种裂解的方法,裂解的方法步骤如下:
1. 生成操作数
2. 将操作数分裂成为两个操作数与运算操作符
3. 随机指定某个操作数进行不断分裂,直到达到预期的操作符的数量(数目也是随机生成的)。

实际上这样的算法本来是满足小学生的真实需求的,因为不仅可以生成真正随机的表达式,而且可以满足每个表达式的值都在小学生所认知的数域范围内。

但是问题又来了:
1、由操作数生成操作数和运算操作符,相当于从结果推出过程,那么我的第一个操作数要定在多少合适?如果只是在值域内,会不会很局限?如果不在值域范围内,会不会很难合法?
2、由操作数裂解生成操作数和运算操作符,不可避免地减少了运算的自由度问题。

但是这样效率还是很低,同时因为表达式值的限定减少了很多能够生成的式子。重复率变得很高,代价太大,同时生成合法式子(即所有数字都在预定范围内)的概率并不高。

所以这个算法夭折了。下面就介绍一下我现在在使用的一个算法,当然它也有很多缺陷:推广性不强(不能应用于更多的操作符式子)、不能获得极大部分的算术表达式。但是它有一个很大的优点:生成过程简单并且效率较高。

重复性的检测与避免

补充说明:在询问了罗老师之后,我获得了关于重复性的严格定义的说明本题中的重复性是基于交换律的较小差异而产生的,所以我们只需要考虑交换律产生的较小差异

关于重复性的检测,我之前的想法是:
每一个算式都能对应一个二叉树,我们只要在每次计算时将式子对应的二叉树来构造一个特殊的唯一编码,并将其所有父亲结点为+或者*的地方将其子树构造出其对称树,然后将其对称树的所有编码一起加入到Hash编码序列中以检测重复。
但是这样有一个比较大的弊端:每个二叉树各自Hash特征码计算中,在值域要求相当大时Hash码的计算量会变得很大,很拖累性能。所以最后这种想法被否定了。(但是后来我才发现,即使使用了ulong,也不过能将值域扩大到200左右而已,所以这种方案实际上是可行的。尤其在看到史神的博客中提到的树的最小表示法后感觉这种方法的适用性更强。这里是我的设计上的失误,没有实践就否定了某种方案。

于是我就开始思考,如何能把避免重复做成理想化的一件事,那就是怎样能在构造的时候就尽量避免重复。

那么我想:既然式子要求上限是三个操作符,那我们把三个操作符全部使用即可。
那么现在我的算法就是一种朴素的算法:

  • 首先通过二元运算生成大量的单项表达式^单项表达式,四种运算的每种单项表达式的个数大约相同(我最后采用了1/20数量来生成单项表达式)。

生成二元式的逻辑表示如下:
1、+法生成——>在这个过程中,要把生成的二元式放入AddArray中,并且对于每一个生成的式子,都要查询是否与前面的重复,比较时需要重载==运算符。
2、-法生成——>在这个过程中,要把生成的减法二元式放入SubArray中,并且为了保证减法的结果一定大于0,如果前面的数比后面的数小的话,则将两个数调换顺序。
3、*法生成——>在这个过程中,都要把生成的乘二元式放入MultArray中。
4、/法生成——>在这个过程中,都要把生成的除法二元式放入DivArray中,并且为了保证除法的除数不为0,如果除数为0时将其变为1。

  • 使用二元表达式子生成四元表达式。这时候比如这样:
    (2+3)*(8*7),2+3,8*7是二元式的值。为了避免交换律意义下的重复出现,在生成四元表达式时我们遵循以下原则:
    1、如果操作符为*或者+,则第二个二元式在二元式数组中的位置一定没有第一个二元式的序号小。
    2、如果操作符为-并且结果为负数,则调换二元式的前后顺序。
    3、如果操作符为/并且除数为0,则重新选取除数二元式。

实际上这种算法本来也可以增加更多的表达式的数量,比如使用二元式+操作数生成三元式,再用三元式加操作符生成四元式。但是由于我们的题目实际上对于生成的数量并没有极端大的要求,所以我就没有实现。因为通过数学上的计算发现,四元式的数量占了总表达式的数量约有(大部分情况)(r^2-1)/r^2。所以四元式的数量已经足够满足需要。

项目测试与总结


时间: 2024-10-11 15:53:11

四则运算个人项目反思总结的相关文章

高级四则运算器—结对项目反思(193 &amp; 105)

高级四则运算器—结对项目反思(193 & 105) 本周我和一位韩国同学(71061105)一起结对编程完成了我们的结对项目——高级的小学四则运算题目生成器. PSP表格   PSP2.1 Personal Software Process Stages Time Planning 计划 · Estimate · 估计这个任务需要多少时间 1.5h Development 开发 · Analysis · 需求分析 (包括学习新技术) 3h · Design Spec · 生成设计文档 5h ·

2016012045 +小学四则运算联系项目报告

  2016012045 +x小学四则运算联系项目报告 代码仓库地址:https://git.coding.net/Enther/operation.git 一.需求分析. 此项目主要是为面向小学生的小学数学四则运算练习所用,所以归纳出以下几点要求: (1)     对于每个数而言不能过大,且其中不涉及小数与负数,即仅考虑100内整数形式的运算. (2)     题目中最少包含两个运算符且保证不一样,且涉及加减乘除. (3)     程序中需要输入参数n作为随机产生题目数量. 二.功能设计. 此

小学生四则运算小项目

我写这个小项目,是在一个小程序上改的,这个小项目的网址为http://www.cnblogs.com/ys1101/p/4368103.htm,在上面的代码上实现的功能不完整,并没有完成课本上的要求.在他的代码上我增加了语言选择,这里只是有英文和中文的选择.还有原来的代码并没有实现真分数的四则运算,在此基础上我在菜单栏上新添加了真分数的四则运算,还有对其菜单栏进行了优化,使得界面更好看.写好后我将此项目放入了我的Github上网址为:https://github.com/HAIWWH/WWH,希

四则运算个人项目进展

一.项目要求 基本要求:将10-20道四则运算题目写入文档,程序读取并输出题目,同时计算出正确结果.使用者对每道题目计算答案,答对进行提示,答错输出正确结果.分别记录回答正确.错误的数目并输出.四则运算题目基本要求:1.加减乘除四种运算全部出现 3.算式中要出现括号2.出现真分数和假分数的运算4.最少出现一个长度为10的四则运算(10个数字的混合运算) 二.项目进展 我使用了C++进行编写,主要使用的类为自定义的堆栈类: template <class T> class arrStack{ p

项目反思 - 项目管理需要一个平台,而不能仅仅靠“人治”

最近手上的一个项目正在逐步切换上线,看似简单的切换,但实际过程却是非常的累心.数据的梳理.运维的支持.后台的切换无一不是走一步一个坑,非常多的项目风险都被脑残的乙方项目经理一个个掩盖了,项目上线前夕才发现是"坑连坑",扑救的过程非常辛苦(非常庆幸我们还有补救的机会),作为甲方的项目经理,我也是责无旁贷,选人不当,项目过程看的不够细致,对项目风险预估的不够充分.我也在反思我犯的错误: 1.选人是大事:项目之初,我就已经注意到乙方项目经理这一关键岗位人选可能不太合适,当初提出了质疑,但很可

关于四则运算组队项目总结

上个礼拜结束了四则运算的结队项目,通过这阶段的学习,我首先了解了组队做项目时应该如何进行分工和配合,由于从来没有尝试过两个人一起进行组队编程,每个人的代码风格也不是很一样,在整合到一起的时候代码兼容性不强,引起了很多麻烦.但是结队编程节省了编程的大量时间,也能充分发挥一个人的才能. 在这次项目中,我学习到了黑盒测试. 黑盒测试: 黑盒测试也称功能测试,它是通过测试来检测每个功能是否都能正常使用.在测试中,把程序看作一个不能打开的黑盒子,在完全不考虑程序内部结构和内部特性的情况下,在程序接口进行测

2016项目反思

毕业至今,大大小小的项目经历了十几个,有不少成功的喜悦,也有不少失败的教训.近来暇时,细细回味,时值2016年年末,略有感想. 算来,7年的项目经历,从最开始的用C语言写编译器,到最近的.NET的机构版.其中最成功的最喜悦应该属health care的UDN和实训平台的TTS,最失败教训最足当属机构版.health care项目组是我跟随zhong li老大做的第一个正式大型项目,第一次完整的接触正式的软件过程管理.全球数千人同时为一个项目服务,其中过程管控是我至今影响我最深.跨地域,跨平台,跨

四则运算 出题项目

项目代码最初是来源自网上的四则运算代码 能实现简单的四则运算出题功能, 不支持括号和累计分数. 使用基于时间产生的1到100的随机数,判断大小来决定运算顺序 改进后能支持真分数的运算,但界面体验不好,还需要优化. 代码链接: https://github.com/q1q2w2w3e3e4/969696

小学生四则运算练习项目报告

github地址:https://github.com/myGitHub1018/Student_comput 一.需求分析 1.由用户输入参数n,然后随机产生n道加减乘除练习题: 2.每个数字在 0 和 100 之间,运算符在3个到5个之间: 3.运算过程中不得出现负数与非整数,比如不能出 3/5+2=2.6,2-5+10=7等算式: 4.将学号与生成的n道练习题及其对应的正确答案输出到文件"result.txt"中,文件目录与程序目录一致. 例如:当程序接收的参数为4时,输出如下: