ペアプログラミングは-python実装しました

ソフトウェア工学のツイニング・プロジェクト:WCを達成するためのPythonプログラム

ツイニングプロジェクトGithubの住所

プロジェクトメンバー

  • 劉Zhihao 3117008744
  • タンワンチュアン3117008747

プロジェクトの要件

説明

コマンドラインプログラムが自動的に四則演算主要被写体を生成実装(グラフィカル・インターフェースも同様の機能を使用することができます)。

  • 自然数:0、1、2、...

  • 適切な割合:1/2、1/3、2/3、1/4、1'1 / 2、...

  • 演算子:+、 - 、×、÷

  • 括弧:(、)

  • 等号:=

  • セパレータ:(オペレータとフロント4つの等しい記号のための)スペース

  • 算術式:

  • E = N | E1 + E2 | E1 - E2 | E2×E1 | ÷E1 E2 | (E)、

  • 前記式のE、E1及びE2、nは自然数または分数です。

  • タイトル四則演算:のE =、eは演算式です。

需要

  1. 例えば、主題の制御パラメータの数を用いて生成-N

    MYAPP.EXE -n 10

    結果の10件のトピック。

  2. コントロール被験体において使用-Rパラメータ値(自然数、適切な画分、及び適切な分数の分母)範囲、例えば

    MYAPP.EXE -r 10

    (10を含まない)被検体10内の得られた四則演算。このパラメータは、1または他の自然数に設定することができます。このパラメータは、そうでないプログラムエラーを与えられたとの情報を助ける提供しなければなりません。

  3. タイトルは、計算フォーム部分式E1- E2における算術式、次いでe1≥E2が存在する場合には、負生成することができない発生しました。

  4. 式E1は÷のE2の形態のタスク生成サブストリング中に存在する場合、その結果は、適切な画分であるべきです

  5. 事業者それぞれの質問の数は3つ以下に表示されます。

  6. 生成されたタスクプログラムの実行を繰り返すことができない、すなわち、被験体は、+×任意の2つの有限スイッチングおよびタイトルトラックに変換ほぼ同じ演算式にすることはできません例えば、+ 45 = 23 + 23 = 45と被写体が繰り返される、= 8×6,6×8 =被写体が繰り返されます。3+(2 + 1)1 + 2 + 3及び二つのトピックは、+ 1 + 2 + 3(1 + 2)+ 3に相当する左の組み合わせので、繰り返され、すなわち、3 +(1 + 2)、すなわち、3 +(2 + 1)。しかし、1 + 2 + 3 + 2 + 1および1 + 2 + 3(1 + 2)+ 3,3 + 2 + 1に相当し、(3 + 2に相当するので、図3に示すように、2つの質問が繰り返されませんそれらの間で交換することはできません)+1は、同じ主題有限なりました。

  7. 次の形式でのディレクトリ生成されたタスクの実行ストアドプロシージャの現在Exercises.txtファイル:

    1. タイトル4つの操作1

    2. 四つの操作対象2

      ......

    前記入力及び出力における次の形式の適切な画分を、適切な画分が2'3 / 8として表さ適切な画分五分の3/5、2〜8分の3として表さ。

  8. トピックを作成しながら、次の形式で、現在のディレクトリ内のプログラムAnswers.txtファイルの実装に計算され、入金のすべての質問に答えます:

    1. 回答1
    2. 回答2

    具体的には、次の例計算適切な画分:1/6 + 1/8 = 7/24。

  9. プログラムは1万トピックの生成をサポートすることができるはずです。

  10. 次のように右と間違った答えと統計の数を決定するために与えられたトピックファイルおよび応答ファイルのサポートプログラム、入力パラメータは次のとおりです。

    MYAPP.EXE -e .TXT -a 。txt

    統計の結果は、次の形式で、ファイルGrade.txtに出力されます。

    正しい:5(1、3、5、7、9)

    間違った:5(2、4、6、8、10)

    ここで「:」5は対象/間違ったの数を示し、続いて括弧は右/間違った被験者数です。簡単にするために、被験体は、本明細書コンプライアンスシーケンス番号に応じて入力されていることを前提としています。

PSPテーブル

PSP2.1 パーソナルソフトウェアプロセス段階 推定時間がかかる(分) 実際の時間がかかる(分)
プランニング 計画 40 50
・見積り •このタスクが必要と推定どのくらいの時間 40 50
開発 開発 1590 1995
・分析 ・分析(新しい技術を学ぶ含む)が必要 90 80
・デザインスペック 設計ドキュメントの生成 60 80
・デザインレビュー ・デザインレビュー(と彼の同僚は、設計文書を見直し) 45 60
・コーディング標準 ・コードの仕様(現在の開発のための適切な規範の開発) 25 20
・ 設計 ・具体的な設計 120 180
・コーディング ・具体的なコーディング 900 1200
・コードレビュー ・コードレビュー 120 125
・テスト ・テスト(セルフテスト、コードを変更し、変更を提出) 70 60
報告 レポート 40 50
・ 試験報告書 ・テストレポート 30 50
・サイズ測定 ・コンピューティングのワークロード 30 30
・死後&プロセス改善計画 ・後知恵、およびプロセス改善計画を提案します 60 60
トータル 1630 2045

