3、軟工結對項目:小學生快樂機(python實現)

李光證 3117004660html

吳子昊 3117004671python

李光證博文地址:https://www.cnblogs.com/Gleez/p/11688368.htmlgit


 

1、github

1. github項目地址:算法

https://github.com/amekao/SE_work2編程

2. 界面示例:數據結構

生成模式app

 

 

 

批改模式框架

 

 

2、PSP表格dom

PSP2.1

Personal Software Process Stages

預估耗時(分鐘)

實際耗時(分鐘)

Planning

計劃

 

 

· Estimate

· 估計這個任務須要多少時間

 1200

 1200

Development

開發

 

 

· Analysis

· 需求分析 (包括學習新技術)

 180

 200

· Design Spec

· 生成設計文檔

 20

 30

· Design Review

· 設計複審 (和同事審覈設計文檔)

 30

 60

· Coding Standard

· 代碼規範 (爲目前的開發制定合適的規範)

 30

 30

· Design

· 具體設計

 100

 100

· Coding

· 具體編碼

 600

 720

· Code Review

· 代碼複審

 200

 180

· Test

· 測試(自我測試,修改代碼,提交修改)

 100

 90

Reporting

報告

 

 

· Test Report

· 測試報告

 30

 40

· Size Measurement

· 計算工做量

 10

 10

· Postmortem & Process Improvement Plan

· 過後總結, 並提出過程改進計劃

 10

 10

合計

 

 1310

 1470

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

3、效能分析

使用line_profiler模塊對時間進行測試:

①從新寫了一個time.py進行測試,對裏面main函數修改,不使用argv來獲取參數,而是直接在程序裏面賦值

②在須要測試的模塊前加上@profiler標誌符

③使用 kernprof -l time.py運行程序,生成time.py.lprof報表

④使用 python -m line_profiler time.py.lprof 查看報表

 

生成模式:生成10000道範圍在0到10的題目

生成模式核心代碼:

    # 生成模式
    ques_count = 10000
    max_val = 10
    random_exercises(ques_count, max_val)

 

核心模塊random_exercises的用時:

Total time: 5.14441 s
File: time.py
Function: random_exercises at line 12

Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
    12                                           @profile
    13                                           def random_exercises(q_c, m_v):
    14                                               """
    15                                               根據輸入的題目數量和數字範圍,生成題目文件和答案文件
    16                                               :param q_c: 題目數量
    17                                               :param m_v: 數字最大值
    18                                               :return: 在目錄下生成試題文件和答案文件
    19                                               """
    20         1       1028.0   1028.0      0.0      e_open = open("Exercises.txt", "a+")
    21         1       1073.0   1073.0      0.0      a_open = open("Answers.txt", "a+")
    22     10001      14619.0      1.5      0.1      for i in range(0, q_c):
    23     10000    7059101.0    705.9     56.3          post = question.post_order_generator(m_v)
    24     10000     217674.0     21.8      1.7          tree = question.post_to_tree(post)
    25     10000     178163.0     17.8      1.4          ordinary = question.tree_to_ordinary(tree, [])
    26     10000    2002280.0    200.2     16.0          readable_str = question.to_readable(ordinary)
    27     10000     109711.0     11.0      0.9          e_open.write(f'{i+1}、{readable_str}\n')
    28
    29     10000    2428198.0    242.8     19.4          ans = question.count_ans(post)
    30     10000     423654.0     42.4      3.4          answer = question.to_with_fraction(ans)  # 轉成帶分數
    31     10000     103062.0     10.3      0.8          a_open.write(f'{i+1}、{answer}\n')
    32         1        576.0    576.0      0.0      e_open.close()
    33         1        390.0    390.0      0.0      a_open.close()

 

 

批改模式:對剛纔生成的10000條題目的答案進行修改

批改模式核心代碼:

    # 批改模式
    exercise_path = "Exercises.txt"
    answer_path = "Answers.txt"
    make_standard(exercise_path)
    check_exercise(answer_path)

 

模塊make_standard的用時:

