Python实现小学四则运算

Github传送门:https://github.com/Chen5173/Math

需要实现功能:

1. 使用 -n 参数控制生成题目的个数

2. 使用 -r 参数控制题目中数值(自然数、真分数和真分数分母)的范围

3. 生成的题目中计算过程不能产生负数,也就是说算术表达式中如果存在形如e1 − e2的子表达式,那么e1 ≥ e2

4. 生成的题目中如果存在形如e1 ÷ e2的子表达式,那么其结果应是真分数。

5. 每道题目中出现的运算符个数不超过3个。其中真分数在输入输出时采用如下格式,真分数五分之三表示为3/5,真分数二又八分之三表示为2’3/8。

6. 在生成题目的同时,计算出所有题目的答案,并存入执行程序的当前目录下的Answers.txt文件。

7. 程序应能支持一万道题目的生成。

8. 程序支持对给定的题目文件和答案文件,判定答案中的对错并进行数量统计,统计结果输出到文件Grade.txt

9.生成的题目不会有重复的

程序简述

程序分析

程序大体分成两部分:题目的生成题目的答案的计算与核对

题目生成(昆乘负责):

 

题目答案的生成(我负责):

1、首先,在题目答案计算出来之前,根据需求可知,在等式整个运算过程之中不能出现负数,所以,需要将等式每一步都要进行运算,只要出现一次负数,则重新生成新的算式。因为题目生成的随机性,故而算式的运算符顺序、括号的位置等都会影响到每一步计算的结果,故而必须等到等式完全生成后方可计算。

2、在判断过程中,先是找到最右边的左括号,这样能够保证左括号右边的算式一定是先执行的,在再之后是获取从最左边左括号起第一个右括号,将两括号里面的算式提取出来。

3、对于提取出来的算式,其必定是没有括号在里面的,此时,先是获取算式中符号的优先级。实现符号优先级的做法是先对符号的类别进行定义,“ + - * /  ”分别使用10,20,30,40对其进行区分,而其个位数,则可用来代表该符号在算式中的字符位置。使用一个整型列表进行存放,例如[31,25,12,43],则可以表示为先对表达式中位置索引为1处的运算符进行运算,其值大于30,则表示该符号为*,然后每计算一次后,将结果反正该符号左边,同时,将表达式右边下一个符号处以后的字符往前挪,形成新的表达式,例如:1+3*4-5在运算一步后表达式变为1+12-5;同时,在进行除法运算之前判断其除数是否为0,以及减法运算后其值是否小于0,如果除数为0,或者减法运算后的结果为负的,则将该式子判断为不符合条件的式子,舍弃。

4、答案生成过程中使用正则表达式进行运算。

5、对于答案的比较,则是打开文件后读取两个文件每一行的信息,然后对于学生写入答案的位置如果是在题目文件中,则查找最后一个等号的位置,读取等号后面的字符串,同时也读取标准答案文件中的每一行的字符串,在除去字符串中的空格后进行字符对比,以此来判断答案的正误。

相关模块代码

生成题目文件的类

 1 class Genera:
 2     'sc'
 3     def __init__(self, numbers, range):
 4         'self.numbers 是生成题目总个数, self.range 是数值的范围'
 5         self.numbers = numbers
 6         self.range = range
 7         self.filename = 'Exercises.txt'
 8         self.Fomulas()
 9 
