结对编程项目报告--四则运算CORE

<!doctype html>

sw_lab2.md

结对编程项目报告--四则运算CORE

第15组:JL17110067 隆晋威 PB16120853 赵瑞


项目GitHub地址

https://github.com/NiceKingWei/homework2

PSP

statusstages预估耗时实际耗时
Acceptedmake plan20 min20 min
Accepteddemand analysis40 min40 min
Acceptedanalysis45 min90 min
Acceptedcode3 hours5 hours
Acceptedtest2 hours3 hours
Acceptedreport1 hours2 hours
Sum 8 hours12 hours

项目需求

像《构建之法》的人物阿超那样,写一个能自动生成小学四则运算题目并给出答案的命令行 “软件”, 如果我们要把这个功能放到不同的环境中去(例如,命令行,Windows 图形界面程序,网页程序,手机App),就会碰到困难,因为目前代码的普遍问题是代码都散落在main ( )函数或者其他子函数中,我们很难把这些功能完整地剥离出来,作为一个独立的模块满足不同的需求。

API

  1. setting() function:

     

    就是可以多次改变设置,在传入参数的时候,max_opearatorsmax_rangeprecision可以传入值或者-1,传值代表更改该设置,-1代表不变。has_fractionhas_real可以传入1,0,-1;1代表开启,0代表没有,-1代表不变。

  2. generate函数:

     

    将参数传进去,运行后结果就存在了string* answer里面。

 

代码逻辑思路

我们首先定义了 fraction 类,这是分数类,用于符号计算。

 

fraction 类里面重载了各个运算函数,并在每次计算结束之后通过类中的reduction()函数把分数变为最简分数。

然后定义了一些工具函数,如输出函数,判断是否是无效值的函数 is_bad_value

 

is_bad_value 是一个判断值是否是坏值的函数。坏值在除零异常时可能会出现,在值超出范围的时候也会出现。坏值具有传递性,坏值和任何值运算的结果都是坏值。这种设计方式的灵感来自函数式语言中的 Maybe Monad ,下面放一下fraction operator / (const fraction x) const;作为例子:

 

 

接下来定义抽象语法树的结构。

 

每一个抽象语法树的节点都包含两个字段,一个是 type,一个是 data。type 的类型是一个枚举值,data 是一个 union 联合体。这种做法的灵感也来自函数式语言。在表示抽象语法树时,tagged union 是一种很好的方式。但因为不确定能不能使用 c++17,所以我们并没有用类型安全的 std::variant ,而是使用了自己定义的tagged union

之后的事情就比较简单了

 

这两个函数产生随机值和随机抽象语法树,根据 setting 的规则产生合适的表达式树。

这两个函数的代码:

 

 

 

calc_asttree 是递归函数,它递归调用自身,计算左子树和右子树的值,然后再计算当前节点的值,如果有一个结点的type是TYPE_DOUBLE,那么返回给上一层的type就是TYPE_DOUBLE

这两个函数的代码

 

ast_eval 是调用 calc_asttree 的函数,它先把 hash_code 设为0,然后调用 calc_asttree

calc_asttree 递归计算的过程中会产生一个操作序列,这个序列可以刻画当前表达式的特征,例如 1+2+3,在计算过程中产生的序列是 1+2=3, 3+3 =6,3+(2+1) 在计算过程中产生的序列为 2+1=3,3+3=6。若定义两个序列等价当前仅当序列中每个算式在交换律意义下等价。可以发现,题目要求的 “重复” 条件与计算序列的等价条件是等价的。因此,我们可以用计算序列来去重。

但计算序列的储存比较占空间,因此我们选择了一个哈希函数,对计算序列进行哈希映射。因为两个序列哈希值相同是两个序列重复的必要条件。因此在实际操作中,只需要两个序列哈希函数不同,则这两个表达式必然不相等。

接下来是表达式输出函数。

 

注释中的内容是我们的计算表达式的 BNF 范式。由语法产生式,我们可以很容易地写出以上递归函数,并通过函数间的互相调用完成对表达式的输出,这种输出方式不会产生多余的括号。

 

generate 函数生成题目和答案。它先调用 random_ast 生成一颗随机语法树,然后对它进行求值,如果结果是坏值,那就重新生成一个。

特别值得一提的是我们的 main 函数

 

它为每一组数据生成了一行 python 代码,是一句断言。只要断言成立,这组数据就是正确的。只需要简单改改更改参数,我们就可以获得很多组数据,并能通过脚本进行自动测试。

 

发布前,我们组一共测试了 500万 组数据,均没有出错。

BUG记录

出现了错误的计算结果:

  1. bug原因:我们俩搞错了乘方和乘法的运算优先级,
  2. 这个不太容易避免,出错了时候我们还疑问到底该先算乘方还是从左到右乘
  3. 大概四十分钟,找到错误样例,研究错误样例就可以了,发现了是逻辑错误,那么改代码就可以了。

第二次出现了错误的结果:

  1. bug原因:分数运算的中间结果分子溢出了long long的范围
  2. 这个bug是在写函数时候错误的估计了可能用到的范围,没有加强函数的鲁棒性。
  3. 调试方法:用错误样例逐步测试,观察中间结果。
  4. 这种鲁棒性的东西,该加还是加上,尽量不要假设前提。

结对编程与个人作业差异

最初,隆晋威同学扮演驾驶员角色,赵瑞同学扮演领航员角色。

在完成整个程序的框架后,分工完成模块细节和测试,互相做驾驶员和领航员,在整个程序写完后,一起测试这个程序,并debug。

一个人用git很随意,可是两个人的话,就要提前看一下队友的修改。

个人看法

结对编程过程中,两个人的作息一致性很重要,在刚写完程序的时候,不出意外出了错误结果,我们开始debug,主要的debug任务是在凌晨完成的。debug到晚上十二点,1000组数据跑对了,后来加到10000又出错了,我们又debug到1点,10000没问题的时候,当时已经凌晨两点了,如果两个人有一个不习惯熬夜的话,这还真有点不舒服了,还好我俩都很不养生。

两个人的debug的速度果然不是1+1=2的简单加法,速度比一个人debug要快得多。

还有就是一起工作在有进展的时候两个人会一起觉得很开心,分享一下喜悦,比一个人有进展自己内心爽一下还happy,比如亮点debug完我们以为大功告成了就很开心(然而第二天加大测试量又出现了新的错误样例)。

结对编程过程中学习的速度时迅速的,这一次结对编程我向隆晋威同学学到了很多,比如代码规范,GitHub的使用和visual studio code的使用,还有用脚本来测试程序。

会选择结对编程用于解决部分任务。

工作时刻

1523806766472

代码

 

 

 

 

 

 

posted @ 2018-04-16 00:42  RicardoZ  阅读(222)  评论(3编辑  收藏  举报