Total time: 1.72545 s
File: time.py
Function: make_standard at line 35

Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
    35                                           @profile
    36                                           def make_standard(e_p):
    37                                               """
    38                                               根據題目文件生成標準答案StandardAnswers.txt
    39                                               :param e_p: 題目文件的path
    40                                               :return: 生成StandardAnswers.txt
    41                                               """
    42         1          9.0      9.0      0.0      try:
    43         1        409.0    409.0      0.0          e_open = open(e_p, "r")
    44                                               except FileNotFoundError:
    45                                                   print("找不到該文件")
    46                                                   sys.exit()
    47                                               except:
    48                                                   print("文件打開失敗,請從新運行程序")
    49                                                   sys.exit()
    50         1        775.0    775.0      0.0      sa_open = open("StandardAnswers.txt", "a+")
    51         1      10663.0  10663.0      0.3      e_lines = e_open.readlines()
    52     10001      15022.0      1.5      0.4      for line in e_lines:
    53     10000      28078.0      2.8      0.7          ques_num = line.split("")[0]  # 題目序號
    54     10000      31358.0      3.1      0.7          ques_str = line.split("")[1].rstrip('\n')  # 去掉前面的序號和後面換行符
    55     10000     894383.0     89.4     21.3          ordinary = question.to_unreadable(ques_str)
    56     10000     370173.0     37.0      8.8          post = question.ordinary_to_post(ordinary)
    57     10000    2341848.0    234.2     55.7          sa = question.count_ans(post)
    58     10000     413446.0     41.3      9.8          standard = question.to_with_fraction(sa)
    59     10000      98687.0      9.9      2.3          sa_open.write(f'{ques_num}、{standard}\n')
    60
    61         1        266.0    266.0      0.0      e_open.close()
    62         1        673.0    673.0      0.0      sa_open.close()

 

模塊check_exercises的用時:

Total time: 0.0277891 s
File: time.py
Function: check_exercise at line 64

Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
    64                                           @profile
    65                                           def check_exercise(a_p):
    66                                               """
    67                                               標準答案與用戶答案逐行對比,統計對錯
    68                                               :param a_p: 用戶答案path
    69                                               :return: 生成Grade.txt統計文件
    70                                               """
    71         1        459.0    459.0      0.7      sa_open = open("StandardAnswers.txt", "r")
    72         1        264.0    264.0      0.4      a_open = open(a_p, "r")
    73         1       5800.0   5800.0      8.6      sa_lines = sa_open.readlines()
    74         1          3.0      3.0      0.0      right = []
    75         1          1.0      1.0      0.0      wrong = []
    76
    77     10001      10571.0      1.1     15.6      for sa_line in sa_lines:
    78     10000      20640.0      2.1     30.5          if sa_line == a_open.readline():
    79     10000      20990.0      2.1     31.0              right.append(sa_line.split("")[0])
    80                                                   else:
    81                                                       wrong.append(sa_line.split("")[0])
    82         1         94.0     94.0      0.1      sa_open.close()
    83         1         32.0     32.0      0.0      a_open.close()
    84
    85         1       5839.0   5839.0      8.6      grade_open = open("Grade.txt", "a+")
    86         1       3019.0   3019.0      4.5      grade_open.write(f'正確{len(right)}題:{right}\n')
    87         1         24.0     24.0      0.0      grade_open.write(f'錯誤{len(wrong)}題:{wrong}\n')

具體時間分析看第五部分後面 

 

4、設計實現過程

簡易版類圖

question.py是與數學操做相關的函數,大部分由光證同窗完成

main.py是主函數,負責獲取用戶輸入並調用question裏面函數,由子昊同窗完成

 

程序有正向輸出和逆向兩部分模塊

正向輸出:逆波蘭表達式列表 → 轉化爲二叉樹 → 轉化爲中序表達式列表 → 轉化爲人看的字符串形式

逆向:人看的字符串形式 → 中序表達式列表 → 逆波蘭表達式列表

 

逆波蘭表達式的好處是不須要括號,減小程序複雜度,運算順序徹底取決於數字的排列,並且計算能夠方便地經過入棧出棧來實現。

生成題目模式使用的random_exercises模塊就是正向輸出,先生成逆波蘭表達式,使用count_ans函數進行計算,存入Answers.txt中,以後再正向輸出字符串到Exercises.txt

