table of Contents
Software engineering twinning projects: Python program to achieve wc
Twinning project Github Address
project members
- Liu Zhihao 3117008744
- Tan Wan Chuan 3117008747
Project requirements
Explanation
Implement a command line program automatically generates four operations primary subject (graphical interface can also be used with similar function).
Natural numbers: 0, 1, 2, ...
Proper fraction: 1/2, 1/3, 2/3, 1/4, 1'1 / 2, ...
Operators: +, -, ×, ÷
Parenthesis :(,)
Equal sign: =
Separator: space (for operator and front four equal sign)
Arithmetic expressions:
e = n | e1 + e2 | e1 - e2 | e1 × e2 | ÷ e1 e2 | (E),
Wherein e, e1 and e2 to the expression, n is a natural number or proper fraction.
Title four operations: e =, where e is the arithmetic expression.
demand
-N generated using the number of control parameters subject, e.g.
Myapp.exe -n 10
The resulting 10 topics.
-R parameter values used in the control subject (natural number, proper fraction, and the denominator of the proper fraction) range, e.g.
Myapp.exe -r 10
The resulting four operations within the subject 10 (not including 10). This parameter can be set to 1 or a natural number other. This parameter must be given, otherwise the program error and gives help information.
Title generated calculations can not produce negative, that is if there is an arithmetic expression in the form subexpression e1- e2, then e1≥ e2.
If present in the task generating substrings of the form of expressions e1 ÷ e2, the result should be a proper fraction .
The number of operators each question appear in no more than 3.
Generated task program run can not be repeated, i.e. the subject can not be any two finite switching × + and approximately the same arithmetic expression converted into the title track . For example, + 45 = 23 + 23 = 45 and the subject is repeated, 6 × 8 = 8 × 6 = and the subject is repeated. 3+ (2 + 1) 1 + 2 + 3 and the two topics are repeated, since the combination of left + 1 + 2 + 3 is equivalent to (1 + 2) + 3, i.e. 3 + (1 + 2), i.e. 3 + (2 + 1). However, 1 + 2 + 3 + 2 + 1 and 3 are not repeated two questions, because 1 + 2 + 3 is equivalent to (1 + 2) + 3, 3 + 2 + 1 and is equivalent to (3 + 2 ) +1 can not be exchanged between them becomes finite the same subject.
Current Exercises.txt files in the directory generated task execution stored procedures in the following format:
Title four operations 1
Four operations subject 2
……
Wherein the following format proper fraction at input and output, expressed as a proper fraction fifths 3/5, two and three eighths proper fraction expressed as 2'3 / 8.
While generating topics, answer all the questions of the calculated and credited to the implementation of the program Answers.txt files in the current directory, in the following format:
- Answer 1
- Answer 2
In particular, the following example calculation proper fractions: 1/6 + 1/8 = 7/24.
Program should be able to support the generation of ten thousand topics.
Support program for a given topic file and answer file to determine right and wrong answers and the number of statistics, input parameters are as follows:
Myapp.exe -e
.txt -a .txt Statistical results are output to a file Grade.txt, in the following format:
Correct: 5 (1, 3, 5, 7, 9)
Wrong: 5 (2, 4, 6, 8, 10)
Wherein ":" followed by the numeral 5 denotes the number of subject / wrong, the parentheses are right / wrong subject number. For simplicity, assume that the subject is entered in accordance with the subject specification compliance sequence number.
PSP table
PSP2.1 | Personal Software Process Stages | Estimated time consuming (minutes) | The actual time-consuming (minutes) |
---|---|---|---|
Planning | plan | 40 | 50 |
· Estimate | • Estimate how much time this task requires | 40 | 50 |
Development | Develop | 1590 | 1995 |
· Analysis | · Needs analysis (including learning new technologies) | 90 | 80 |
· Design Spec | Generate design documents | 60 | 80 |
· Design Review | · Design Review (and his colleagues reviewed the design documents) | 45 | 60 |
· Coding Standard | · Code specifications (development of appropriate norms for the current development) | 25 | 20 |
· Design | · Specific design | 120 | 180 |
· Coding | · Specific coding | 900 | 1200 |
· Code Review | · Code Review | 120 | 125 |
· Test | · Test (self-test, modify the code, submit modifications) | 70 | 60 |
Reporting | report | 40 | 50 |
· Test Report | · testing report | 30 | 50 |
· Size Measurement | · Computing workload | 30 | 30 |
· Postmortem & Process Improvement Plan | · Hindsight, and propose process improvement plan | 60 | 60 |
total | 1630 | 2045 |
Description of problem-solving ideas
By extracting and analyzing the heavy and difficult topic, the team will present the project is divided into Expression Builder module proofreading and correcting answers module; in the Expression Builder module, so that the resulting formula is not repeated very complex, also on the Internet no corresponding algorithm, then use the enumeration as much of the case into account. Problem-solving ideas framework of the project are as follows:
1. Expression generation request
- Randomly generated numbers
- randint function
- Randomly generated symbol
- Randomly from the four operands
- Randomized parentheses case
- combination
- The operands, operators, parentheses expression
- Duplicate Checking
- Enumerated as much as possible the number of different formulas in brackets, and numbers of different operators as in the case
- The case where the equation corresponding to the generated comparison, if the same is False; else return True
- Proper fraction conversion
- If there is an improper fraction, convert to true score
- Score with a fraction module
- Calculate the answer
- When it is determined in parentheses, using the expression of a different order of operations answer calculated
- Formatted input file to
- Randomly generated numbers
2. marking topic, proofreading answer
- Deal with topics
- Extraction operands
- Proper fraction conversion
- Extraction Operator
- The order of operation is determined bracket
- Calculation results
- Extraction operands
- Processing answers
- Compare the previous step and calculate the answers in the answer answer.txt
- Return results
- When compared to the same answer: Correct + 1
- If the ratio is inconsistent answers: Wrong + 1
- Deal with topics
Design and Implementation
Code Organization Chart
Code Analysis
Re-check module (check.py)
First determine whether the string is the same, whether the operator is the same, whether the same number with only one operator, the operator first determines whether or not the same, and then determines if the same numbers are exactly the same
'''对于有两个运算符的,若以上都一样,则有以下几种情况:
1.(NoN)oN
2.No(NoN)
3.NoNoN
将他们每个元素放入list中
1.若list长度均为6(无括号情况):
若两个式子都是list1[1]==list2[1] AND list1[3]==list2[3]:
if list1[0]=list2[4] and list1[2]=list2[2]:
return True
else
return False
else :
reurn False
2.若list1长度为6list2长度为8:
若list2[0]='(':
if list1[1]等于list2[1]或list2[3]
reutnr True
else
return False
3.若list1长度为8,list2长度为6:
若list1[0]='(':
if list2[1]等于list1[1]或list1[3]
reutnr True
else
return False
4.若长度均为8:
判断括号中的数字是否一样即可
对于三个运算符的情况,由于种类较多,若以上都一样,则返回TRUE,否则返回False
'''
def isSame (list,salist):
numlist1=''.join(list).split('+|-|*|/|(|)')
#运算符个数
ops1=''.join(list).split('+|-')
for string in salist:
if numlist1 == string:
return True
else :
#判断数字是否一样
numlist2=string.split('+|-|*|/|(|)')
if len(numlist1)!=len(numlist2):
return False
else :
for i in numlist1 :
if i in numlist2:
continue
else :
return False
#判断运算符个数是否一样
if (len(list)-len(numlist1)) != (len(string)-len(numlist2)):
return False
else:
if len(list)==6 and len(salist)==6:
if list[0]==string[4] and string[2]==list[2] :
if list[0]==salist[4] and list[2]==salist[2]:
return True
else:
return False
else:
return False
if len(list) == 6 and len(salist) == 8:
if salist[0] == '(':
if list[1]==salist[1] or list[1]==salist[3]:
return True
else:
return False
if len(list)==8 and len(salist)==6 :
if list[0] == '(':
if salist[1]==list[1] or salist[1]==list[3]:
return True
else:
return False
if len(list)==8 and len(salist)==8:
return True
def check(list,answer , e_file , a_file):
#读取文件
efile=open(e_file,"r")
afile=open(a_file,"r")
#定义一个list用来存储具有相同结果的式子
salist = []
#先判断库中是否有相同的结果
i=0
j=0
for aline in afile.readlines():
answer=answer.strip()
realanswer=aline.split(':')[1]
realanswer=realanswer.strip()
if answer == realanswer:
i+=1
for eline in efile.readlines():
j+=1
if j == i :
#提取出式子
eline=eline.split(':')[1]
eline=eline.split('=')[0]
salist.append(eline.strip())
break
return (not isSame(list,salist))
return True
Expression generation module (main.py)
import re, os, argparse
from random import randint
from fractions import Fraction
from goto import with_goto
import check, grade
class EA_gen():
def __init__(self):
self.gen_need = 10
self.gen_range = 10
def gen(self):
f = open('./exercise.txt', 'a+')
f2 = open('./answer.txt', 'a+')
f.seek(0)
f2.seek(0)
f.truncate()
f2.truncate()
count = 0
while True:
try:
elist, answer = self.gen_combine()
except Exception as e:
# 临时作处理:当0位除数 和 负数情况
continue
# True表示检查后无重复
if check.check(elist, answer, e_file='./exercise.txt', a_file='./answer.txt') == True:
f.write("题目" + str(count+1) + ": " + ' '.join(elist) + ' =\n')
if re.search('/', answer):
d , n = answer.split('/')
if int(d) > int(n):
answer = self.__to_fraction(answer)
f2.write("答案" + str(count+1) + ": " + answer + '\n')
count += 1
if count == self.gen_need:
break
f.close()
f2.close()
def gen_combine(self):
# 不超过3个运算符
nums_operatior = randint(1, 3)
bracket = 0
n1 = self.gen_num()
op1 = self.gen_operator()
n2 = self.gen_num()
elist = [n1, op1, n2]
# 两步运算以上
if nums_operatior >= 2:
op2 = self.gen_operator()
n3 = self.gen_num()
elist.append(op2)
elist.append(n3)
bracket = randint(0,2)
# 三步运算
if nums_operatior == 3:
op3 = self.gen_operator()
n4 = self.gen_num()
elist.append(op3)
elist.append(n4)
bracket = randint(0,4)
# 插入括号
if bracket != 0:
elist = self.__bracket_insert(elist, bracket)
answer = self.__get_answer(elist, bracket)
if re.search('-', answer):
# 有负号就报错
raise Exception("Negative")
return elist, answer
def __get_answer(self, elist, bracket):
nlist = []
olist = []
flist = []
for i in elist:
if re.match(r'\+|-|x|÷', i):
if i == '÷': i = '/' # 除号转换
if i == 'x': i = '*' # 乘号转换
olist.append(i)
elif re.match(r'\d+', i): nlist.append(i)
else: pass
for j in nlist:
if re.search(r"'", j):
f1, f2 = j.split("'")
fraction = Fraction(f1) + Fraction(f2)
flist.append(fraction)
else: flist.append(Fraction(j))
answer = None
#根据括号情况计算出答案
if bracket == 0:
if len(olist) == 1:
answer = eval("flist[0] %s flist[1]" % (olist[0]))
if len(olist) == 2:
answer = eval("flist[0] %s flist[1] %s flist[2]" % (olist[0], olist[1]))
if len(olist) == 3:
answer = eval ('flist[0] %s flist[1] %s flist[2] %s flist[3]'%(olist[0], olist[1], olist[2]))
if bracket == 1:
if len(olist) == 2:
answer = eval("(flist[0] %s flist[1]) %s flist[2]" % (olist[0], olist[1]))
if len(olist) == 3:
answer = eval('(flist[0] %s flist[1]) %s flist[2] %s flist[3]' % (olist[0], olist[1], olist[2]))
if bracket == 2:
if len(olist) == 2:
answer = eval("flist[0] %s (flist[1] %s flist[2])" % (olist[0], olist[1]))
if len(olist) == 3:
answer = eval('flist[0] %s (flist[1] %s flist[2]) %s flist[3]' % (olist[0], olist[1], olist[2]))
if bracket == 3:
answer = eval ('flist[0] %s flist[1] %s (flist[2] %s flist[3])'%(olist[0], olist[1], olist[2]))
if bracket == 4:
answer = eval ('(flist[0] %s flist[1]) %s (flist[2] %s flist[3])'%(olist[0], olist[1], olist[2]))
return str(answer)
def __bracket_insert(self, elist, bracket):
if bracket == 1:
elist.insert(0, '(')
elist.insert(4, ')')
if bracket == 2:
elist.insert(2, '(')
elist.insert(6, ')')
if bracket == 3:
elist.insert(4, '(')
elist.insert(8, ')')
if bracket == 4:
elist.insert(0, '(')
elist.insert(4, ')')
elist.insert(6, '(')
elist.insert(10, ')')
return elist
# 插入括号位置的四种情况
# 1:(NoN)oNoN;
# 2:No(NoN)oN;
# 3:NoNo(NoN);
# 4:(NoN)o(NoN):
def gen_operator(self):
operators = ['+', '-', 'x', '÷']
return operators[randint(0,len(operators) - 1)]
def gen_num(self):
#是否用真分数
flag_is_rf = randint(0,1)
if flag_is_rf is 1:
n = self.gen_fraction()
else: n =str(randint(0, self.gen_range - 1))
# 返回的是str类型
return n
def gen_fraction(self):
denominator = randint(2, self.gen_range)
numerator = randint(1, denominator - 1)
random_attach = randint(0, 1)
real_fraction = str(Fraction(numerator, denominator))
# 调用fraction方法生成真分数
if random_attach != 0:
real_fraction = str(random_attach) + "'" + real_fraction
return real_fraction
def __to_fraction(self, fraction):
f = Fraction(fraction)
denominator = f.denominator
numerator = f.numerator
attach = int(numerator / denominator)
denominator = numerator - attach * denominator
real_fraction = str(attach) + "'" + str(denominator) + '/' + str(numerator)
return real_fraction
def opt():
parser = argparse.ArgumentParser()
# 设置四个选项
parser.add_argument("-n", dest = "need", help = "生成数量")
parser.add_argument("-r", dest = "range", help = "生成范围")
parser.add_argument("-e", dest = "grade_e", help = "练习文件" )
parser.add_argument("-a", dest = "grade_a", help = "答案文件" )
args = parser.parse_args()
return args
def main():
args = opt()
# #测试用
# args.range = 100
# args.need = 100
# args.grade_e = "exercise.txt"
# args.grade_a = "answer.txt"
# 这里简化下操作:-n-r输入 或 -e-a输入 两种操作情况。
if args.range and args.need:
ea = EA_gen()
ea.gen_need = int(args.need)
ea.gen_range = int(args.range)
ea.gen()
elif args.grade_e and args.grade_a:
eag = grade.EA_grade()
result = eag.grade(args.grade_e, args.grade_a)
with open('grade.txt', 'w+') as f:
f.write(result)
else:
print("Please check.")
if __name__ == '__main__':
main()
The answer verification module (grade.py)
import re
from fractions import Fraction
class EA_grade():
def grade(self, e_file, a_file):
efile = open(e_file, "r")
afile = open(a_file, "r")
# 定义一个flag记录同行的练习和答案
wrong = []
correct = []
line_flag = 0
# 依次对两个文件里的练习和答案校对
for e, a in zip(efile.readlines(), afile.readlines()) :
line_flag += 1
a = a.split(':')[1]
a = a.strip()
if re.search(r"'", a):
a_r, a_f = a.split("'")
realanswer = str(Fraction(a_r) + Fraction(a_f))
else:
realanswer = str(Fraction(a))
e = e.split(': ')[1]
e = e.split('=')[0]
nlist = []
olist = []
flist = []
bracket = 0
bracket_after = -1
pattern = re.compile(r"\d+'\d+/\d+|\d+/\d+|\d+")
nlist = re.findall(pattern, e)
for i in e:
if re.match(r'\+|-|x|÷', i):
if i == '÷': i = '/' # 除号转换
if i == 'x': i = '*' # 乘号转换
olist.append(i)
elif re.match(r'\(', i):
bracket_before = e.index(i)
bracket_after = e.find(")", -2, -1)
if bracket_before == 0 and bracket_after == -1: bracket = 1
if bracket_before != 0 : bracket = 2
if bracket_before != 0 and bracket_after == (len(e)-2) and len(nlist)==4 : bracket = 3
if bracket_before == 0 and bracket_after == (len(e)-2) and len(nlist)==4: bracket = 4
else:
pass
for j in nlist:
if re.search(r"'", j):
f1, f2 = j.split("'")
fraction = Fraction(f1) + Fraction(f2)
flist.append(fraction)
else:
flist.append(Fraction(j))
cal_answer = None
# 分析得出四种情况后,计算答案与answer.txt里的答案校对
try:
if bracket == 0:
if len(olist) == 1:
cal_answer = eval("flist[0] %s flist[1]" % (olist[0]))
if len(olist) == 2:
cal_answer = eval("flist[0] %s flist[1] %s flist[2]" % (olist[0], olist[1]))
if len(olist) == 3:
cal_answer = eval('flist[0] %s flist[1] %s flist[2] %s flist[3]' % (olist[0], olist[1], olist[2]))
if bracket == 1:
if len(olist) == 2:
cal_answer = eval("(flist[0] %s flist[1]) %s flist[2]" % (olist[0], olist[1]))
if len(olist) == 3:
cal_answer = eval('(flist[0] %s flist[1]) %s flist[2] %s flist[3]' % (olist[0], olist[1], olist[2]))
if bracket == 2:
if len(olist) == 2:
cal_answer = eval("flist[0] %s (flist[1] %s flist[2])" % (olist[0], olist[1]))
if len(olist) == 3:
cal_answer = eval('flist[0] %s (flist[1] %s flist[2]) %s flist[3]' % (olist[0], olist[1], olist[2]))
if bracket == 3:
cal_answer = eval('flist[0] %s flist[1] %s (flist[2] %s flist[3])' % (olist[0], olist[1], olist[2]))
if bracket == 4:
cal_answer = eval('(flist[0] %s flist[1]) %s (flist[2] %s flist[3])' % (olist[0], olist[1], olist[2]))
except Exception as exception:
wrong.append(str(line_flag))
continue
if Fraction(realanswer) - Fraction(cal_answer) < 1:
correct.append(str(line_flag))
else:
# print(line_flag, (realanswer),Fraction(cal_answer), e, bracket, len(nlist), bracket_before, bracket_after)
wrong.append(str(line_flag))
# 处理结果,返回输出
correct_result = "Correct:" + str(len(correct)) + " " + "(" + ",".join(correct) + ")\n"
wrong_result = "Wrong:" + str(len(wrong)) + " " + "(" + ",".join(wrong) + ")"
return correct_result + wrong_result
Code coverage
- Get the code coverage with coverage
Module | statements | missing | coverage |
---|---|---|---|
Total | 290 | 54 | 91% |
Main.py | 52 | 39 | 25% |
check.py | 55 | 42 | 24% |
grade.py | 77 | 4 | 95% |
test
unit test
Test Details (manual test)
Test function -r -n
- And generating a topic within a case of the digital operation:
Main.py -r 1 -n 1 #在中断输入
Topic files:
The answer file:
- Ten thousand topics generate situations:
题目文件:
答案文件:
-E -a test function
Modify the answer file following three places, about to answer the three places Correction
The answer error rate testing with -e -a function
Test Results:
Regression Testing
Regression testing refers to the old code modified, re-tested to confirm the modification does not introduce new bugs or cause other code to generate a test method wrong.
The center of gravity regression testing, is a key module as the core.
This small-scale project, limited functionality, the module does not change the key, you can recall the observation unit tests for regression testing.
Effectiveness Analysis
- The use of Python tools for analysis of CProfile
- ncalls:表示函数调用的次数;
tottime:表示指定函数的总的运行时间,除掉函数中调用子函数的运行时间;
percall:(第一个percall)等于 tottime/ncalls;
cumtime:表示该函数及其所有子函数的调用运行的时间,即函数开始调用到返回的时间;
percall:(第二个percall)即函数运行一次的平均时间,等于 cumtime/ncalls;
filename:lineno(function):每个函数调用的具体信息;
项目总结与收获
本次项目由Python编写,工具、模块主要有用到:re正则表达式模块、OptionParser参数解析、unittest用于单元测试、Cprofile用于效能分析等。整体的项目分工并不难,将整个项目分成表达式生成模块、答案生成模块和答案校验模块,其中表达式生成模块中难点需要注意判断生成的表达式是否有相同并将其排除;正因为题目中有了这个不能出现重复题目的要求,为了去重而设计出的算法占了很大的时间复杂度,直接影响了题目生成的效率,这是本项目做的不足的地方,希望后期可以设计出时间复杂度更低的算法以优化生成大量题目的速度。
由于本次项目是第一次结对项目,对队友的擅长领域和代码风格都不太了解,中途遇到过不少难题,但经过一段时间的磨合便很快适应了彼此的风格与节奏。因此,整个结对项目下来,学到了团队之间的磨合与沟通很重要,它直接影响了项目的进度。通过及时有效地沟通可以改进项目的瓶颈。