10     def GeneralOneFormula(self):
11         Range = self.range
12         OperateNumbers = random.randint(1, 3)
13         CountNUmbers = OperateNumbers + 1
14         Ostyle = ['+', '-', '*', '÷']
15         OperateStyle = random.choice(Ostyle)
16 
17         # 生成符号list
18         Operates = []
19         a = 0
20         while (a <= OperateNumbers):
21             Operates.append(random.choice(Ostyle))
22             a += 1
23         # 生成数字list与括号list
24         Counts = []
25         i = CountNUmbers
26         while (i > 0):
27             if (random.randint(1, 10) != 1):
28                 term = str(random.randint(1, Range))
29                 Counts.append(term)
30             else:
31                 term = [str(random.randint(1, Range)), '/', str(random.randint(1, Range))]
32                 termT = ''.join(term)
33                 # 此处插入分数化简
34                 Counts.append(termT)
35             i -= 1
36         if ((Operates.count('-') != 0) and (Operates.count('+') != 0) and (random.randint(1, 6) == 1)):  # 假定1/6的括号生成概率
37             leftPosition = random.randint(1, OperateNumbers) - 1
38             rightPosition = random.randint(leftPosition + 2, OperateNumbers + 1) - 1
39             term = '(' + str(Counts[leftPosition])
40             Counts[leftPosition] = term
41             term = str(Counts[rightPosition]) + ')'
42             Counts[rightPosition] = term
43         # 合并符号list 数字括号list
44         FinalList = []
45         j = 0
46         k = 0
47         i = OperateNumbers + CountNUmbers - 1
48         while (i >= 0):
49             if (i % 2 != 1):
50                 FinalList.append(Counts[j])
51                 j += 1
52             else:
53                 FinalList.append(Operates[k])
54                 k += 1
55             i -= 1
56         FinalList = ''.join(FinalList)
57         return FinalList
58 
59     def Fomulas(self):
60         Range = self.range
61         Numbers = self.numbers
62         ' 生成多个Formula并写入文档 '
63         file = open("Exercises.txt", 'a+')
64         for i in range(1, Numbers + 1):
65             print(str(self.GeneralOneFormula()), file=file)
66         file.close()

生成题目答案文件的类

class Answer:
    '这是用于生成任何题目文件的结果到Answers.txt中的类'

    def __init__(self, FileName):
        self.file = FileName
        self.OpenAFile()

    def mul_divOperation(self, s):
        sub_str = re.search('(\d+\.?\d*[*/]-?\d+\.?\d*)', s)
        while sub_str:
            sub_str = sub_str.group()
            if sub_str.count('*'):
                l_num, r_num = sub_str.split('*')
                s = s.replace(sub_str, str(float(l_num) * float(r_num)))
            else:
                l_num, r_num = sub_str.split('/')
                s = s.replace(sub_str, str(float(l_num) / float(r_num)))
            sub_str = re.search('(\d+\.?\d*[*/]\d+\.?\d*)', s)
        return s

    def add_minusOperation(self, s):
        s = '+' + s
        tmp = re.findall('[+\-]\d+\.?\d*', s)
        s = str(functools.reduce(lambda x, y: float(x) + float(y), tmp))
        return s

    def compute(self, formula):
        formula = self.mul_divOperation(formula)
        formula = self.add_minusOperation(formula)
        return formula

    def calc(self, formula):
        """计算程序入口"""
        if (formula[0] == '(' and formula[len(formula) - 1] == ')'):
            formula = formula.replace('(', '')
            formula = formula.replace(')', '')
        formula = re.sub('[^.()/*÷\-+0-9]', "", formula)  # 清除非算式符号
        if(formula[1] == '.'):
            formula = formula.replace(formula[0:2],'') # 计算含有题目序列号的标准算式
        has_parenthesise = formula.count('(')
        while has_parenthesise:
            sub_parenthesise = re.search('\([^()]*\)', formula)  # 匹配最内层括号
            if sub_parenthesise:
                formula = formula.replace(sub_parenthesise.group(), self.compute(sub_parenthesise.group()[1:-1]))
            else:
                has_parenthesise = False
        ret = self.compute(formula)
        return ret

    def Transfer(self, formula):
        '这是一个把小数字符串转换成分数的函数'
        i = formula.find('.')
        if (i != -1 and formula.find('-') == -1):  # 如果存在小数点,只取小数点后三位
            e = float(formula[0:i + 4])
            intE = int(e)
            term = round(e - intE, 4)  # 小数部分四舍五入
            if (term == 0): return formula[:i]
            termD = term * 1000
            Deno = 1000
            if (termD % 333 == 0): Deno = 999  # 优化小学生算术题中常出现的1/3
            while (termD != Deno):  # 求最大公约数以化简
                if (Deno > termD): Deno = Deno - termD
                if (termD > Deno): termD = termD - Deno
            term = int(term * 1000 / termD)
            Deno = int(1000 / termD)
            if (intE != 0): answers = [str(intE), '\'', str(term), '/', str(Deno)]
            if (intE == 0): answers = [str(term), '/', str(Deno)]
            answers = ''.join(answers)
            return answers
        else:
            return formula

    def OpenAFile(self):
        fileE = open(self.file, "r+")
        string = fileE.read()
        fileE.close()
        string = string.replace('÷', '/')
        out = ""
        for line in string.splitlines():
            # out = out + self.compute(line) + '\n'
            out = out.replace('+', '')
            out = out + self.Transfer(self.calc(line)) + '\n'
        fileA = open("Answers.txt", "w+")
        print(out, file=fileA)
        fileA.close()