問題解決のためのアイデアの説明

式ビルダーモジュールでは、得られる式は、インターネット上で、非常に複雑で繰り返されないように、重く、難しい話題を抽出し、分析することにより、チームはプロジェクトが式ビルダーモジュールの校正分けと回答モジュールを修正されて提示します該当するアルゴリズムは、アカウントに例として、多くの列挙を使用していません。プロジェクトの問題解決のアイデアの枠組み次のとおりです。

  • 1.式の生成要求

    • ランダムに生成された数字
      • randint機能
    • ランダムに生成されたシンボル
      • ランダムに4つのオペランドから
      • ランダム化括弧ケース
    • 組み合わせ
      • オペランド、演算子、括弧表現
    • 重複チェック
      • 同様に、異なる括弧内の式、および異なるオペレータの数の数を極力列挙
      • 同じがFalseの場合式は、生成された比較に対応する場合、他にTrueを返します
    • 適切な分数変換
      • 仮分数がある場合は、真のスコアに変換
      • 分数モジュールとスコア
    • 答えを計算
      • これは、操作の異なる順序の式を使用して、括弧内に判定された場合、算出答えます
    • 書式付き入力ファイルへ
  • 2.マーキングトピック、校正の回答

    • トピックを扱います
      • 抽出オペランド
        • 適切な分数変換
      • 抽出演算子
      • 操作の順序は、ブラケット決定されます
      • 結果
    • 処理の回答
      • 前のステップを比較し、その答えで回答を求めanswer.txt
    • リターン結果
      • 正しい+ 1:同じ答えと比較した場合、
      • 比率は一貫性のない答えである場合:1 +間違いました

設計と実装

コード組織図

コード分​​析

再チェックモジュール(check.py)

最初同数か否かを、操作者が同じであるかどうか、文字列が同じであるかどうかを決定つのみオペレータとは、オペレータが最初か否かを同じ判断し、同じ番号が正確に同一であるか否かを判断します

'''对于有两个运算符的,若以上都一样,则有以下几种情况:
    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

式生成モジュール(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()

回答検証モジュール(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

コードカバレッジ

  • カバレッジとコードカバレッジを取得します

モジュール ステートメント 行方不明 カバレッジ
トータル 290 54 91%
Main.py 52 39 25%
check.py 55 42 24%
grade.py 77 4 95%

テスト

ユニットテスト

試験の詳細(手動テスト)

  • テスト機能-r -n

    • そして、デジタル操作の場合内のトピックを生成します:
    Main.py -r 1 -n 1  #在中断输入

    トピックのファイル:

応答ファイル:

  • 一万件のトピックは状況を生成します。

题目文件:

答案文件:

  • -E -aテスト機能

    三箇所の修正を答えることが、約3ヶ所を、以下の応答ファイルを変更します。

    -e -a機能付き解答・エラー・レート・テスト

    テスト結果:

回帰テスト

回帰テストは、古いコード修正、変更は新しいバグを導入したり、間違ったテストメソッドを生成するために他のコードが発生しない確認するために再テストを指します。

重力回帰テストの中心は、コアとしてキー・モジュールです。

この小規模プロジェクト、限られた機能は、キーを変更しないモジュールは、あなたが回帰テストのための観察ユニットテストを呼び出すことができます。

効果分析

  • CProfileの分析のためのPythonツールの使用
  • ncalls:表示函数调用的次数;
    tottime:表示指定函数的总的运行时间,除掉函数中调用子函数的运行时间;
    percall:(第一个percall)等于 tottime/ncalls;
    cumtime:表示该函数及其所有子函数的调用运行的时间,即函数开始调用到返回的时间;
    percall:(第二个percall)即函数运行一次的平均时间,等于 cumtime/ncalls;
    filename:lineno(function):每个函数调用的具体信息;

项目总结与收获

​ 本次项目由Python编写,工具、模块主要有用到:re正则表达式模块、OptionParser参数解析、unittest用于单元测试、Cprofile用于效能分析等。整体的项目分工并不难,将整个项目分成表达式生成模块、答案生成模块和答案校验模块,其中表达式生成模块中难点需要注意判断生成的表达式是否有相同并将其排除;正因为题目中有了这个不能出现重复题目的要求,为了去重而设计出的算法占了很大的时间复杂度,直接影响了题目生成的效率,这是本项目做的不足的地方,希望后期可以设计出时间复杂度更低的算法以优化生成大量题目的速度。

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

おすすめ

転載: www.cnblogs.com/tanwanchuan/p/11682054.html