20180925-6 四则运算试题生成

此作业要求参考:https://edu.cnblogs.com/campus/nenu/2018fall/homework/2148

1.项目分析及编程收获

项目说明,本次项目使用了Python作为开发语言,该语言较为简易灵活,对多平台的支持较好。

  • 开发环境是 macOS High Sierra
  • 开发工具是 pycharm
  • Python语言版本为 3.7
  • 版本控制选用 Git

1.1 功能一

要求:四则运算:支持出题4个数的四则运算题目

1.1.1 功能一重点

表达式生成:将随机生成的表达式用列表存储,只需要判断列表中元素索引的位置是奇数还是偶数,如果是偶数则向列表中随机添加操作数,若果是奇数则添加运算符

而且列表长度是固定的,含有4个操作数,3个运算符,具体代码如下:

1         # 操作数4,运算符3
2         for j in range(4 + 3):
3             if j % 2 == 0:
4                 # 随机生成操作数
5                 exp.append(self.generate_operand())
6             else:
7                 # 随机生成运算符
8                 exp.append(self.generate_operation())

 

操作数和运算符的随机生成:这里将操作数和运算符的生成代码分布封装到两个函数内,然后通过python自带的random.randit()方法产生随机数,具体代码如下:

1     # 生成操作数
2     def generate_operand(self):
3         return str(random.randint(1, 9))
4 
5     # 生成运算符
6     def generate_operation(self):
7         operators = ['+', '-', '*', '/']
8         return operators[random.randint(0, len(operators) - 1)]

 

1.1.2 功能一难点

中缀表达式转后缀表达式:我通过数据结构中“栈”的概念来实现这个操作,先划分不同操作符的优先级,然后通过对比优先级的不同进行出栈操作,划分优先级的代码如下:

1 # 制定栈中操作符的优先级
2 ops_rule = {
3     '+': 1,
4     '-': 1,
5     '*': 2,
6     '/': 2
7 }

中缀转后缀的代码如下:

 1 # 将中缀表达式转换为后缀表达式
 2 def middle_to_after(s):
 3     expression = []
 4     ops = []
 5     # ss = s.split(' ')
 6     # s = s.replace('=', '')
 7     for item in s:
 8         if item in ['+', '-', '*', '/']:
 9             while len(ops) >= 0:
10                 if len(ops) == 0:
11                     ops.append(item)
12                     break
13                 op = ops.pop()
14                 if op == '(' or ops_rule[item] > ops_rule[op]:
15                     ops.append(op)
16                     ops.append(item)
17                     break
18                 else:
19                     expression.append(op)
20         elif item == '(':
21             ops.append(item)
22         elif item == ')':
23             while len(ops) > 0:
24                 op = ops.pop()
25                 if op == '(':
26                     break
27                 else:
28                     expression.append(op)
29         else:
30             expression.append(item)
31 
32     while len(ops) > 0:
33         expression.append(ops.pop())
34 
35     return expression

 

后缀表达式的计算:在比较优先级后出栈的同时进行表达式的计算,这里我将四则运算单独做了封装,可以降低程序的耦合程度,具体代码如下:

 1 # 计算后缀表达式
 2 def expression_to_value(expression):
 3     stack_value = []
 4     for item in expression:
 5         if item in ['+', '-', '*', '/']:
 6             n2 = stack_value.pop()
 7             n1 = stack_value.pop()
 8             result = cal(n1, n2, item)
 9             stack_value.append(result)
10         else:
11             stack_value.append(int(item))
12     return stack_value[0]
13 
14 # 四则运算规则
15 def cal(n1, n2, op):
16     if op == '+':
17         return n1 + n2
18     if op == '-':
19         return n1 - n2
20     if op == '*':
21         return n1 * n2
22     if op == '/':
23         return n1 / n2

 

1.1.3 功能一编程收获

在实现功能一的过程中,对之前数据结构里“栈”的使用较多,也花了许多时间复习这个概念以及中缀表达式与后缀表达式之间的转变,虽然之前使用过多次c语言版本的中缀转后缀,但这次通过python实现还是觉得非常便利。而在制定操作符优先级和四则运算规则时,通过不断封装函数来降低程序的耦合也使得程序更加易读,对我和于洋的结队开发收益良多。

 

1.2 功能二

要求:支持括号

1.2.1 功能二重点

