2017年秋季学期软件工程第二次作业

源代码链接:https://github.com/superwales/Random_test/blob/master/random_test1.0.py

一,psp表格

PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
Planning 计划 30 60
Estimate 估计这个任务需要多少时间 860  
Development 开发 540 690
Analysis 需求分析 课程项目,10 暂无
Design Spec 生成设计文档 60  暂无
Design Review 设计复审 60 暂无
Coding Standard 代码规范 30 30
Design 具体设计 60 90
Coding 具体编码 120 300
Code Review 代码复审 个人项目,自我测试,300  
Test 测试(自我测试,修改代码,提交修改) 300 300
Reporting 报告 60 60
Test Report 测试报告 60  
Size Measurement 计算工作量 10 10
Postmortem&Process Improvement Plan 事后总结,并提出过程改进计划 60 60
合计   860 910

二,解题思路描述

本次个人项目共有四个基本要求:

  1,操作数随机生成,包含整数和真分数

  2,运算符的种类的顺序随机生成

  3,能判断对错并计分

  4,生成的题目数量可以控制

以及四个附加要求:

  1,运算符的个数随机生成

  2,根据题目难度分配分值

  3,根据答题时间调整分数

  4,防止题目重复

首先考虑基本要求:根据用户输入的题目数量,随机生成字符串,打印出来就能得到一个题目,由于要与用户输入的答案比较,所以应当知道这个字符串的值是多少。而题目是操作数与操作符交错的,只要确定了操作符,然后插入随机的操作数就能得到一个题目。

考虑附加要求1:把运算符存在一个列表中,然后随机生成序号,取出对应的运算符就好

考虑附加要求2:用题目的长短(操作符的个数)和乘除法占的多少来衡量一个题目难易与否,把难度量化,根据难度分配分值,满分默认为100分

考虑附件要求3:首先根据题目多少计算答题时间,然后有两个思路,一是超时就停止答题,二是在规定时间答完正常打分,超时扣分

考虑附加要求4:把所有已生成的题目都存起来,每得到一个新题目,就到题库里去比,如果重复就重新生成一个新题目。

在开始本次项目之前,我只有一定的C和C++基础。在和同学沟通讨论后,我决定自学python,并用python完成本次作业。首先通读了《简明python教程》(Swaroop, C. H . 著沈洁元 译),明确了python的相关编程规范之后,开始了代码的编写。

三,设计实现过程

基本功能点:

用户交互得到题目数量与运算范围:首先由用户决定需要多少题以及操作符的运算范围是多大,使程序的适用性更强

随机生成操作符:把所有的操作符都存在一个列表中,例如【‘+’,‘-’,‘*’,‘/’】,然后随机从中抽取。后来考虑到小学运算的难度,我调整了加减号和乘除号的比例,为2:1。即建立一个【‘+’,‘-’,‘+’,‘-’,‘*’,‘/’】列表,然后从中随机抽取操作符

随机生成操作数:首先获取一个在(1,2)中的随机整数,如果为1,则返回一个整数,如果为2,则返回一个真分数,保证了操作数的随机性。与调整操作符种类比例类似,可以调整操作数的范围来改变返回整数与真分数的比值。返回整数直接在操作数运算范围中随机得到一个就行,如果是返回真分数,可以获取两个随机数。python有一个Fraction类可以进行分数的数值运算和自动约分。

随机得到题目(包含运算符个数随机生成):首先在(1~10)中随机获取操作符的个数,这也确定了题目的长度。然后调用已经得到的随机获取操作符和随机获取操作数的函数,交错得到一道题目。

把题目写入txt文件中:每得到一道新题就写一道新题,便于后续查看与打印。当前只能写题,后续将开发把题目,参考答案和回答答案、每题的对错和对应的分数全部写入,即打印成绩单。

计算题目的答案:这部分困扰我最久。我把一道题目存在一个列表中,列表中的元素是操作数或者操作符。比如:

【‘5’,‘+’,‘1/2’,‘*’,‘4’,‘-’,‘2’】

在没有括号的情况下,乘除运算先于加减。于是我从左到右遍历这个列表,如果有乘除号,那么使用乘除号和前后的两个操作数进行一次乘除运算,把结果赋给前一个操作数,并删除操作符和后一个操作数:

【‘5’,‘+’,‘2’,‘-’,‘2’】

做完所有的乘除法后,就用前三个元素做加减运算,同样把值赋给前一个操作数,并删除操作符和下一个操作数:

【‘7’,‘-’,‘2’】

直到列表只剩最后一个元素:

【‘5’】

这个数就是表达式的答案。