判断整个过程当中是否产生负数的类

class Verify:
    '这是一个用于修正有负数结果的式子,判断式子是否有重复,以及生成题目序号的类,判断/后面有没有0'

    # 筛选出等式中的符号
    def __init__(self, FileName):
        self.file = FileName
        self.VerifyAFile()

    def VerifyAFile(self):
        No = 1
        with open(self.file) as r:
            lines = r.readlines()
        with open('StandExercises.txt', 'w') as w:
            for l in lines:
                s = l
                s = s.replace('÷', '/')
                if ((self.math_compute(s) == 1)):
                    position = re.search('\Z', l).end()
                    l = l.replace(l[position - 1], ' = \n')
                    l = str(No) + '. ' + l
                    w.write(l)
                    No += 1
        # for line in string.splitlines():
        #     answer = self.math_compute(line)
        #     if(answer == 0):
        #         fileE.write('')
        r.close()
        w.close()
        # out = out + answer + '\n'
        # fileA = open("Answers.txt", "w+")
        # print(out, file=fileA)
        # fileA.close()

    def filt_sym(self, e1_fs):
        sym_get = ""
        for sym in e1_fs:
            if sym == '+' or sym == '-' or sym == '*' or sym == '/':
                sym_get = sym_get + sym
        return sym_get

    # 筛选出等式中的数字
    def filt_num(self, e1_fn):
        num_get = []
        num_c = ""
        for num in e1_fn:
            if num != '+' and num != '-' and num != '*' and num != '/':
                flag = 1
                num_c += num
            else:
                flag = 0
            if flag == 0:
                num_get = num_get + [float(num_c)]
                num_c = ""
        num_get = num_get + [float(num_c)]
        return num_get

    # 判断优先级
    def judge_pri(self, sym_int):
        i = 0
        sym_p = []
        for sym_jp in sym_int:
            if sym_jp == '/':
                sym_p += [40 + i]
                i += 1
            elif sym_jp == '*':
                sym_p += [30 + i]
                i += 1
            else:
                i += 1
        i = 0
        for sym_jp in sym_int:
            if sym_jp == '-':
                sym_p += [20 + i]
                i += 1
            elif sym_jp == '+':
                sym_p += [10 + i]
                i += 1
            else:
                i += 1
        return sym_p

    # 等式运算计算细节实现
    def int_compute(self, num_int, sym_int):
        sym_p_int = self.judge_pri(sym_int)
        while sym_p_int != []:
            sym = int(sym_p_int[0])
            if sym >= 40:
                if num_int[sym - 40 + 1] == 0:
                    return -1
                num_int[sym - 40] /= num_int[sym - 40 + 1]
                num = num_int[sym - 40: sym - 40 + 1]
                del num_int[sym - 40 + 1: sym - 40 + 2]
                sym_int = sym_int[:sym - 40] + sym_int[sym - 40 + 1:]
            elif sym >= 30:
                num_int[sym - 30] *= num_int[sym - 30 + 1]
                num = num_int[sym - 30: sym - 30 + 1]
                del num_int[sym - 30 + 1: sym - 30 + 2]
                sym_int = sym_int[:sym - 30] + sym_int[sym - 30 + 1:]
            elif sym >= 20:
                num_int[sym - 20] -= num_int[sym - 20 + 1]
                num = num_int[sym - 20: sym - 20 + 1]
                if num[0] < 0:
                    return -1
                del num_int[sym - 20 + 1: sym - 20 + 2]
                sym_int = sym_int[:sym - 20] + sym_int[sym - 20 + 1:]
            elif sym >= 10:
                num_int[sym - 10] += num_int[sym - 10 + 1]
                num = num_int[sym - 10: sym - 10 + 1]
                del num_int[sym - 10 + 1: sym - 10 + 2]
                sym_int = sym_int[:sym - 10] + sym_int[sym - 10 + 1:]
            sym_p_int = self.judge_pri(sym_int)
        return float(num[0])

    # 等式运算
    def compute_c(self, e1):
        num_int = float()
        num_int = self.filt_num(e1)
        sym_int = self.filt_sym(e1)
        flag = self.int_compute(num_int, sym_int)
        if flag < 0:
            return 'f'
        else:
            return str(flag)

    # 将等式中括号里面的等式提取出来
    def judge_bracket(self, equ_j):
        left = equ_j.rfind('(')
        right = equ_j.find(')', left)
        e1 = equ_j[left + 1:right]
        c1 = self.compute_c(e1)
        if c1 == 'f':
            return False
        equ_j = equ_j[0:left] + str(c1) + equ_j[(left + len(c1)):]
        equ_j = equ_j[0: left + len(str(c1))] + equ_j[right + 1:]
        return equ_j

    def math_compute(self, equation):
        equ_m = equation
        while equ_m.find('(') != -1:
            if equ_m.find('(') != -1:
                equ_m = self.judge_bracket(equ_m)
                if not equ_m:
                    break;
            else:
                break
        if not equ_m:
            return 0
        elif equ_m.find('+') != -1 or equ_m.find('-') != -1 or equ_m.find('*') != -1 or equ_m.find('/') != -1:
            val = self.compute_c(equ_m)
            if val == 'f':
                return 0
            else:
                return 1
        else:
            return 1

