结对项目
| 软件工程 | 网络工程1934 |
|---|---|
| 这个作业要求在哪里 | 结对项目 |
| 这个作业的目标 | 学会结对编程,合作编写程序 |
3119005389 麦俊宇
3119005385 刘宇
| PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
|---|---|---|---|
| Planning | 计划 | ||
| Estimate | 估计这个任务需要多少时间 | 30 | 30 |
| Development | 开发 | ||
| Analysis | 需求分析 (包括学习新技术) | 120 | 120 |
| Design Spec | 生成设计文档 | 30 | 20+? |
| Design Review | 设计复审 | 60 | 60 |
| Coding Standard | 代码规范 (为目前的开发制定合适的规范) | 5 | 5 |
| Design | 具体设计 | 20 | 15 |
| Coding | 具体编码 | 60 | 80 |
| Code Review | 代码复审 | 30 | 60 |
| Test | 测试(自我测试,修改代码,提交修改) | 60 | 90 |
| Reporting | 报告 | ||
| Test report | 测试报告 | 60 | 60 |
| Size Measurement | 计算工作量 | 20 | 10 |
| Postmortem & Process Improvement | 事后总结, 并提出过程改进计划 | 10 | 30 |
| Total | 合计 | 505 | 580+? |
设计思路
用一个邻接表表示各模块之间的调用关系
main -> generateExercise -> checkExercise
generateExercise -> generateFunction
generateFunction -> generateAnswer
checkExercise -> generateAnswer
1.主程序
程序进入点,处理参数,对于不同的参数输入用不同的模块进行处理,使用-ae参数的进行批改,而使用-nr参数的生成题目,否则报错;
2.生成题目
generateExercise()
使用主程序传递nr作为参数,并调用n次生成算式模块进行算式的生成,生成完成后将题目和答案分别输入到文件中以待检阅,本身不参与具体的运算;
3.生成算式
generateFunction()
在生成时避免重复,在随机生成时添加限制,使式子不会重复,生成后检查答案是否为负数,并且有除号时是否为真分数(分子一定小于分母),如果不是则重新生成 并将符合条件的式子返回到上一级函数;
4.生成答案
generateAnswer()
根据给出的式子生成答案,要对给出的式子进行处理,使其能被解释器理解并计算,传入时保证式子合法;
5.对比答案
checkExercise()
提取题目文本上Exercises.txt的题目并计算其正确答案,用之与答案文本answerfile.txt上的答案对比,并记录相同答案的题号,最后在结果文本Grade.txt输出答案比较情况: Correct 相同答案的数目(题号,...) Wrong 不相同答案的数目(题号,...)
代码说明
主程序
处理各种参数,判断参数是否正确,处理异常抛出。
主要就是处理参数的一部分,这里简单介绍一下
try: # reading arguments
opts, args = getopt.getopt(argv, "hn:r:e:a:", ["num=", "rad=", "eFile=", "aFile="])
except getopt.GetoptError:
print('Arguments ERROR')
sys.exit(2)
n, r = 50, -1
flage, flaga = 0, 0
for opt, arg in opts: # process arguments
if opt == '-h':
print('Myapp.exe -r <radius> [-n <number=10>] to generate n Exercise which between 0 and r. Or')
print('Myapp.exe -e <exerciseFile> -a <answerFile> to check if answer right')
if opt in ('-n', '--num'):
try:
n = int(arg)
if n > 1e5:
print('n cannot larger than 1e6. Set r to 1e6')
n = int(1e5)
except ValueError:
print('n must be a number')
sys.exit(2)
if opt in ('-r', '--rad'):
try:
r = int(arg)
if r > 1e6:
print('r cannot larger than 1e6. Set r to 1e6')
r = int(1e6)
except ValueError:
print('r must be a number')
sys.exit(2)
if opt in ('-e', '--eFile'):
try:
efile = open(arg, mode='r')
flage = 1
except [OSError, FileNotFoundError]:
print('Exercise file not found. Please double check the path and your spelling.')
sys.exit(2)
if opt in ('-a', '--aFile'):
try:
afile = open(arg, mode='r')
flaga = 1
except [OSError, FileNotFoundError]:
print('Answer file not found. Please double check the path and your spelling.')
sys.exit(2)
读入参数以及处理异常,还有返回错误信息,除了题目给出的nrea参数之外,还增加了一个h简要说明程序如何使用。
题目生成
这里占了随机生成的大头,因为我的程序为了避免生成重复,设置了前验和后验,前验可以避免生成交换之后重复的式子,不需要等到完成生成式子之后再去检验重复,大大提高了效率,而且这种重复占了绝大多数非法式子,而且编写随机生成时花了比较长的时间调参,让程序保证题目的多样性,这里的随机函数会随机生成五个参数交给下一个函数生成式子,经过设计的参数能够尽最大努力防止生成重复式子。
random.seed() # initial random
ans = []
again = False
while len(ans) < n:
been = (0, 1,)
div = 1
for i in range(1, r + 1):
for j in range(0, r - i + 1):
if i == 1 and j == 0 and random.random() < 0.8:
continue
for k in (2, 3, 4):
if again and k == 2:
continue
if i + (j * (k - 1)) <= r:
if random.random() < 0.6: # call generateFunction to generate integer function
ans += generateFunction(i, k, j, div, r)
if len(ans) >= 0.9 * n:
break
if len(ans) >= 0.9 * n:
break
if len(ans) >= 0.9 * n:
break
if len(ans) >= 0.9 * n:
break
这里仅生成包含整数的式子,并符合需求和给出参数。
式子生成
根据给定的参数生成式子,根据五个参数大致确定了式子的结构,剩下来的是填充数字、符号以及括号,填充数字时要注意带分数形式,所以写了一个函数标准化分数,最后传入参数计算出答案进行后验(是否出现小于0,是否除以0,是否有除号时结果不为分数),通过检验之后才将式子返回。
def fractionsNormalize(s, r): # formalize fractions
x = fractions.Fraction(s)
x = x.limit_denominator(r)
if x.denominator == 1:
return str(x.numerator) # integer
elif x > 1:
t = int(x.numerator / x.denominator)
b = int(x.numerator % x.denominator)
return str(t) + '\'' + str(b) + '/' + str(x.denominator) # fractions which > 1
else:
return str(x.numerator) + '/' + str(x.denominator) # fractions which < 1
标准化分数,分为整数,带分数和真分数。
for i in range(0, num):
if i == a: # insert left parentheses before number
dec[i] += '('
if div == 1:
dec[i] += str(arr[(-i - 1)])
else:
dec[i] += fractionsNormalize(arr[i] / (div + random.choice(range(-div + 2, div))), div)
if i == b: # insert right parentheses after number
dec[i] += ')'
char = [random.choice(calcChar), ]
for i in range(1, num - 1):
char += [random.choice(calcChar), ]
func = str(dec[0])
for i in range(1, num):
func += ' ' + char[i - 1] + ' ' + dec[i]
选定数字、符号和括号并向向字符串中填充。
计算答案
将式子翻译成合法的,能被解释器读懂的字符串,然后使用eval()运算出结果。
def repl(matched):
res = '(' + matched.group() + ')'
res = res.replace('\'', '+')
return str(res)
def generateAnswer(func):
func = str(re.sub("[1-9][0-9]*\'[1-9][0-9]*/[1-9][0-9]*", repl, func))
func = str(re.sub("[1-9][0-9]*/[1-9][0-9]*", repl, func)) # use Regular Expression to translate the function
func = func.replace('÷', '/')
func = func.replace('×', '*')
ans = decimal.Decimal(eval(func))
return ans
要注意优先级,特别是带分数和真分数的加法和除法,举个例子:\(5 × 2'1/4\) 和 \(1 ÷ 6/8\) 不加括号就会出现优先级错误。
批改练习
将两个文件传入后,每一行计算并比对等号两边是否相等,因为用了Decimal,所以要使用误差控制,控制在了\(1e^{-6}\)内。
def checkExercise(efile, afile):
correct = ()
wrong = ()
exc = efile.readline()
ans = afile.readline()
pid = 1
while exc != "" and ans != "":
a = generateAnswer(exc[len(str(pid)) + 1:-3])
b = generateAnswer(ans[len(str(pid)) + 1:])
if abs(a - b) < 0.000001:
correct += (pid,)
else:
wrong += (pid,)
# print(a, b)
exc = efile.readline()
ans = afile.readline()
pid = pid + 1
ofile = open("Grade.txt", 'w')
ofile.write("Correct: " + str(len(correct)) + str(correct) + "\n")
ofile.write("Wrong: " + str(len(wrong)) + str(wrong) + "\n")
return
闪光点
前验和后验:大幅度节省时间,使每一条式子只需要验证自己而不需要交叉比对。如果要进行交叉比对的话,要使用至少 \(O(n^2)\) 的时间进行检验,而现在只需要 \(O(1)\)。
分数处理:使用一个函数规整化真分数、带分数以及整数,使式子符合要求的同时能够被解释器读懂,尽可能减少计算误差。
性能分析
959 function calls (943 primitive calls) in 0.000 seconds
Ordered by: call count
ncalls tottime percall cumtime percall filename:lineno(function)
233 0.000 0.000 0.000 0.000 {method 'append' of 'list' objects}
147 0.000 0.000 0.000 0.000 {built-in method builtins.isinstance}
138 0.000 0.000 0.000 0.000 sre_parse.py:164(__getitem__)
68 0.000 0.000 0.000 0.000 sre_parse.py:233(__next)
64 0.000 0.000 0.000 0.000 sre_parse.py:172(append)
63 0.000 0.000 0.000 0.000 sre_parse.py:254(get)
63 0.000 0.000 0.000 0.000 {built-in method builtins.ord}
52/46 0.000 0.000 0.000 0.000 {built-in method builtins.len}
18 0.000 0.000 0.000 0.000 {built-in method builtins.min}
13 0.000 0.000 0.000 0.000 sre_parse.py:160(__len__)
9 0.000 0.000 0.000 0.000 enum.py:659(name)
9 0.000 0.000 0.000 0.000 types.py:171(__get__)
6/1 0.000 0.000 0.000 0.000 sre_compile.py:71(_compile)
6 0.000 0.000 0.000 0.000 sre_parse.py:111(__init__)
6/1 0.000 0.000 0.000 0.000 sre_parse.py:174(getwidth)
5 0.000 0.000 0.000 0.000 sre_parse.py:249(match)
5 0.000 0.000 0.000 0.000 sre_parse.py:493(_parse)
5 0.000 0.000 0.000 0.000 {method 'find' of 'bytearray' objects}
5 0.000 0.000 0.000 0.000 {built-in method builtins.max}
2 0.000 0.000 0.000 0.000 enum.py:283(__call__)
2 0.000 0.000 0.000 0.000 enum.py:562(__new__)
2 0.000 0.000 0.000 0.000 sre_compile.py:453(_get_iscased)
2 0.000 0.000 0.000 0.000 sre_compile.py:595(isstring)
2 0.000 0.000 0.000 0.000 sre_parse.py:81(groups)
2 0.000 0.000 0.000 0.000 {method 'items' of 'dict' objects}
1 0.000 0.000 0.000 0.000 re.py:250(compile)
1 0.000 0.000 0.000 0.000 re.py:289(_compile)
1 0.000 0.000 0.000 0.000 <string>:1(<module>)
1 0.000 0.000 0.000 0.000 sre_compile.py:249(_compile_charset)
1 0.000 0.000 0.000 0.000 enum.py:790(_missing_)
1 0.000 0.000 0.000 0.000 enum.py:797(_create_pseudo_member_)
1 0.000 0.000 0.000 0.000 enum.py:833(__and__)
1 0.000 0.000 0.000 0.000 sre_compile.py:276(_optimize_charset)
1 0.000 0.000 0.000 0.000 enum.py:886(<listcomp>)
1 0.000 0.000 0.000 0.000 enum.py:869(_decompose)
1 0.000 0.000 0.000 0.000 sre_compile.py:413(<listcomp>)
1 0.000 0.000 0.000 0.000 sre_compile.py:411(_mk_bitmap)
1 0.000 0.000 0.000 0.000 sre_compile.py:461(_get_literal_prefix)
1 0.000 0.000 0.000 0.000 sre_compile.py:492(_get_charset_prefix)
1 0.000 0.000 0.000 0.000 sre_compile.py:536(_compile_info)
1 0.000 0.000 0.000 0.000 sre_compile.py:598(_code)
1 0.000 0.000 0.000 0.000 sre_compile.py:759(compile)
1 0.000 0.000 0.000 0.000 sre_parse.py:76(__init__)
1 0.000 0.000 0.000 0.000 sre_parse.py:224(__init__)
1 0.000 0.000 0.000 0.000 sre_parse.py:286(tell)
1 0.000 0.000 0.000 0.000 sre_parse.py:435(_parse_sub)
1 0.000 0.000 0.000 0.000 sre_parse.py:921(fix_flags)
1 0.000 0.000 0.000 0.000 sre_parse.py:937(parse)
1 0.000 0.000 0.000 0.000 {built-in method __new__ of type object at 0x00007FF85872B810}
1 0.000 0.000 0.000 0.000 {built-in method _sre.compile}
1 0.000 0.000 0.000 0.000 {method 'translate' of 'bytearray' objects}
1 0.000 0.000 0.000 0.000 {method 'get' of 'dict' objects}
1 0.000 0.000 0.000 0.000 {method 'setdefault' of 'dict' objects}
1 0.000 0.000 0.000 0.000 {method 'extend' of 'list' objects}
1 0.000 0.000 0.000 0.000 {method 'sort' of 'list' objects}
1 0.000 0.000 0.000 0.000 {built-in method builtins.exec}
1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
Cprofile跑太快了,结果0.000s完成了10000道题的生成(我怀疑它根本没跑)。从这里可以看到调用的最多的是list的调用,毕竟插入答案需要很多次,调用次数多的并且时间比较长的应该是eval()函数。
所以上面的效能分析可以说是根本没用嘛,只好自己写了一个全局的凑凑数。
根据全局跑出来的时间分析一下:
\(n = 1e4\ r = 1e6\)
generateTime:
Time elapsed: 0.46s +-0.01s
checkTime:
Time elapsed: 0.35s +-0.01s
\(n = 1e4\ r = 1\)
generateTime:
Time elapsed: 1.03s +-0.10s
checkTime:
Time elapsed: 0.45s +-0.02s
\(n = 1e5\ r = 1e6\)
generateTime:
Time elapsed: 4.54s +- 0.20s
checkTime:
Time elapsed: 14.66s +- 0.50s
第一个是正常设定参数时候的用时,虽然说只有\(1e^4\)的数据大小,但因为程序中很多字符串处理和python本身的缺点导致运行得很慢(相对于\(O(n)\)而言);
第二个\(r=1\)的时候全是分数,并且要对r进行扩大(不然数量不够),所以时间会长一点(一点,指翻了一倍);
第三个生成时间确实是正常时的十倍,可以认为\(O(n)\)的时间复杂度是正确的,但批改的时候很慢,猜测是因为文件太大了(pyc都警告了)。
异常处理
在随机生成中的各种数据发生的异常在generateExercise里处理了,代码在上面有。
异常处理主要集中在main里:
对参数n, r的检查
if opt in ('-n', '--num'):
try:
n = int(arg)
if n > 1e5:
print('n cannot larger than 1e5. Set r to 1e5')
n = int(1e5)
except ValueError:
print('n must be a number')
sys.exit(2)
if opt in ('-r', '--rad'):
try:
r = int(arg)
if r > 1e6:
print('r cannot larger than 1e6. Set r to 1e6')
r = int(1e6)
except ValueError:
print('r must be a number')
sys.exit(2)
if r > 0:
generateExercise(n, r)
print('Generating', n, 'exercise...')
else:
print('Require -r <radius> argument. Input Myapp.exe -h for further help.')
sys.exit(2)
对文件名错误的异常:
if opt in ('-e', '--eFile'):
try:
efile = open(arg, mode='r')
flage = 1
except (OSError, FileNotFoundError):
print('Exercise file not found. Please double check the path and your spelling.')
sys.exit(2)
if opt in ('-a', '--aFile'):
try:
afile = open(arg, mode='r')
flaga = 1
except (OSError, FileNotFoundError):
print('Answer file not found. Please double check the path and your spelling.')
sys.exit(2)
测试运行
单元测试
对每一个函数(批改除外)进行单元测试,代码详情见Github。
..............
----------------------------------------------------------------------
Ran 14 tests in 0.004s
OK
测试批改函数
我和partner分别造了两组数据,在github上有,我这里贴一下他的:
//Ex.txt
1. 1'1/2 + 1/9 ÷ 55 =
2. 1 + 1 - 1 =
3. 2 +2 ÷ 9999 =
4. 695 × 0 -0 =
5. 0 + (7/10 + 2 - 1) =
6. 3/10 ÷ ( 1'69/100 + 31/100) =
7. 5 + 5 - 5 =
8. 123 + 568 / 96 =
9. 321 + 123 * 65 =
10. 33 * 55 / 22 =
11. 90 / 55 + 66 =
12. 45 + 85 - 99 =
13. 99 + 13 / 123 =
14. 5 + 3 + 2 =
15. 1 + 2 + 6 =
16. 12 * 45 / 45 =
17. 56 / 45 + 123 =
18. 66 / 258 + 123 =
19. 5 + 6 + 1'5/1 - 9 =
20. 1 + 1 =
//Ans.txt
1. 0
2. 5
3. 20000/9999
4. 0
5. 17/10
6. 3
7. 5
8. 1547/12
9. 8316
10. 330/4
11. 744/11
12. 65
13. 12190/123
14. 10
15. 9
16. 12
17. 5591/45
18. 5
19. 8
20. 2
//Myapp.exe -e Ex.txt -a Ans.txt
//Grade.txt
Correct: 15(3, 4, 5, 7, 8, 9, 10, 11, 13, 14, 15, 16, 17, 19, 20)
Wrong: 5(1, 2, 6, 12, 18)
有兴趣的自己算一算。
总结
partner总结:结对编程需要团队之间进行充分的交流,对题目的具体要求要达成一致,
在程序上不能埋头自己写自己的,需要时常进行沟通交流并最好规定好格式以及所需要的参数,
目的是让各自写的程序模块能够进行很好衔接运行。
我的总结:笑死,我有点害怕上面的被查重了。这次的代码工作大多数都是我一个人完成的,partner因为过于得闲再写了一个简化版的,我放在了另一个分支里。吐槽一下partner总是抓不住重点,写了一个下午的设计文档,又写了一个晚上的总结,结果设计文档我还要大修了一次;让他造数据的时候也是,设计测试计算答案的数据的时候一直给我一些贼简单的还单调的题目,要么直接给我要手算1min的题(问题是不给我答案,再问他拿的时候还等了很久),设计测试生成题目的时候反而给我很复杂的题目,造数据就造了半小时,明明前面已经验证了计算答案的模块了。不过partner还是挺好的,至少注释和闪光点都是他的成就,毕竟我觉得这代码全是常识,就会一个注释也没有了(虽然说我打这个10000个字符的博客的时候,他在打一个1600字的选修课报告),还有造的数据也不错,虽然说因为格式不对被我打回去几次。算了不说了,感觉我在暴露别人黑点的说。毕竟找不到别人才会内部消化嘛

浙公网安备 33010602011771号