括号的生成方式:为了能够复用功能一的代码,我决定在实现功能二时对功能一已生成的表达式进行括号的插入,具体方法是将存放表达式的列表传入,然后在添加到新的带括号的表达式时随机添加一对括号,具体代码如下:

 1         if exp:
 2             i = 0
 3             for j in self.position_matrix[position]:
 4                 # 0表示添加操作数和运算符
 5                 if j == 0:
 6                     expression.append(exp[i])
 7                     if i < len(exp):
 8                         i += 1
 9                 # -1表示添加左括号
10                 elif j == -1:
11                     expression.append('(')
12                 # 1表示添加右括号
13                 elif j == 1:
14                     expression.append(')')

 

1.2.2 功能二难点

括号的成对生成:众所周知,在表达式生成时括号肯定是要成对存在的,否则无意义。但是在每次向表达式中插入括号时都进行50%的判断会使得代码很不简练,而此题目只规定生成4个数字的表达式,则括号最多出现2对,那么出现括号的情况就只有10种,因此我跟于洋决定枚举这10种情况,然后制定一个矩阵。这里我跟于洋约定:矩阵中“-1”代表左括号,“1”代表右括号,“0”代表操作符数和运算符,“2”是用来填充只有一个括号的情况时缺少的括号,“2”不做任何处理。最后在生成表达式时只需要在这10种情况的矩阵里随机抽取一种即可,具体代码如下:

 1     # 4个数的式子括号最多两对,随机生成一对括号或两对括号
 2     position_matrix = [[-1, 0, 0, 0, 1, 0, 0, 0, 0, 2, 2],
 3                        [-1, 0, 0, 0, 0, 0, 1, 0, 0, 2, 2],
 4                        [0, 0, -1, 0, 0, 0, 1, 0, 0, 2, 2],
 5                        [0, 0, -1, 0, 0, 0, 0, 0, 1, 2, 2],
 6                        [0, 0, 0, 0, -1, 0, 0, 0, 1, 2, 2],
 7                        [-1, 0, 0, 0, 1, 0, -1, 0, 0, 0, 1],
 8                        [-1, -1, 0, 0, 0, 1, 0, 0, 1, 0, 0],
 9                        [-1, 0, 0, -1, 0, 0, 0, 1, 1, 0, 0],
10                        [0, 0, -1, -1, 0, 0, 0, 1, 0, 0, 1],
11                        [0, 0, -1, 0, 0, -1, 0, 0, 0, 1, 1]
12                        ]
13 
14     # 生成括号表达式
15     def generate_parentheses(self, exp):
16         expression = []
17 
18         # 从10种情况中选取一种
19         position = random.randint(0, 9)

 

1.2.3 功能二编程体会

在编写功能二时令我和于洋头疼的问题是括号的随机生成问题,单个括号通过位置也好实现,但是题目要求括号出现了嵌套的情况,这需要在每次添加操作数前都要随机插入括号,有时还需要插入2个左括号,而在插入左括号后还要随机插入右括号与之匹配,这些判断一一添加进去会十分冗余,经过我们的分析,先发现最多只能出现两对括号的情况,然后将所有括号一一列举,就发现这个数量并不庞大,通过枚举一个矩阵便可以轻松表示,随后我们又制定了矩阵的表示规则,较为简练的实现了功能二。

 

1.3 功能三

要求:限定题目数量,打印输出,避免重复

1.3.1 功能三重点

对输入指令的判断:在控制台中输入“-c”表示执行功能三,但是前两个功能要求是没有参数的,在接受控制台参数时需要格外注意,开始时我们只是单纯的判断第一个参数是否为“-c”但是在没有第一个参数时会产生越界问题因此我们改成判断参数中是否含有“-c”,具体代码如下:

 1 # 这是主方法
 2 def main(argv):
 3     if '-c' in sys.argv:
 4         input_text = str(sys.argv[2])
 5         if input_text.isdigit() and int(input_text) > 0:
 6             normalize_exp(int(input_text))
 7         else:
 8             print('题目数量必须是 正整数。')
 9     else:
10         circular_problem()
11 
12 
13 if __name__ == '__main__':
14     main(sys.argv[1:])

 

按指定格式输出:由于不是帮助老师一个一个出题,而是直接将题目与结果打印,那么之前的打印函数就不能使用了,为此我特意通过单独的函数将打印的方式规范化,具体代码如下:

 1 # 规范输出
 2 def normalize_exp(num):
 3     i = 0
 4     while i < num:
 5         generate = Exp_Generating.Generator()
 6         inputText = generate.generate()
 7         expression = middle_to_after(inputText)
 8         value = expression_to_value(expression)
 9         inputText += '='
10         print('%-15s %-15s' % (inputText, value))
11         i += 1

 

