A, Github project Address: https://github.com/asswecanfat/git_place/tree/master/oper_make
Two, PSP2.1 table:
PSP2.1 | Personal Software Process Stages | Estimated time consuming (minutes) | The actual time-consuming (minutes) |
---|---|---|---|
Planning | plan | 150 | 150 |
· Estimate | • Estimate how much time this task requires | 150 | 150 |
Development | Develop | 1340 | 1720 |
· Analysis | · demand analysis | 50 | 60 |
· Design Spec | Generate design documents | 40 | 60 |
· Design Review | · Design Review | 30 | 30 |
· Coding Standard | · Code Specification | 20 | 20 |
· Design | · Specific design | 100 | 120 |
· Coding | · Specific coding | 1000 | 1250 |
· Code Review | · Code Review | 60 | 80 |
· Test | · Test (self-test, modify the code, submit modifications) | 40 | 100 |
Reporting | report | 90 | 130 |
· Test Report | · testing report | 50 | 80 |
· Size Measurement | · Computing workload | 20 | 20 |
· Postmortem & Process Improvement Plan | · Hindsight, and propose process improvement plan | 20 | 30 |
total | 1580 | 2000 |
Third, performance analysis
1. write their own context manager to record the time and compare performance (because of click module led module built timeit not easy to use)
2. optimization process:
a. Before the write_file file, which is a method of write_in_file open a file on every call to generate ten thousand questions to 16.3 seconds (forget the screenshot)
b math_op iterators dictionary and by - pass in a list structure as parameters in iteration write_in_file method, the time becomes 2 to 3 seconds
Fourth, the design and implementation process
1. After reading the title, just the two of us in the evening discussion, first check the weight of the data structure of a mathematical expression has been explored, I would like to start with a dictionary, O (1) query time, but the key value may be repeated to give up, and finally I thought of using + - method * / list of links to child together, but in the process of implementation, we build a unified into a binary tree, in order to re-check. In the mathematical expression generated in a recursive hierarchical I used, the function is a randomly selected number of layers, up to 3, at least 1, n = 2 for recursive number of layers to achieve a completely random mathematical expressions.
2. The idea is to -r and -n received parameters to control expression of the number and range of values, and the random number by the number of layers recursive class (which may generate a score (whether true or false) and integer) and a method of generating random parentheses generating a mathematical expression string, how difficulty is to construct a unified binary tree.
3. mathematical expression string we use Reverse Polish Notation, is postfix expression, the mathematical expression passed to AnalyOp.check_math_opj static methods for processing to determine whether an error or return the child list, Reverse Polish Notation and answers . We learn from an online source, later found inside the source code defects, modify the source code yourself, to solve the BUG generated when no parentheses, generate a prioritized, only two numbers for computing and sub-list Reverse Polish expression, as well as answers.
4. Then the sub-list and Reverse Polish Notation passed to re-check module and decided to write the challenge is how to build a unified binary tree, behind I used an identifier - the smallest bit to solve the problem and build Dictionary - list of data structures is O (1 + m) queries, where the key is the answer, the answer to the first comparison with a binary tree list before deciding whether or not the same insert.
The click of particularity, wherein the or -r -n parameter must be added.
6. Fraction class constructed with integers and fractions, such to achieve a good __add __ () method and the like magic operation between two numbers
Function code structure:
data_sturct.py: dictionary defines - list of data structures and the trees of nodes (Node datasave classes and class)
duplicate_check.py: Construction of the binary tree and defines a weight check (creat_tree is_equal Method and Method)
main.py: run method for running
math_op.py: Creat defined class, which has a single mathematical expression creator method for constructing and building a plurality of generator creat_more
math_op_analysis.py: AnalyOp defined class, using RPN stack implementation and computing, as well as static methods check_math_op check the legality of math_op
num_creat.py: NumCreat defined class, randomly generated scores (whether proper fraction improper fraction), there is an improper fraction static method revolutions proper fractions
op_error.py: inherit the parent class ValueError, subclass ReduceError and ExceptError
wirte_file.py: defines the processing mathematical expressions math_op, improper fractions containing the answer, write to the file method
V. Code Description
1. Create mathematical expression class
class Creat(object): def __init__(self, max_num: int, formula_num: int): self.max_num = max_num # 最大范围 self.formula_num = formula_num # 公式最大条数 self.level = random.randint(2, 4) # 递归层数 self.start_level = 0 # 递归开始层 self.first_level = random.randint(1, self.level - 1) # 第一个子式递归次数 self.second_level = self.level - self.first_level # 第二个子式递归次数 self.operator = { 1: '+', 2: '-', 3: '*', 4: '÷', } def creator(self) -> str: math_op = '{}{}{}{}{}'.format( self.__creat_math_op(self.first_level), # 第一个子式 ' ', self.operator[random.randint(1, 4)], # 随机选取运算符 ' ', self.__creat_math_op(self.second_level), # 第二个子式 ) return math_op def __creat_math_op(self, level_choice: int) -> str: random_num = random.randint(0, 1) self.start_level += 1 if self.start_level == level_choice: self.start_level = 0 return random.randint(0, self.max_num) math_op = '{}{}{}{}{}{}{}'.format( self.__brackets(random_num, 0), # '(' NumCreat(self.max_num).choices_num(), # 随机数 ' ', self.operator[random.randint(1, 4)], # 随机选取运算符 ' ', self.__creat_math_op(level_choice), # 'xx +|-|*|/ xx' self.__brackets(random_num, 1), # ')' ) self.start_level = 0 return math_op def __brackets(self, random_num: int, choice: int) -> str: # 决定括号是否填入 if random_num: if choice: return ')' else: return '(' return ''
def creat_more(self, data_save): # 迭代器 op_num = 0 while op_num < self.formula_num: math_op = self.creator() try: postfix, answer = AnalyOp.check_math_op(math_op) math_op = deal_math_op(math_op, list(postfix), answer, data_save) if math_op: yield (math_op, answer) op_num += 1 except (ExceptError, ReduceError, ZeroDivisionError): pass self.__init_variable() def __init_variable(self): # 初始化以下值 self.start_level = 0 self.level = random.randint(2, 4) self.first_level = random.randint(1, self.level - 1) self.second_level = self.level - self.first_level
2.构建逆波兰表达式
class AnalyOp(object): # 使用逆波兰表达式解析 OPERATOR = ('+', '-', '*', '÷', '(', ')') # 运算符常量 def __init__(self, math_op): self.math_op = math_op.replace('(', '( ').replace(')', ' )') self.math_op = self.math_op.split() self.postfix_deque = deque() # 后缀表达式栈 self.operators_deque = deque() # 运算符栈 self.operators_priority = { # 运算符优先级 '+': 1, '-': 1, '*': 2, '÷': 2, } def __clear_deque(self): # 用于清空栈 self.postfix_deque.clear() self.operators_deque.clear() def mathop_to_postfix(self): """将中缀表达式转换为后缀表达式。""" for i in self.math_op: if self.is_num(i): self.postfix_deque.append(i) elif i in self.OPERATOR: if i == '(': self.operators_deque.append(i) elif i == ')': self.__pop_to_left_bracket() else: self.__compare_and_pop(i) self.__pop_rest() return self.postfix_deque @staticmethod def is_num(text): # 判断是否为数字 if '/' in text: return True return text.isdigit() def __pop_to_left_bracket(self): """依次弹栈并追加到后缀表达式,直到遇到左括号为止。""" while self.operators_deque: operator = self.operators_deque.pop() if operator == '(': break self.postfix_deque.append(operator) def __compare_and_pop(self, text): """比较优先级并进行相应操作。""" if not self.operators_deque: self.operators_deque.append(text) return while self.operators_deque: operator = self.operators_deque.pop() if operator == '(': self.operators_deque.append('(') self.operators_deque.append(text) return elif self.operators_priority[text] > self.operators_priority[operator]: self.operators_deque.append(operator) self.operators_deque.append(text) return elif text == operator: self.postfix_deque.append(operator) else: self.postfix_deque.append(operator) self.operators_deque.append(text) def __pop_rest(self): """弹出所有剩余的运算符,追加到后缀表达式。""" while self.operators_deque: self.postfix_deque.append(self.operators_deque.pop()) def parse_out_son(self): """只解析出有优先级的子式""" postfix = deepcopy(self.mathop_to_postfix()) num_deque = deque() son_op_list = [] answer = None for i in self.postfix_deque: if self.is_num(i): num_deque.append(i) else: right_num = num_deque.pop() left_num = num_deque.pop() son_op = f'{left_num}{i}{right_num}' son_op_list.append(son_op) answer = self.operation_func(i)(left_num, right_num) num_deque.append(answer) self.__clear_deque() return son_op_list, postfix, str(answer) @staticmethod def operation_func(operator): # 选择运算方法 operation_dict = { '+': lambda x, y: Fraction(x) + Fraction(y), '-': lambda x, y: Fraction(x) - Fraction(y), '*': lambda x, y: Fraction(x) * Fraction(y), '÷': lambda x, y: Fraction(x) / Fraction(y), } return operation_dict[operator]
3.统一二叉树构建
def creat_tree(postfix_deque): # 创建统一的二叉树 """_min为树结点的最小标志位,左子树小,右子树大""" node = deque() for i in postfix_deque: if AnalyOp.is_num(i): node.append(Node(num=i, answer=i, _min=i)) else: max_num, min_num = node.pop(), node.pop() if i != '÷' and i != '-': if max_num.answer == min_num.answer: max_num, min_num = (max_num, min_num) \ if Fraction(max_num._min) >= Fraction(min_num._min) \ else (min_num, max_num) else: max_num, min_num = (max_num, min_num) \ if Fraction(max_num.answer) > Fraction(min_num.answer) \ else (min_num, max_num) op = Node(operator=i, right_child=max_num, left_child=min_num, answer=AnalyOp.operation_func(i)(min_num.answer, max_num.answer), _min=min_num._min,) node.append(op) return node.pop()
4.题目/答案文件生成
def write_in_file(creat, data_save): # 写入文件 with open(r'./Exercises.txt', 'a', encoding='utf-8') as q: with open(r'./Answers.txt', 'a', encoding='utf-8') as a: for num, (math_op, answer) in enumerate(creat.creat_more(data_save)): answer = deal_answer(answer) q.write('{}.{} = {}'.format(num + 1, math_op, '\n', )) a.write('{}.{}{}'.format(num + 1, answer, '\n')) print('{}{}{}'.format('\r', num + 1, '条题目已生成!!'), sep='', end='', flush=True)
5.文件之间的比较
def compare_2_file(f1, f2): # f1是题目文件,f2是答案文件 coding1 = _get_coding(f1) coding2 = _get_coding(f2) if coding1 is not None and coding2 is not None: with open(f1, 'r', encoding=coding1) as f1: with open(f2, 'r', encoding=coding2) as f2: with open(r'.\Grade.txt', 'w', encoding='utf-8') as g: right_num = 0 right_answer = [] wrong_num = 0 wrong_answer = [] f1_dict = _deal_lsit(f1) f2_dict = _deal_lsit(f2) for i in f1_dict.keys(): if f1_dict[i] == f2_dict[i]: right_num += 1 right_answer.append(i) else: if f1_dict[i] != '': wrong_num += 1 wrong_answer.append(i) g.write(f'Correct: {right_num} ({right_answer[0]}') for i in right_answer[1:]: g.write(f', {i}') g.write('){}Wrong: {} ({}'.format('\n', wrong_num, wrong_answer[0])) for i in wrong_answer[1:]: g.write(f', {i}') g.write('){}'.format('\n')) def _deal_lsit(f) -> dict: f_list = list(map(lambda x: re.split(r'\.(.+= ?)?', x), f.readlines())) # 用于分开题目序号和答案 return {i[0]: i[2].rstrip('\n') for i in f_list} def _get_coding(f): # 处理文件的编码,以防报错 text = open(f, 'rb').read() return chardet.detect(text)['encoding']
六、测试运行
1. 测试-n -r 功能:
a. 单道题目生成和结果正确
b. 两道题目生成和结果正确,-n参数处理正确(与a比较)
c.-r为负数
d.-n为负数
e.只有-r
f.只有-n
g.测试生成10000道题
2 测试验证功能-e -a
a 测试正确的答案文件
b.测试-e和-a指令
c.只有一个指令
3.由于我是递归生成的数学表达式,最多递归4层(总和),不会出现4个以上及4个的运算符
七、项目小结
1.本次项目使用python实现,使用了click处理命令行参数,将中缀表达式转化为后缀表达式以实现数学表达式的解析,使用递归生成子式的方式生成题目,题目中的数字可以为整数或真分数、在生成题目时同步生成答案;在判断提交答案正确率时用正则获取提交部分并将其与正确答案进行比对。
2.第一次接触结对编程这种开发模式,深刻的感受到讨论与监督对于编程任务实现的推进作用,两个人的思路碰撞之后可以推进出更新更全面的想法与思路。