基於Python實現的四則運算生成程序
Github項目地址:傳送門
小組成員:黃曉彬(代碼實現) 黃鈺城(代碼審查)
需求:
1. 使用 -n 參數控制生成題目的個數。
2. 使用 -r 參數控制題目中數值(自然數、真分數和真分數分母)的範圍。
3. 生成的題目中計算過程不能產生負數,也就是說算術表達式中如果存在形如e1 ? e2的子表達式,那麽e1 ≥ e2。
4. 生成的題目中如果存在形如e1 ÷ e2的子表達式,那麽其結果應是真分數。
5. 每道題目中出現的運算符個數不超過3個。
6. 程序一次運行生成的題目不能重復,
生成的題目存入執行程序的當前目錄下的Exercises.txt文件。
7. 在生成題目的同時,計算出所有題目的答案,並存入執行程序的當前目錄下的Answers.txt文件。
8. 程序應能支持一萬道題目的生成。
9. 程序支持對給定的題目文件和答案文件,判定答案中的對錯並進行數量統計,輸入參數如下:
Myapp.exe -e <exercisefile>.txt -a <answerfile>.txt
統計結果輸出到文件Grade.txt,格式如下:
Correct: 5 (1, 3, 5, 7, 9)
Wrong: 5 (2, 4, 6, 8, 10)
其中“:”後面的數字5表示對/錯的題目的數量,括號內的是對/錯題目的編號。為簡單起見,假設輸入的題目都是按照順序編號的符合規範的題目。(未實現)
PSP表格:
PSP2.1 |
Personal Software Process Stages |
預估耗時(分鐘) |
實際耗時(分鐘) |
Planning |
計劃 |
30 |
30 |
· Estimate |
· 估計這個任務需要多少時間 |
30 |
60 |
Development |
開發 |
480 |
520 |
· Analysis |
· 需求分析 (包括學習新技術) |
120 |
200 |
· Design Spec |
· 生成設計文檔 |
60 |
70 |
· Design Review |
· 設計復審 (和同事審核設計文檔) |
30 |
60 |
· Coding Standard |
· 代碼規範 (為目前的開發制定合適的規範) |
30 |
30 |
· Design |
· 具體設計 |
60 |
60 |
· Coding |
· 具體編碼 |
600 |
700 |
· Code Review |
· 代碼復審 |
500 |
700 |
· Test |
· 測試(自我測試,修改代碼,提交修改) |
200 |
300 |
Reporting |
報告 |
60 |
30 |
· Test Report |
· 測試報告 |
30 |
30 |
· Size Measurement |
· 計算工作量 |
60 |
30 |
· Postmortem & Process Improvement Plan |
· 事後總結, 並提出過程改進計劃 |
30 |
40 |
合計 |
|
2230 |
2860 |
效能分析:算法運行過程較慢,沒有使用數據結構進行優化,並且還大量使用庫函數,導致結果運行緩慢
設計實現過程:接收命令行參數-->獲取數值和符號列表-->計算結果並生成題目和答案列表-->存入兩個txt文件
函數 | 功能 |
get_num_sym(i,r) | 獲取數值列表和符號列表 |
calculate(a,b,s) | 計算單元,a,b是數,s是符號 |
f(f) | 分數的轉換 |
writeF(slist,num,hb) | 生成算術表達式 |
getF(n,r) | 用於生成題目和答案列表 |
save(fname, d) | 用於寫入文件 |
main() | 主函數 |
代碼說明:
用get_num_sym(i,r)函數循環遍歷以獲得隨機分數列表和符號列表
def get_num_sym(i,r):#獲取數值列表和符號列表 nlist=[]#數值列表 slist=[]#符號列表 hb=0#判斷怎麽加括號 l=0#判斷是否是減數運算 for m in range(i+1):#根據i的值遍歷輸出數值列表 nlist.append(Fraction(random.randint(1, r), random.randint(1, r))) for x in range(i): sy=random.choice([‘+‘,‘-‘,‘ב,‘÷‘]) if sy==‘+‘or sy==‘-‘: hb +=10**(i-x-1) else : hb += 2 * (10 ** (i - x - 1)) slist.append(sy) if sy==‘-‘: l=1 return nlist,slist,hb,i,l
把分數轉換為整數、真分數和帶分數
def f(f):#分數的轉換 a=f.numerator b=f.denominator if a%b==0:#為整數 return ‘%d‘%(a/b) elif a<b:#為真分數 return ‘%d%s%d‘ % (a,‘/‘,b) else:#為帶分數 c=int(a/b) a = a - c * b return ‘%d%s%d%s%d‘ % (c,‘’‘,a,‘/‘,b)
用於四則運算
def calculate(a,b,s):#計算單元,a,b是數,s是符號 ans=0 if s==‘+‘:#加法運算 ans=a+b elif s==‘-‘:#減法運算 a,b=max(a,b),min(a,b)#防止結果為負數 ans=a-b elif s==‘ב:#乘法運算 ans=a*b else:ans=a/b#除法運算 return ans
生成算術表達式
def writeF(slist,num,hb):#生成算術表達式 global j,k s=‘‘ if hb>100:#符號數為3 if j==1 and k==0: s = ‘%s %s (%s %s %s) %s %s = ‘ % (f(num[0]), slist[0], f(num[1]),slist[1], f(num[2]), slist[2], f(num[3])) elif j==1 and k==1: s = ‘%s %s (%s %s (%s %s %s)) = ‘ % (f(num[0]), slist[0], f(num[1]),slist[1], f(num[2]), slist[2], f(num[3])) elif j==0 and k==1: s = ‘%s %s (%s %s %s %s %s) = ‘ % (f(num[0]), slist[0], f(num[1]),slist[1], f(num[2]), slist[2], f(num[3])) if hb == 112 or hb ==212: s = ‘(%s %s %s %s %s) %s %s = ‘ % (f(num[0]), slist[0], f(num[1]),slist[1], f(num[2]), slist[2], f(num[3])) elif hb == 121 or hb ==122: s = ‘(%s %s %s) %s %s %s %s = ‘ % (f(num[0]), slist[0], f(num[1]),slist[1], f(num[2]), slist[2], f(num[3])) else: s = ‘%s %s %s %s %s %s %s = ‘ % (f(num[0]), slist[0], f(num[1]),slist[1], f(num[2]), slist[2], f(num[3])) elif hb>10:#符號數為2 if j==1 : s = ‘%s %s (%s %s %s) = ‘ % (f(num[0]), slist[0], f(num[1]), slist[1], f(num[2])) if hb == 12: s = ‘(%s %s %s)%s %s = ‘ % (f(num[0]), slist[0], f(num[1]), slist[1], f(num[2])) else: s = ‘%s %s %s %s %s = ‘ % (f(num[0]), slist[0], f(num[1]), slist[1], f(num[2])) else :#符號數為1 s =‘%s %s %s = ‘ % (f(num[0]),slist[0],f(num[1])) return s
生成題目和答案列表
def getF(n,r):#用於生成題目和答案列表 E,A,E1,E2=[],[],[],[] global j,k x=1 while x<n+1:#循環生成題目和答案列表 i=random.randint(1, 3)#隨機獲取符號數目 num,slist,hb,i,l=get_num_sym(i,r) num1=num legal = True if l==1: #用於防止除法運算出現負數 if num[0]<num[1]: num1[0],num1[1]=num[1],num[0] if i>=2 and calculate(num[0],num[1],slist[0])<num[2]: num1[0],num1[1],num1[2]=num[2],num[0],num[1] j=1 if i>=3 and calculate(calculate(num[0],num[1],slist[0]),num[2],slist[1])<num[3]: num1[0],num1[1],num1[2],num1[3]=num[3],num[0],num[1],num[2] k=1 ans=num1[0] for y in range(i): cal=calculate(ans,num[y+1],slist[y]) if cal>=0:#判斷算式是否合法 ans=cal else: legal=False break if legal:#判斷是否重復題目 try: num=A.index(ans)#第一個重復答案的索引 if operator.eq(E1[num],slist) and operator.eq(E2[num],num): pass except ValueError as e:#可以寫入 A.append(ans) E1.append(slist) E2.append(num1) E.append(‘%d. %s‘%(x,writeF(slist,num1,hb))) x+=1 else:pass return E,A
寫入txt文件
def save(fname, d):#fname為寫入文件的路徑,d為要寫入的數據列表. file = open(fname,‘a‘) file.seek(0) file.truncate() #清空 for i in range(len(d)):#循環寫入文件fname s = str(d[i]).replace(‘[‘,‘‘).replace(‘]‘,‘‘) s = s.replace("‘",‘‘).replace(‘,‘,‘‘) +‘\n‘ file.write(s) file.close() print(‘%s文件保存成功‘%fname)
主函數(包括命令行參數的實現)
def main():#主函數 parser = argparse.ArgumentParser(description="this is auto calculator")#命令行參數控制 parser.add_argument(‘-n‘,help=‘控制生成題目的個數‘,type=int) parser.add_argument(‘-r‘,help=‘控制題目中數值(自然數、真分數和真分數分母)的範圍‘,type=int) args = parser.parse_args() if args.n: n=args.n print(‘n值為%d‘%n) if args.r: r=args.r print(‘r值為%d‘%r) E, A=getF(n,r) for x in range(n):#循環生成答案列表 A[x]=‘%d. %s‘%(x+1,f(A[x])) save(‘Exercises.txt‘,E) save(‘Answers.txt‘,A) end = time.clock() print(‘運行時間: %s ‘%(end-start)) if __name__ == ‘__main__‘: main()
測試運行:
一萬道題目生成:
項目小結:由於我們兩個Python水平有限,花了幾天都想不出到底要怎麽寫這個程序,最後還是問了同學加上網看別人寫的思路,通過先全部隨機生成分數(分數包括了自然數),在用函數區分自然數、真分數和帶分數,再用函數根據條件的不同生成不同的表達式,並通過單步循環計算出結果,最後再根據情況加括號(其實是前面忘了,後面為了這個花了很長時間),才完成了這個自動生成程序。通過這次編程,我深刻感受到自己寫Python的技術有待提高,代碼讓鈺誠看得一頭包,bug更是跟修不完似的。兩個人的編程和一個人的還是有很大的不同,意見的不統一,想法的不同都會讓我們產生分歧甚至是爭執,這種時候良好的溝通才是最重要的。
基於Python實現的四則運算生成程序