批改題目模式使用make_standard模塊:逆向把Exercises.txt的字符串轉化爲逆波蘭表達式,使用count_ans函數進行計算,保存到StandardAnswers.txt

                         check_exercises模塊:逐行與Answers.txt對比,正確的就存入right列表,錯誤的就存入wrong列表,最後輸出各列表的元素和元素個數

 

5、核心代碼講解

1. 隨機生成逆波蘭表達式

 1 def post_order_generator(max_value):
 2     """
 3     逆波蘭式子生成器,隨機生成一個逆波蘭表達式
 4     :param max_value: int類型,表示數值的最大值
 5     :return: 存儲逆波蘭表達式的列表
 6     """
 7     ch = ('+', '-', '×', '÷')
 8     char_num = random.randrange(1, 4)
 9     num_num = char_num - 1
10     # suffix_list是逆波蘭表達式列表,先在列表前面插入兩個數字
11     suffix_list = [str(get_random_fraction(max_value)), str(get_random_fraction(max_value))]
12     now_num = 2
13     while char_num or num_num:
14         if now_num >= 2:
15             if char_num and random.randrange(0, 2):
16                 suffix_list.append(ch[random.randrange(0, 4)])
17                 now_num = now_num - 1
18                 char_num = char_num - 1
19             elif num_num:
20                 suffix_list.append(str(get_random_fraction(max_value)))
21                 num_num = num_num - 1
22                 now_num = now_num + 1
23             else:
24                 suffix_list.append(ch[random.randrange(0, 4)])
25                 char_num = char_num - 1
26                 now_num = now_num - 1
27         else:
28             suffix_list.append(str(get_random_fraction(max_value)))
29             now_num = now_num + 1
30             num_num = num_num - 1
31     st = ".".join(suffix_list)
32     if st in problem_set or count_ans(suffix_list) < 0:
33         suffix_list = post_order_generator(max_value)
34         return suffix_list
35     else:
36         problem_set.add(st)
37         return suffix_list

 

逆波蘭表達式的限制是第一二個元素必須是數字,最後一個元素必須是運算符,另外數字個數是運算符個數+1

本模塊是先隨機出式子有n個運算符,則數字有n+1個,除去前兩個數先放入列表,和列表最後的運算符,則剩下的符號數和數字數都是n-1,以後在剩下位置隨機生成

另外將生成的逆波蘭表達式轉化爲字符串存入一個set集合中,因爲集合的惟一性,若是新生成的表達式已經在集合中,則這條式子要從新生成

遺憾的是3+4和4+3的判重咱們仍是沒有成功實現

 

2. 逆波蘭轉二叉樹

 1 def post_to_tree(post_list):
 2     """
 3     把逆波蘭式轉換成一棵模擬二叉樹
 4     :param post_list: 逆波蘭式列表
 5     :return: 一個列表,表示一棵樹
 6     """
 7     ch = ('+', '-', '×', '÷')
 8     temp = []
 9     for v in post_list:
10         if v in ch:
11             t1, t2 = temp.pop(), temp.pop()
12             temp.append([t2, t1, v])
13         else:
14             temp.append(v)
15     return temp[0]

逆波蘭表達式的兩個數字存入葉子結點,運算符存入根結點,成爲一棵二叉樹

 

3. 二叉樹轉中序表達式

 1 def tree_to_ordinary(tree_list, medium_list):
 2     """
 3     把二叉樹轉換成普通表達式
 4     :param tree_list: 二叉樹的列表
 5     :param medium_list:中序表達式的列表,主要爲了遞歸調用,剛開始可傳一個空列表
 6     :return:一個普通表達式的列表
 7     """
 8     ch_val = {'+': 1, '-': 1, '×': 2, '÷': 2}  # 符號優先級
 9     if type(tree_list[0]) == list:
10         if ch_val[tree_list[2]] > ch_val[tree_list[0][2]]:
11             medium_list.append('(')
12             medium_list = tree_to_ordinary(tree_list[0], medium_list)
13             medium_list.append(')')
14         else:
15             medium_list = tree_to_ordinary(tree_list[0], medium_list)
16     else:
17         medium_list.append(tree_list[0])
18     medium_list.append(tree_list[2])
19     if type(tree_list[1]) == list:
20         medium_list.append('(')
21         medium_list = tree_to_ordinary(tree_list[1], medium_list)
22         medium_list.append(')')
23     else:
24         medium_list.append(tree_list[1])
25     return medium_list

