每天起床第一句,四则运算我爱你!
一、项目说明
成员:林立新 林俊博
项目github地址:https://github.com/06linxi/topicgennerator
实现一个自动生成小学四则运算题目的命令行程序。
一、需求:
1. 使用 -n 参数控制生成题目的个数,例如
Myapp.exe -n 10
将生成10个题目。
2. 使用 -r 参数控制题目中数值(自然数、真分数和真分数分母)的范围,例如
Myapp.exe -r 10
将生成10以内(不包括10)的四则运算题目。该参数可以设置为1或其他自然数。该参数必须给定,否则程序报错并给出帮助信息。
3. 生成的题目中计算过程不能产生负数,也就是说算术表达式中如果存在形如e1 − e2的子表达式,那么e1 ≥ e2。
4. 生成的题目中如果存在形如e1 ÷ e2的子表达式,那么其结果应是真分数。
5. 每道题目中出现的运算符个数不超过3个。
6. 程序一次运行生成的题目不能重复,即任何两道题目不能通过有限次交换+和×左右的算术表达式变换为同一道题目。
例如,23 + 45 = 和45 + 23 = 是重复的题 目,6 × 8 = 和8 × 6 = 也是重复的题目。3+(2+1)和1+2+3这两个题目是重复的,
由于+是左结合的,1+2+3等价于(1+2)+3,也就是3+(1+2),也就是3+(2+1)。但是1+2+3和3+2+1是不重复的两道题,因为
1+2+3等价于(1+2)+3,而3+2+1等价于(3+2)+1,它们之间不能通过有限次交换变成同一个题目。
生成的题目存入执行程序的当前目录下的Exercises.txt文件,格式如下:
1. 四则运算题目1
2. 四则运算题目2
……
其中真分数在输入输出时采用如下格式,真分数五分之三表示为3/5,真分数二又八分之三表示为2’3/8。
7. 在生成题目的同时,计算出所有题目的答案,并存入执行程序的当前目录下的Answers.txt文件,格式如下:
1. 答案1
2. 答案2
特别的,真分数的运算如下例所示:1/6 + 1/8 = 7/24。
8. 程序应能支持一万道题目的生成。
9. 程序支持对给定的题目文件和答案文件,判定答案中的对错并进行数量统计,输入参数如下:
Myapp.exe -e <exercisefile>.txt -a <answerfile>.txt 。统计结果输出到文件Grade.txt,格式如下:Correct: 5 (1, 3, 5, 7, 9),Wrong: 5 (2, 4, 6, 8, 10)。
二、未实现的功能
1、 程序一次运行生成的题目不能重复
三、PSP
PSP2.1 |
Personal Software Process Stages |
预估耗时(分钟) |
实际耗时(分钟) |
Planning |
计划 |
30 |
30 |
· Estimate |
· 估计这个任务需要多少时间 |
10 |
30 |
Development |
开发 |
600 |
900 |
· Analysis |
· 需求分析 (包括学习新技术) |
60 |
120 |
· Design Spec |
· 生成设计文档 |
10 |
10 |
· Design Review |
· 设计复审 (和同事审核设计文档) |
60 |
60 |
· Coding Standard |
· 代码规范 (为目前的开发制定合适的规范) |
30 |
30 |
· Design |
· 具体设计 |
30 |
60 |
· Coding |
· 具体编码 |
1200 |
900 |
· Code Review |
· 代码复审 |
60 |
60 |
· Test |
· 测试(自我测试,修改代码,提交修改) |
30 |
30 |
Reporting |
报告 |
60 |
60 |
· Test Report |
· 测试报告 |
20 |
10 |
· Size Measurement |
· 计算工作量 |
10 |
20 |
· Postmortem & Process Improvement Plan |
· 事后总结, 并提出过程改进计划 |
30 |
60 |
合计 |
|
|
四、设计思路
1、写一个随机生成整数或者分数的函数,写一个随机生成运算符的函数,写一个随机生成表达式组成部分的列表。
2、利用二叉树对表达式组成部分的列表进行处理,返回一个规范的表达式组成部分的列表,利用join()把列表元素连接在一起,输出标准表达式。
3、利用二叉树以及定义的加减乘除运算的法则,输出假分数,再调用假分数转化为真分数的方法,将结果转化为正分数。
4、对输入题目文件,逐行读取,将每一行转化为列表的形式,并利用方法第2点,第3点,输出其结果与答案文件中每一行答案对比,判断对错。
五、具体代码实现
这是自己定义的加减乘除函数:
def calculate(a, b, c): if len(a.split('/')) == 2: m = a.split('/') fzm = m[0] fmm = m[1] else: fzm = int(a) fmm = 1 if len(b.split('/')) == 2: n = b.split('/') fzn = n[0] fmn = n[1] else: fzn = int(b) fmn = 1 if c == '+': Fz = int(fzm) * int(fmn) + int(fmm) * int(fzn) Fm = int(fmm) * int(fmn) return str(Fz) + '/' + str(Fm) if c == '-': Fz = int(fzm) * int(fmn) - int(fmm) * int(fzn) Fm = int(fmm) * int(fmn) return str(Fz) + '/' + str(Fm) if c == '×': Fz = int(fzm) * int(fzn) Fm = int(fmm) * int(fmn) return str(Fz) + '/' + str(Fm) if c == '÷': Fz = int(fzm) * int(fmn) Fm = int(fmm) * int(fzn) return str(Fz) + '/' + str(Fm)
这是对给定的题目文件和答案文件,判定答案中对错的数量和具体的题目实现函数。
def compare(exercisefile ,answerfile ): correct = [] wrong = [] with open(answerfile, 'r') as a: text = a.readlines() hangshu = len(text) for i in range(hangshu): # 读取答案文件的行数,循环对比 # 第i行 with open(exercisefile, 'r') as problem: timuhang = problem.readlines() a = [] question = timuhang[i].split() b='' for size in question[1]: if size not in "+-×÷()": b=b+size else: if b!='': a.append(b) b='' a.append(size) if b!='': a.append(b) with open(answerfile, 'r') as filea: text = filea.readlines() l = len(text) answer=text[i].split() result = answer[1] if str(result) == str(exchangesize(check(create_expression_tree(postfix_convert(a))))): correct.append(i + 1) else: wrong.append(i + 1) print('Correct: {}{}'.format(len(correct), tuple(correct)) + '\n' + 'Wrong: {}{}'.format(len(wrong), tuple(wrong)))
使用表达式树进行计算并避免运算过程出现假分数和负数
def postfix_convert(exp):
'''
将表达式字符串,转为后缀表达式
如exp = "1+2*(3-1)-4"
转换为:postfix = ['1', '2', '3', '1', '-', '*', '+', '4', '-']
'''
stack = [] # 运算符栈,存放运算符
postfix = [] # 后缀表达式栈
for char in exp:
# print char, stack, postfix
if char not in operator_precedence: # 非符号,直接进栈
postfix.append(char)
else:
if len(stack) == 0: # 若是运算符栈啥也没有,直接将运算符进栈
stack.append(char)
else:
if char == "(":
stack.append(char)
elif char == ")": # 遇到了右括号,运算符出栈到postfix中,并且将左括号出栈
while stack[-1] != "(":
postfix.append(stack.pop())
stack.pop()
elif operator_precedence[char] > operator_precedence[stack[-1]]:
# 只要优先级数字大,那么就继续追加
stack.append(char)
else:
while len(stack) != 0:
if stack[-1] == "(": # 运算符栈一直出栈,直到遇到了左括号或者长度为0
break
postfix.append(stack.pop()) # 将运算符栈的运算符,依次出栈放到表达式栈里面
stack.append(char) # 并且将当前符号追放到符号栈里面
while len(stack) != 0: # 如果符号站里面还有元素,就直接将其出栈到表达式栈里面
postfix.append(stack.pop())
return postfix
# ===========================这部分用于构造表达式树================================#
class Node(object):
def __init__(self, val):
self.val = val
self.left = None
self.right = None
def create_expression_tree(postfix):
"""
利用后缀表达式,构造二叉树
"1+2*(3-1)-4"
['1', '2', '3', '1', '-', '*', '+', '4', '-']
"""
stack = []
# print postfix
for char in postfix:
if char not in operator_precedence:
# 非操作符,即叶子节点
node = Node(char)
stack.append(node)
else:
# 遇到了运算符,出两个,进一个。
node = Node(char)
node.right = stack.pop()
node.left = stack.pop()
stack.append(node)
# 将最后一个出了即可。
return stack.pop()
# 避免出现负数,假分数
def check(tree):
if tree:
if tree.left == None:
return tree.val
num1 = check(tree.left)
num2 = check(tree.right)
if tree.val == '-':
if eval(num1 + '-' + num2) < 0:
supporter = tree.left
tree.left = tree.right
tree.right = supporter
return calculate(num2, num1, '-')
else:
return calculate(num1, num2, '-')
elif tree.val == '÷':
if eval(num1 + '/' + num2) > 1:
supporter = tree.left
tree.left = tree.right
tree.right = supporter
return calculate(num2, num1, '÷')
else:
return calculate(num1, num2, '÷')
elif tree.val == '+':
return calculate(num1, num2, '+')
elif tree.val == '×':
return calculate(num1, num2, '×')
# 将树输出为字符串
def exchangetree(tree, equation):
if tree:
if tree.left == None:
equation.append(tree.val)
else:
equation.append('(')
exchangetree(tree.left, equation)
equation.append(tree.val)
exchangetree(tree.right, equation)
equation.append(')')
六、测试结果
1、生成10000道题目的截图
2、生成10000道题目的答案截图
3、校对题目答案的截图
七、项目小结
- 本次任务分工实现,事先和队友分析了整个思路,确定了各自实现的部分,出现问题,相互讨论,最后汇总,一起实现剩下的功能板块以及优化。
- 在完成整个项目后,回顾整个项目,早期各自想思路时花费时间较多,也比较困难,在讨论之后的实现过程效率较单人思考会更高。首次合作,配合度也还不错,分工明确,效率高,但是由于单人思考时间花费的多了一些,导致后面的交流讨论不是很够,希望以后可以更合理的分配时间。