1.3.2 功能三难点

输入题目个数的校验:正数的判断以及是否为数字的判断都比较容易判断,但是是否为整数通过简单的条件并不好处理,通过查阅资料后我们发现python自带的isdigit()对小数的返回值也是False,因此只需要两个条件就可以完成校验,代码如下:

1      input_text = str(sys.argv[2])
2         if input_text.isdigit() and int(input_text) > 0:
3             normalize_exp(int(input_text))
4         else:
5             print('题目数量必须是 正整数。')

 

避免重复:这个查重的逻辑表面上看起来较为复杂,但是我们可以通过利用数据结构中二叉树的原理进行实现,先将表达式转化为二叉树,再将二叉树进行后序遍历,如果两个式子通过交换律能变成一样的式子,那么他们所生产的二叉树后序遍历的结果也一样。

 

2.结对编程的体会

由于这次作业是我与于洋两人结对编程实现的,因此在困难问题的解决上会更加高效。很多次我自己想不出解决方法时,叫来于洋一起讨论,我在向他描述问题的时候顺便理清了思路,直接就发现了问题的所在,而于洋只是听了一下我们就解决了。当然这只是在调试程序时的优点,在开发时我对功能二添加括号的问题纠结了好久,与于洋讨论过之后我们发现了含有括号的表达式的十种情况,我们分别进行枚举得到的答案一致,这也坚定了我用矩阵的信心,正因如此,我们程序的代码量变得十分精简。除此之外,我们在结对编程时为了让对方更易读懂自己的代码,我们统一了编码规范,我们各自放弃了之前的一些习惯,比如我喜欢将函数名和变量名卸载写成一个单词,然后用首字母大写来区分不同单词,这次改成统一小写并使用下划线来区分单词,而于洋之前喜欢使用拼音来命名文件和函数,但是我觉得不过高大上就统一使用英文单词来命名。所以通过这次的结对编程,我感觉对开阔编程思路,提高问题解决效率,规范代码格式等方面有较大的收获。

 

3.花费时间较长的事件

3.1 中缀表达式转成后缀表达式

在这项问题的研究中,我对此前数据结构的内容掌握不牢,许多细节知识处理不当,在多次实现无果后我选择求助于洋,我们通过举例、画二叉树、通过栈对结果的校验等方式将表达式转换搞懂。

3.2 函数名之争

在对函数和变量的命名规则上我们出现了歧义,于洋认为使用简易的命名方式可以更加干练的表示该函数的意思,而我更喜欢将函数名尽量写的详细,尽量不省略单词。对于这个问题我们查阅了阿里的命名规则,事实证明,更完整的函数名可以提高程序的可读性,我俩在互换编程的时候都可以轻松读懂对方的函数以及变量名称。

3.3 是枚举还是随机

遇到添加括号问题时,我首先想到的是随机,但是要判断的次数太多了,每次都对括号是否插入进行50%的判断时工作量就特别大,这时于洋提出只有4个数进行枚举所以出现括号的情况这种方法,我起初是排斥的,因为枚举10种情况只能满足4个数的表达式,当对程序进行扩展时就要重新枚举,代码扩展性不好,但是考虑到此题只要求4个数,因此进过一番讨论后我们决定将“四则运算试题生成” 1.0 的版本通过枚举实现,该版本只能生成4个数的表达式。

3.4 每个文件中代码行数是否需要控制

当我完成功能一时代码量就已经达到170多行,我认为继续添加功能二就需要再新建一个文件,而于洋认为程序并不大,只需要在一个文件中编码即可。在一番讨论之后我们决定为以后功能模块的添加做准备,将每个功能做区分,将文件中行数精简,为此我们单独写了一个表达式生成类放在一个单独的文件中。

3.5 工期拖延难解决

由于是结对编程,为了充分利用时间,我们制定了计划,当一个人编程时另一个人完成其他作业,可总是在即将交接工作时经常出现bug或者不能及时完成,然后出现于洋等好久我也没有改完这个bug,反过来也是如此。也通过这次经验我发现在只能任务计划的时候要尽可能多的分析需求制定合理的时间表。

 

 

4.给出照片1张,包括结对的2位同学、工作地点、计算机,可选项包括其他能表达结对编程工作经历的物品或场景。

工作地点:信息科学与技术学院 数据可视化与可视分析实验室

使用我的电脑进行编程

 

5.Git地址

本次程序的Git地址:https://git.coding.net/tianl364/Arithmetic_Demo.git

转载于:https://www.cnblogs.com/94V587/p/9753717.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值