對於這一棵二叉樹,後序遍歷就是逆波蘭表達式(後綴表達式),中序遍歷就是中序表達式(平時用的樣式)

難點在於× ÷號的優先級比+ -號要高,遇到這種狀況須要加括號

 

4. 計算逆波蘭表達式

 1 def count_ans(post_list):
 2     """
 3     計算逆波蘭式的結果和判斷這條式子是否合法,
 4     若是中途出現負數或者除零都會返回-1,不然返回最終結果
 5     :param post_list: 逆波蘭式的列表
 6     :return: Fraction類型的結果
 7     """
 8     ch = ('+', '-', '×', '÷')
 9     stack = []
10     for v in post_list:
11         if v in ch:
12             t1, t2 = stack.pop(), stack.pop()
13             try:
14                 if v == '+':
15                     t1 = t1 + t2
16                 elif v == '-':
17                     t1 = t2 - t1
18                 elif v == '×':
19                     t1 = t1 * t2
20                 else:
21                     t1 = t2 / t1
22                 if t1 < 0:
23                     return -1
24                 stack.append(t1)
25             except ZeroDivisionError:
26                 return -1
27         else:
28             stack.append(Fraction(v))
29     return stack[0]

使用逆波蘭表達式的計算就是由於式子中沒有括號因此容易寫程序,

將逆波蘭表達式列表當作棧來使用,

新建一個ch元祖存+ - x ÷四個運算符,而後對逆波蘭式列表中元素逐個遍歷,若是元素是運算符,則把運算符前兩個元素出棧進行計算再入棧,直到棧中沒有運算符爲止

 

回看效能分析:

生成模式:

1. 能夠看到隨機生成逆波蘭表達式post_order_generator是最耗費時間的,佔了56%的時間,猜想是由於random函數比較耗時,而一條式子平均須要5次random

2. 第二耗時間的是計算逆波蘭count_ans,由於每個列表元素都要遍歷一下看在不在ch元祖裏面,並且要作乘除法

3. 第三耗時間的讓人驚奇,竟然是轉成可閱讀to_readable,多是將假分數轉化爲帶分數須要作除法

 

批改模式:

1. make_standard模塊中第一耗時是計算函數count_ans,理由同上

2. make_standard模塊中第二耗時是轉成不可閱讀to_unreadable,做爲to_readable的反函數,理由也是由於須要作乘法通分把整數部分加回去分子中

3. check_exercises模塊中最耗時是逐行對比部分,該部分是這個模塊最核心的代碼,並且由於須要字符串匹配,看兩邊字符串是否同樣

 

6、測試用例

因爲本程序的特殊性,運行一次就能夠生成n條測試,極大方便咱們判斷對錯,如下給出生成15條算式時的例子

Myapp.exe -n 15 -r 10

生成的Exercises.txt

