Pair programming implemented -python

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

  1. -N generated using the number of control parameters subject, e.g.

    Myapp.exe -n 10

    The resulting 10 topics.

  2. -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.

  3. Title generated calculations can not produce negative, that is if there is an arithmetic expression in the form subexpression e1- e2, then e1≥ e2.

  4. If present in the task generating substrings of the form of expressions e1 ÷ e2, the result should be a proper fraction .

  5. The number of operators each question appear in no more than 3.

  6. 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.

  7. Current Exercises.txt files in the directory generated task execution stored procedures in the following format:

    1. Title four operations 1

    2. 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.

  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:

    1. Answer 1
    2. Answer 2

    In particular, the following example calculation proper fractions: 1/6 + 1/8 = 7/24.

  9. Program should be able to support the generation of ten thousand topics.

  10. 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
  • 2. marking topic, proofreading answer

    • Deal with topics
      • Extraction operands
        • Proper fraction conversion
      • Extraction Operator
      • The order of operation is determined bracket
      • Calculation results
    • 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

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用于效能分析等。整体的项目分工并不难,将整个项目分成表达式生成模块、答案生成模块和答案校验模块,其中表达式生成模块中难点需要注意判断生成的表达式是否有相同并将其排除;正因为题目中有了这个不能出现重复题目的要求,为了去重而设计出的算法占了很大的时间复杂度,直接影响了题目生成的效率,这是本项目做的不足的地方,希望后期可以设计出时间复杂度更低的算法以优化生成大量题目的速度。

​ 由于本次项目是第一次结对项目,对队友的擅长领域和代码风格都不太了解,中途遇到过不少难题,但经过一段时间的磨合便很快适应了彼此的风格与节奏。因此,整个结对项目下来,学到了团队之间的磨合与沟通很重要,它直接影响了项目的进度。通过及时有效地沟通可以改进项目的瓶颈。

Guess you like

Origin www.cnblogs.com/tanwanchuan/p/11682054.html