每天起床第一句,四则运算我爱你!

一、项目说明

成员:林立新 林俊博

项目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、校对题目答案的截图

 

 

 

七、项目小结

  1. 本次任务分工实现,事先和队友分析了整个思路,确定了各自实现的部分,出现问题,相互讨论,最后汇总,一起实现剩下的功能板块以及优化。
  2. 在完成整个项目后,回顾整个项目,早期各自想思路时花费时间较多,也比较困难,在讨论之后的实现过程效率较单人思考会更高。首次合作,配合度也还不错,分工明确,效率高,但是由于单人思考时间花费的多了一些,导致后面的交流讨论不是很够,希望以后可以更合理的分配时间。

 

posted @ 2018-09-30 15:38  信安二参谋长  阅读(346)  评论(0编辑  收藏  举报