一、4'1/2 - 2/5
二、3/4 × 1/2
三、2'2/3 ÷ 1/2 + 1
四、2 × 4'1/2 ÷ 1'2/5
五、2/3 - ( 1/7 - ( 0 × 1'1/7 ) )
六、5/9 ÷ 5
七、0 × 7/9
八、2 + 1'3/4
九、1'1/5 ÷ 1
十、7/9 ÷ 1
十一、1 + 1'1/2 + 4
十二、0 × 0 × 7/8
1三、( 5/7 + 1'1/5 ) ÷ 1/4
1四、4 × 3
1五、1'1/7 × ( 0 + 1/3 )

生成的答案Answers.txt

一、4'1/10
二、3/8
三、6'1/3
四、6'3/7
五、11/21
六、1/9
7、0
八、3'3/4
九、1'1/5
十、7/9
十一、6'1/2
12、0
1三、7'23/35
1四、12
1五、8/21

 

對其中的2 3 5 7 11 13題故意改爲錯誤答案,存爲MyAnswers.txt

一、4'1/10
二、3/9
三、6'1/4
四、6'3/7
5六、1/9
七、5
八、3'3/4
九、1'1/5
十、7/9
十一、6
12、0
1三、7'23/36
1四、12
1五、8/21

使用以下代碼執行批改模式

Myapp.exe -e Exercises.txt -a MyAnswers.txt

生成的標準答案StandardAnswers.txt和原來的Answers.txt徹底一致

一、4'1/10
二、3/8
三、6'1/3
四、6'3/7
五、11/21
六、1/9
7、0
八、3'3/4
九、1'1/5
十、7/9
十一、6'1/2
12、0
1三、7'23/35
1四、12
1五、8/21

批改結果Grade.txt也判斷正確

正確9題:['1', '4', '6', '8', '9', '10', '12', '14', '15']
錯誤6題:['2', '3', '5', '7', '11', '13']

 

7、小結

1. 子昊對光證的

       我的認爲本次結對編程項目很是成功,你們都發揮了各自的所長,李光證同窗是ACM隊的,對算法比較熟悉,我由於在工做室作過項目,對使用python比較有經驗

       一開始咱們討論了兩種方案,方案一是由內往外生成,即5 x(6 +(7 + 5)),從7+5開始生成,7+5和5+7等價,再到6+12和12+6等價,不過這樣會引入不少無用的括號並且括號括的位置也千奇百怪;

所以後來選擇了使用逆波蘭表達式的方案,計算能夠直接對列表出棧入棧,也省卻了括號的麻煩,使用樹便可把逆波蘭表達式轉爲日常的中序表達式。

       光證對算法方面比較熟悉,因此負責逆波蘭表達式的生成,計算,轉化,其餘零散的部分則由我完成。

       此次是光證先寫出他的部分,(他花了大概5小時)再由我來組裝,我在剛接手光證代碼時,被一堆奇奇怪怪的數據結構方面的用法搞懵了,徹底看不懂·,並且代碼很亂,不符合PEP8規範,在Pycharm中全是波浪線看着很難受,因此花了一點時間教會了光證用pycharm的自動pep8,並使用‘’‘’‘’來註釋函數功能,這樣在懸停時就能夠看到這個函數到底是作什麼的,不過光證的150多行代碼仍是花了一個多小時才徹底看懂。

       後來就是根據本身的理解把光證的函數封裝好,用時大約6小時,程序就寫完了,後面再用了約2個小時對框架進行優化,將模塊分得更細,更容易維護。因此整個開發流程仍是比較舒服的,比我的項目快了好多,表揚光證。

   因此此次的成功合做,一是分工明確,光證負責數學函數,我負責封裝; 二是接口定義好,相對比較獨立; 三是我和光證python水平差很少,並且在同一個宿舍方便交流; 四是你們都很好完成了本身的任務,沒有互相拖沓。

       但願之後還有更多相似這樣完美的合做。

2.  光證對子昊的

         通過多天的努力,本次的結對編程項目完美結束,在此次的結對項目中,收益匪淺。

  子昊在工做室裏面作過項目,有過合做的經驗,而且對python的使用有經驗,而我是學校ACM集訓隊的一員,對於算法的設計、實現比較熟悉。因此我主要負責編寫關於表達式的生成,轉換還有計算和分數之間的轉換等功能函數,而子昊負責把我寫的函數整合在一塊兒,對於輸入輸出的編寫還有程序的性能分析。

  由於我是參加ACM競賽的,平時敲的變量名能有多短就寫多短,因此此次寫代碼的時候也奇奇怪怪的,因此第一次給子昊的時候他很難理解,而後子昊就來給我講了python代碼的規範,還有教會了我使用pycharm的一些竅門,我改完以後就行了不少。這可能就像蕭伯納所說的那樣「你有一個蘋果,我有一個蘋果,咱們交換一下,一人仍是隻有一個蘋果;你有一個思想,我有一個思想,咱們交換一下,一人就有兩個思想」。

  而本次也有許多的不足之處,好比表達式重複的問題我只排除了後綴表達式相同的狀況而已,還有的就是最後函數沒有封裝進類裏面。

相關文章
相關標籤/搜索