但是我这种有一个很明显的缺点,就是无法处理包含括号的字符串运算。

题目判重:用一个列表存题,每生成一道题目就去题库中比较一次,如果不一样就添加到题库中,如果一样就重新随机生成。

根据用户输入判断答题对错:到这里已经得到了题目和答案,分别用两个列表存储。如果用户的输入与答案相符,输出“回答正确!”,反之输出“回答错误!”

根据题目难易分配分值:首先是量化难易度。回溯到随机操作符和操作数的时候,如果随机得到了一个加减号,则认为难度+1,乘除号,则难度+2;如果随机得到一个整数,则难度+1,真分数,则难度+1.5。在随机生成题目的时候,设置初始难度为0,每得到一个操作数和一个操作符就把难度累加起来,得到一个难度的判据。把难度也可以存在一个列表中。所有的题目可以累加得到一个总难度,根据每一题的难度占总难度的比值就可以换算为百分制的分数。每做对一题,就把获得的分数加起来,得到一个初始分数。当前代码使用整数表示分数,当题目过多时,可能出现每一题都是零分的情况,后续应当修改为浮点数。

根据答题时间调整分数:根据题目难度的不同应当分配不同的时间,我设置了一个系数k表示题目难度与时间(s)的换算比例,默认是1。意思是,当题目总难度为100时,则默认100s内结束答题为正常。如果超时,则使用

grade=(grade*time_limite)/time_use

来调整分数

四,代码说明

算法思路已经在第三部分讲解得比较详细了,这一部分主要用注释来说明。

1,随机生成操作符:

def getoperators():
    operatorslist=('+','-','*','/','+','-')#在列表中调整四种运算符出现的比值
    operators=random.choice(operatorslist)#从列表中随机选一个元素,保证了随机性
    if operators=='*':#乘除号难度赋值为2
        length=2
    elif operators=='/':
        length=2
    else:
    length=1#加减号难度赋值为1
    return operators,length#返回操作符与其难度

2,随机生成操作数:

def getoperands(range):
    operandstype=random.randint(1,2)#得到一个随机数,确定返回的数据类型
    degree=0#难度初始化为0
    if operandstype==1:#返回整数,有三个参数:字符串,用于打印与显示;数值,用于计算;难度,用于分配分数
        operands=random.randint(1,range)
        operandsvalue=Fraction(operands,1)#整数可以看做是一个分母为1的分数,统一为fraction格式可以方便后面的数值运算
        operands=str(operands)#字符串
        degree=1#整数难度为1
    else:#返回真分数
        operands1=random.randint(1,range)#得到两个随机数
        operands2=random.randint(1,range)
        degree=1.5#真分数难度为1.5
    if operands1<operands2:#调整大小,确保分子小于分母,为真分数
        operands1,operands2=operands2,operands1
    if operands1==operands2:#万一分子分母相等,赋值为1/2
        operandsvalue=Fraction(1,2)
        operands='(1/2)'
    else:
        operandsvalue=Fraction(operands2,operands1)
        operands=str(Fraction(operands2,operands1))
    return operands,operandsvalue,degree#有三个参数:字符串,用于打印与显示;数值,用于计算;难度,用于分配分数

3,随机得到题目:

symbolnumber=random.randint(1,5)#随机得到操作符的个数,题目要求是1~10,这里是1~5,原理一样
question=''#每一题用字符串存
questionstack=[]#用列表存下题目的每一个操作数和操作符,以用自定义的方法计算答案
ans=0#用于后文计算答案,初始化为0
length_ques=0#用于后文分配分值,初始化难度为0

for i in range(1,symbolnumber+1):#根据有多少个操作数,逐一得到操作数与操作符
    (op,va,de)=getoperands(ran)
    operands=op#传递字符串
    value=va#传递数值
    (operators,length)=getoperators()
    question=question+operands+operators
    questionstack.append(value)#生成列表
    questionstack.append(operators)
    length_ques=length_ques+length+de#难度累加
(op,va,de)=getoperands(ran)
operands=op
value=va
question=question+operands#得到了一道题目
questionstack.append(value)#得到了便于计算答案的题目的列表格式
length_ques=length_ques+de#难度累加

4,计算答案:

condition=0#条件,乘除法计算完毕时变为1
while len(questionstack)>1:#计算至最后一个元素
    for i in range(0,len(questionstack)):
        if questionstack[i]=='*':#先算乘除,
            questionstack[i-1]=questionstack[i-1]*questionstack[i+1]#一次运算并赋值
            del questionstack[i]#删除元素
            del questionstack[i]
            break
        elif questionstack[i]=='/':
            questionstack[i-1]=questionstack[i-1]/questionstack[i+1]
            del questionstack[i]
            del questionstack[i]
            break
        else:
            condition=1#表示乘除法已经计算完毕
    if condition==1:
            if len(questionstack)>1:
                questionstack[0]=calculate(questionstack[0],questionstack[2],questionstack[1])#始终在前三位进行运算,意思是从左到右执行加减法
                del questionstack[1]
                del questionstack[1]
