结对作业 张旗 王思豪

结对作业

姓名:张 旗 学号:3121005322
姓名:王思豪 学号:3121005313
github:https://github.com/97ZQ/97ZQ/tree/main/Two-person work

这个作业属于哪个课程 软件工程
这个作业要求在哪里
班级首页-作业列表-详情
这个作业的目标 熟悉github以及熟悉与他人合作完成项目以及博客,使用PSP表格记录自己在程序开发上的时间

实现程序前前的PSP表格

PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
Planning 计划 120
· Estimate · 估计这个任务需要多少时间 120
Development 开发 930
· Analysis · 需求分析 (包括学习新技术) 20
· Design Spec · 生成设计文档 20
· Design Review · 设计复审 10
· Coding Standard · 代码规范 (为目前的开发制定合适的规范) 10
· Design · 具体设计 30
· Coding · 具体编码 600
· Code Review · 代码复审 60
· Test · 测试(自我测试,修改代码,提交修改) 120
Reporting 报告 60
· Test Repor · 测试报告 25
· Size Measurement · 计算工作量 20
· Postmortem & Process Improvement Plan · 事后总结, 并提出过程改进计划 15
----------------- · 合计 1170

效能分析:

再性能改进上花了150分钟左右,在最开始只是很简单的完成了这个项目,当时大致的需求是达到了,但是当时只是生成自然数的加减乘除,而且乘除号都用了*和/,然后也没有判断生成的表达式是否跟之前生成的等价,在我们完成这个粗略的程序之后,我们才一点一点完善了这些性能。
然后再文件中的for循环while循环之类的,我们使用了continue来终止,使得在10000个算数表达式生成上更加省时。
然后有些地方也调用了python自带的库,这样也会使得程序运行的更快
性能分析图:

消耗最大的函数:

设计实现过程:

首先我们的代码存在于两个文件,一个文件为Main.py,代码的入口在这里面,还有就是Method.py,在其中,有很多功能函数,其用法如下所示:

其关系图如下:

其中还有一些重要的代码没有封装成方法。

对代码的测试:


覆盖率高达97%,则证明代码运行良好!

代码说明:

此处的说明多为介绍自己如何解决需求的关键代码

需求1、2:

首先程序使用了python的argparse库,该库的作用是设置命令行的参数parse,参考文档argparse教程,.通过对argparse.ArgumentPaser的方法,设置命令行的参数,以及添加提醒,通过下面的语句,使得,只能按照需求所示的输入,否则会抛出异常raise Exception在异常中会告诉你怎么正确使用:
大致代码如下:

parser = argparse.ArgumentParser()
group = parser.add_argument('-n','--numberNum',type=int,help='add your question numbers')
group = parser.add_argument('-r','--rangeNum',type=int,help='add your question range')
group = parser.add_argument('-e','--exerciseFile',help='read your question file')
group = parser.add_argument('-a','--answerFile',help='read your question answer file')
args = parser.parse_args()
print(type(args))
#处理异常,规定命令行的使得程序要么接受 -r -n 要么接受 -e -a
if (args.numberNum is None and args.rangeNum is None and args.exerciseFile is not None and args.answerFile is not None) \
    or (args.numberNum is not None and args.rangeNum is not None and args.exerciseFile is None and args.answerFile is None):
    #当输入的是-n -r 时
    if args.numberNum is not None:
        #使用户输入正确的命令行参数
        if args.numberNum <0 or args.rangeNum <0 :
            raise Exception('Please make your numberNum and rangeNum >=0')
        #在处理完异常之后,就进行第一个需求,生成相应数目的题目以及各自的答案,并将他们按照相应的形式存入文件中
        generateQuestionAndAnswer(args.rangeNum, args.numberNum)
    #当输入的是-e -a时
    if args.exerciseFile is not None:
        newExerciseFile = str(args.exerciseFile)
        newAnswerFile = str(args.answerFile)
        #查看所输入的文件是否存在,不存在的话抛出异常
        if not os.path.exists(newExerciseFile):
            raise (f'Not Found"{newExerciseFile}" File')
        if not os.path.exists(newAnswerFile):
            raise (f'Not Found"{newAnswerFile}" File')
        #基于所给定的文件newExerciseFile和newAnswerFile来给出计算的对错
        generateGrade(newExerciseFile,newAnswerFile)