判断用户输入答案与标准答案的类

class Judge:
    '判断Exercises 和 Answers.txt ,并返回处理结果'
    def __init__(self, FileName,FilenameAns):
        self.user_file = FileName
        self.standAns_file = FilenameAns
        self.judge_ans(self.user_file,self.standAns_file)

    def judge_ans(self,user_ans, stand_ans):
        user_a = open(user_ans, 'r')
        std_a = open(stand_ans, 'r')
        i = 0
        c_sum = []
        e_sum = []
        while 1:
            equa_u = user_a.readline()
            equa_s = std_a.readline()
            if not equa_u:
                break
            ind = equa_u.rfind('=')
            if equa_u[ind + 1:].strip() == equa_s.strip():
                i += 1
                c_sum += [i]
            else:
                i += 1
                e_sum += [i]
        print("Correct: ", len(c_sum), c_sum)
        print("Wrong: ", len(e_sum), e_sum)

运行贴图

效能分析

 

存在问题

1、判断两条式子是否相同没有实现,有个初步的想法是利用hash表,将答案相同的式子放在一起,但因为时间和能力有限,暂时没有办法完成;

2、对于分数的运算的结果,没办法很精确的等到一个分数,只能等到一个近似值;

3、程序能够判断除0错误和出现负数错误,但却对于判断不合格的题目不能在之后补回来

个人感悟

两个人合作的一个项目,相比于一个人做项目而言,会更加的累,更加的幸苦,但同时也对于问题的查找也更加全面和仔细。对于两个人合作一个项目,是在两个人的不同想法、思维方式、做事风格方面的碰撞之下来完成的。在前期,因为两个人在没有分配好任务的情况之下,也没有一起讨论时,我的合作伙伴已经开始着手开始搞题目的生成了,而在这种情况,也很快出现了问题。写出来的代码和题目需求存在很多问题。两个人都得重新审题审需求。然而在重新审题之后,两个人的分工依然不明确,昆乘在生成题目的同时,也按照自己的想法使用正则表达式来运算了等式的结果,而我想在一步一步计算的时候也运算结果,这使得两个人在此时产生了分歧,但最终因为我写出来的分数运算时存在一些bug而选择采用他的形式。之后他又负责写传参部分,我负责写核对答案部分。这次结对编程项目个人觉得很失败,两个人在没有分工以及讨论的情况之下就开始独自一个人搞,使得整个过程搞得跟艰难,同时因为这次也是自己第一次使用python进行做编程项目,所以,对于花在学习python上的时间有很多。经过这次结对编程体验,一方面是对结对编程有了一个崭新的了解,另外一方面,也知道的提前分工安排好并且多沟通的重要性。

猜你喜欢

转载自www.cnblogs.com/small-vegetable-bird/p/9727284.html