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上的时间有很多。经过这次结对编程体验,一方面是对结对编程有了一个崭新的了解,另外一方面,也知道的提前分工安排好并且多沟通的重要性。