#
# 如果输入的命令不是-r -n 或者 -a -e则抛出异常
else:
    raise Exception('Please input --help or your command with(python Main.py -n [int] -r[int]) or python Main.py (-e [str] -a [str])')

在该代码中,如果不是按照要求输入,他就会抛出异常,然后结束程序
执行结果如下:

需求3:

本人使用循环随机生成算数表达式,在生成算数表达式后,我会使用eval计算表达式的值,并且要求,如果这个值大于了所要求的range范围或者小于0,我会终止这个表达式(用continue结束本次循环)重新生成一个表达式,直到值满足要求
代码如下:

           try:

               answer = Fraction(eval(temp)).limit_denominator()
           except ZeroDivisionError:
               continue
           #如果答案的结果大于前面命令行给定的范围,或者使负数,那么该情况不合理,也将社区
           if answer < 0 or answer > r or answer.denominator > 10:
               continue

需求4:

为了保证输出的结果时真分数,我们使用了Fraction().limit_denominator()将结果1近似为最接近的具有指定分母的分数。然后至于当分子大于分母的时候,我们会对结果进行处理,此部分代码如下:
answers为存所有生成表达式答案的集合,将answers中所有元素都要进行标准化,然后将真分数存于answerNew列表中

answersNew = []
    for i in range(len(answers)):
        #通过循环将答案列表中的答案按照需求中给给的格式输出
        a = Fraction(eval(answers[i])).limit_denominator()
        if a.denominator != 1 and a.numerator > a.denominator:
            s = str(i+1) + ". " + str(a.numerator // a.denominator) + "'" + str(a - a.numerator // a.denominator)+'\n'
            answersNew.append(s)
        else:
            s = str(i+1) + ". " + str(a) + '\n'
            answersNew.append(s)

需求5:

只有'+',‘-’,‘*’,‘/’这四个算运算符,所以在随机生成表达式的时候,我们先随机生成运算符的个数,使其范围在1-3之间,然后再根据运算符生成数字1,例如1个运算符链接两个数字,两个运算符连接三个数字,三个运算符连接四个数字。这样就规定了运算符不超过三个。

operators=['+','-','×','÷']
    allOperators=['+','-','×','÷','(',')']
    #通过循环的方式随机生成算数表达式
    while len(answers) < n:
        #此处的意思为随机产生运算符的个数
        operatorNum = random.randint(1,3)
        #当为一个运算符的时候
        if operatorNum == 1:
            list = []
            num1 = genarateNumber(r)
            num2 = genarateNumber(r)
            operator = random.choice(operators)
        if operatorNum == 2:
            list =[]
            num1 = genarateNumber(r)
            num2 = genarateNumber(r)
            num3 = genarateNumber(r)
            operator1 = str(random.choice(operators))
            operator2 = str(random.choice(operators))
        if operatorNum == 3:
            list = []
            num1 = genarateNumber(r)
            num2 = genarateNumber(r)
            num3 = genarateNumber(r)
            num4 = genarateNumber(r)
            operator1 = str(random.choice(operators))
            operator2 = str(random.choice(operators))
            operator3 = str(random.choice(operators))

中间还有好多代码,但是只取了跟需求想关的代码

需求6:

6-1:

为了使程序一次运行生成的题目不能重复,我们通过了标准化算术表达式并且去掉括号,将他们排序。
重复条件:
1.他们的结果相等
2.他们通过标准化之后得到的结果排序后也相等
当满足以上两点之后就,我们就认为题目重复:
代码如下:

questionSwitch = False
        answerSwitch = False
        questions2Temp=[]
        #对questions的quetsions变成可以比较是否等价的1样子
        for i in range(len(questions2)):
            questions2Temp.append(''.join(sorted(questions2[i].replace('(','').replace(')','').replace(' ',''))))
        if answerStr in answers:
            answerSwitch = True
        if ''.join(sorted(question1.replace('(','').replace(')','').replace(' ',''))) in questions2Temp:
            questionSwitch = True
        if questionSwitch == True and answerSwitch == True:
                #print('作废',continueTime)
            continueTime +=1
            continue
            #如果生成的式子等价太多的话,就说明你生成的范围太小了,数目太大了
        if continueTime >=10000:
            raise Exception("you have input too large number and two small range")

如果生成的式子等价太多的话,就说明你生成的范围太小了,数目太大了,为了避免浪费时间,我们会统计重复的次数,如果次数大于10000,我们就会抛出异常,并且给出提醒

6-2:

为了将题目按需求所给存入文件'Exercises.txt'中,文件将之前处理标准的问题再进行格式化,使其满足需求所给的格式,代码如下:

questionsNew = []
for i in range(len(questions)):
        #按照需求,将/变为'÷',将*变成'×'
        dataNew = str(i + 1) + '. ' + questions[i] + '\n'
        dataNew = dataNew
        questionsNew.append(dataNew)
fp1 = open('Exercises.txt','w')
fp1.writelines(questionsNew)
fp1.close()

真分数输入输出的格式:
以三个运算符的为例子

            list.append(num1)
            list.append(operator1)
            list.append(num2)
            list.append(operator2)
            list.append(num3)
            list.append(operator3)
            list.append(num4)
            # 为列表添加括号
            list = addBracket(operatorNum, list)
            listNew = standardFraction(list,allOperators)
            # 为算数表达式添加空格

            question1 = addBlank(list)
            question2 = addBlank(listNew)

其中addBracket,stardFraction以及addBlank的作用已经在注释中解释过了,代码在github上,这里就不给写出了

需求7:

将答案存入‘Answers.txt’中的代码(由于跟之前的操作都大同小异,这里就不多解释了,直接上代码):

    answersNew = []
    for i in range(len(answers)):
        #通过循环将答案列表中的答案按照需求中给给的格式输出
        a = Fraction(eval(answers[i])).limit_denominator()
        if a.denominator != 1 and a.numerator > a.denominator:
            s = str(i+1) + ". " + str(a.numerator // a.denominator) + "'" + str(a - a.numerator // a.denominator)+'\n'
            answersNew.append(s)
        else:
            s = str(i+1) + ". " + str(a) + '\n'
            answersNew.append(s)
    fp2 = open('Answers.txt','w')
    fp2.writelines(answersNew)
    fp2.close()

需求8:

支持10000个题目的生成,由于怕重复太多,这里就用range =1000 了,结果如图:

需求9:

为了能够比较哪个对错,所以我将exercisefile与answerfile都都出来了,然后分别建立列表存放他们,除此之外,再多建一个列表,用来存放exercise的正确答案,代码如下(这个代码超长,我就用了可折叠的代码快,喜欢看就看!):

点击查看代码
def generateGrade(newExerciseFile,newAnswerFile):
    questions = []
    with open(newExerciseFile,'r') as file:
        #将问题文件读到列表中
        lines= file.readlines()
        #将列表中固定格式的算术表达式取出来,并变成标准的算术表达式格式
        for line in lines:
            line = line.replace("\n","")
            line = line.replace("÷", "/")
            line = line.replace("×", "*")
            line = line.split('. ')[1]
            questions.append(line)
    questionsNew = []
    for question in questions:
        tempsNew = []
        temps = question.split(' ')
        for i in range(len(temps)):

            if "'" in temps[i]:
                temp = Fraction(eval(temps[i].split("'")[1])).limit_denominator()
                temp = (temp + eval(temps[i].split("'")[0]))
                tempsNew.append(temp)
                #print(temp)
            else:
                tempsNew .append(temps[i])
        #print(temps)
       # print(tempsNew)
        questionsNew.append(str(addBlank(tempsNew)))
    #print(questionsNew)
    questions = questionsNew
    answersTrue = []

    #通过循环找出每个问题真的答案存入到answerTrue中
    for question in questions:
        answer = Fraction(eval(question)).limit_denominator()
        if answer.denominator != 1 and answer.numerator > answer.denominator:
            s = str(answer.numerator // answer.denominator) + "'" + str(answer - answer.numerator // answer.denominator)
            answersTrue.append(s)
        else:
            s= str(answer)
            answersTrue.append(s)
        #print(s,type(s))

    with open(newAnswerFile,'r') as file:
        answers=[]
        lines = file.readlines()
        #将答案文件的答案变成标准形式
        for line in lines:
            line = line.replace('\n','')
            line = line.split('. ')[1]
            answers.append(line)
            #print(line,type(line))
    #定义两个列表,存入正确的答案和错误的答案
    questionCorrect =[]
    questionWrong =[]
    #比较文件中答案和算出的正确对答案,相同则将题号存入questionCorrect,不同则存入questionWrong
    for i in range(len(questions)):
        if answersTrue[i] == answers[i]:
            questionCorrect.append((i+1))
        else:
            questionWrong.append((i+1))
    #print(questionWrong)
    #print(questionCorrect)
    #将信息转换成需求中的那种形式
    strCorrect = 'Correct: '+str(len(questionCorrect)) +' ('
    if len(questionCorrect) ==0 :
        strCorrect +=')'
    for i in range(len(questionCorrect)):
        if i == len(questionCorrect) - 1:
            strCorrect+= str(questionCorrect[i])+')'
        else:
            strCorrect+=str(questionCorrect[i])+', '

    strWrong = 'Wrong: ' + str(len(questionWrong)) + ' ('
    if len(questionWrong) == 0:
        strWrong+=')'
    for i in range(len(questionWrong)):
        if i == len(questionWrong) - 1:
            strWrong += str(questionWrong[i]) + ')'
        else:
            strWrong += str(questionWrong[i]) + ', '

    print(strCorrect)
    print(strWrong)
    with open ('Grade.txt','w') as file:
        strCorrect +='\n'
        file.writelines(strCorrect)
        file.writelines(strWrong)

所比较文件内容:

运行指令:

运行结果:

由于博主有一个朋友外号叫2,所以我将有关2的项都改成错的了

实现程序后的PSP表格:

PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
Planning 计划 120 40
· Estimate · 估计这个任务需要多少时间 120 40
Development 开发 930 1120
· Analysis · 需求分析 (包括学习新技术) 20 30
· Design Spec · 生成设计文档 20 30
· Design Review · 设计复审 10 10
· Coding Standard · 代码规范 (为目前的开发制定合适的规范) 10 5
· Design · 具体设计 30 15
· Coding · 具体编码 600 900
· Code Review · 代码复审 60 30
· Test · 测试(自我测试,修改代码,提交修改) 120 100
Reporting 报告 60 90
· Test Repor · 测试报告 25 30
· Size Measurement · 计算工作量 20 30
· Postmortem & Process Improvement Plan · 事后总结, 并提出过程改进计划 15 30
----------------- · 合计 1170 1250

小结:

在共同合作解决编程项目的过程中,我们充分体验到团队协作的重要性以及如何高效地充分利用各自的专长和优势。通过密切合作,我们成功地完成了项目,实现了既定的需求和目标。
首先,我们进行了充分的需求分析,明确了项目的要求和功能。在明确任务的基础上,我们进行了合理的分工,根据个人的技能和兴趣分配了任务。这确保了每个人都可以专注于自己的领域,提高了工作效率。
在整个项目的开发过程中,我们保持了密切的沟通。无论是面对面交流还是线上沟通,我们随时分享项目进展、遇到的问题以及解决方案。及时的反馈和讨论帮助我们快速解决了一些技术上的疑难问题。
我们也采用了从基层开始,先做大概的模板,不断去添加方法,逐步完善项目。这样就使一个人需要用到什么方法后,另一个迅速去完成。
在项目接近完成时,我们一起审查了代码,并做了一些优化。这些优化不仅包括性能上的改进,还有代码的清理、注释的补充以及格式的统一,以确保代码具有良好的可读性和可维护性。
总的来说,这个项目锻炼了我们的团队协作能力、沟通能力以及问题解决能力。我们通过相互合作、共同努力,成功地完成了这个项目,取得了令人满意的成果。在今后的合作中,我们会继续借鉴这次合作的经验,更好地发挥合作编程的优势,实现更多高质量的项目。

posted @ 2023-09-29 01:32  lspzq  阅读(54)  评论(0)    收藏  举报