else:
    ans=questionstack[0]#最后剩下的元素就是答案

5,题目查重:

questionfile=file('questionlist.txt','w')#准备把题目写入txt
#根据输入的题目个数生成题目清单,每一个新生成的题会与现有题进行比较,重复不则重新生成
#将生成的题目写入文件中,便于查看与打印
while len(questionlist)<questionnumber:#用户输入的题目数量
    (question,ans,length)=getquestion(ran)#随机得到一道题目
    cond=0
    for element in questionlist:#在现有列表中去比较
        if element==question:#如果题目已经存在,那么重新生成一道题
            cond=1
    if cond==0:#cond=0表示题目无重复
        questionlist.append(question)#题目用列表存
        anslist.append(ans)#答案用列表存
        lengthlist.append(length)#每一题的难度用列表存
        totalscore=totalscore+length#所有题的总难度,为后文分配分值准备
        questionfile.write(question+' ')
questionfile.close()
print '题目已经生成完毕!'

6,根据权重分配分值:

for i in range(0,len(lengthlist)):
    scorelist.append(round(lengthlist[i]*100/totalscore)) #totalscore是总难度,每一题的难度占比转换为百分制的分数

7,判断对错:

for i in range(0,questionnumber):#总共有questionnumber道题
    print'第',i+1,'题是:'
    print questionlist[i],'='
    print'分值为',scorelist[i],'分'
    print '答案是:',anslist[i]
    ans_user=raw_input('请输入你的答案:')
    print '您的答案是:',ans_user
    if ans_user==str(anslist[i]):
        print'回答正确!'
       grade=grade+scorelist[i]#答对的分值累加起来,得到初始成绩
    else:
        print'回答错误!'

8,根据时间调整分数:

time_start=time.time()#答题前开始计时
*************************
time_end=time.time()#答题结束停止计时
time_use=time_end-time_start#实际花费时间
#这里设置一个题目权重与时间花费的关系k,默认为1
k=1
if time_use<totalscore*k:#在规定时间内,分数不做调整
    print'答题时间符合要求!'
    grade=grade
else:
    print;'答题超时!'
    grade=round(totalscore*grade/time_use)#答题超时后,根据超出时间的多少调整分数
print '您本次的得分为:',grade,'分'

五,测试运行

展示两个运行结果

一:

本次运行时,首先输入了题目的个数和运算范围,然后开始答题。为了方便测试我在屏幕中输出了答案。每一题都标明了分值,在作答后直接判断对错。答题结束后显示时间符合要求,结果全对,成绩为100分。

二:

而在本次运行中,所有题目依然全部答对,但是超出了答题时间太多,最终得分只有51.0分。

同时,题目已经被写入到txt文件里:

六,项目小结

总的来说,本次项目的要求均已完成。由于本次项目不复杂,在编代码之前就能够很清晰的想清楚该做些什么。遇到的最大的困难是对python语言不熟悉,很多功能不清楚。

psp表格已经在博客的开头给出了,代码复审和单元测试还没有进行,后续会学习,尽快培养出良好的软件开发习惯。

目前的成果还存在很多漏洞或者说希望改进的地方:

1,分数计算有问题。由于在分配分值的时候进行了取整,引入了较大的误差。可能出现规定时间内每题都做对了,可是分数不是100分的情况。

2,根据时间调整分数的模型做的不够合理,在超出时间后,分数衰减得很快

3,生成的题目不包括括号

4,程序运行界面太不友好

等等等等。

因此我会继续改进我的代码。之前有一个想法是做一个可视化的界面出来,就类似于驾考宝典这种,一开始确定相关参数,然后显示出一道题目,回答之后直接判对错,点击下一题继续等等。但是在学习用python开发界面的时候遇到了一些问题,所以想法暂时搁置了。

在12月25日课上,我读一本python的教程,里面有一句话说“python处理100万字符的txt文档”,大概的意思就是这样。这句话突然启发了我,我为什么不能直接做一个题库,然后直接从题库里抽题呢?这样还可以实现和何老师交流时提到的“题目主观难度判断”,有关题库的相关尝试我将写在下一篇博客中。

原文地址:https://www.cnblogs.com/seven-v13/p/8119850.html