结对项目
| 这个作业属于哪个课程 | <网络1934-软件工程> |
| ---- | ---- | ---- |
| 这个作业的要求在哪里 | <作业要求> |
| 这个作业的目标 | 实现一个自动生成小学四则运算题目的命令行程序 |
陈松坤 3119005366
纪培浩 3119005375
PS:使用语言Golang
github链接
一、PSP表格
| PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
|---|---|---|---|
| Planning | 计划 | ||
| · Estimate | · 估计这个任务需要多少时间 | 605 | 590 |
| Development | 开发 | ||
| · Analysis | · 需求分析 (包括学习新技术) | 120 | 100 |
| · Design Spec | · 生成设计文档 | 30 | 30 |
| · Design Review | · 设计复审 | 30 | 30 |
| · Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 20 | 20 |
| · Design | · 具体设计 | 60 | 80 |
| · Coding | · 具体编码 | 120 | 150 |
| · Code Review | · 代码复审 | 30 | 30 |
| · Test | · 测试(自我测试,修改代码,提交修改) | 90 | 90 |
| Reporting | 报告 | ||
| · Test Repor | · 测试报告 | 30 | 20 |
| · Size Measurement | · 计算工作量 | 15 | 10 |
| · Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 60 | 30 |
| · 合计 | 605 | 590 |
二、效能分析
效能测试代码:
package main
import "testing"
func BenchmarkGenerate(b *testing.B) {
n = 10000
r = 10
for i := 0; i < b.N; i++ {
generateProblems()
}
}
func BenchmarkChekc(b *testing.B) {
exercisefile = "exercisefile.txt"
answerfile = "answerfile.txt"
for i := 0; i < b.N; i++ {
check()
}
}


产生题目耗时(执行4次):生成10000道题目平均耗时不到1s

阅卷模式耗时(执行1290次)
三、设计实现过程
核心思想:
算式构造:先随机获取运算符个数,然后从左到右构造数据,拼接。
算式计算:将运算式通过转化成逆波兰式来计算,双向链表代替栈来计算逆波兰式。
可以参考,里面波兰式讲的很全.
整数和真分数计算:用FAL分数结构来代替数值类型,整数也用FAL表示,支持加减乘除等分数类型的操作,需要加入真假分数的转换。
查重:时间原因,只是把结果重复的去掉。
异常:会出现除零,只能全局定义个bool来判断是否除零,处理结果跟查重一样,丢弃掉。
主要结构及其作用:
Problem存储了运算式链表,逆波兰式链表,运算式字符串以及计算的结果。




FAL和Sign都实现了Value接口的Len()类型

四、代码说明
随机产生小于r的数据.
func randData(l *list.List, r int64, p *Problem) {
var (
num int64
nume int64
deno int64
)
num = rand.Int63n(r) + 1
deno = rand.Int63n(r) + 1
//以接近2/7的概率产生分数,如果nume=0的话,需要将deno置1,方便计算
if (rand.Intn(7) % 6) == 0 {
nume = rand.Int63n(deno)
if nume == 0 {
deno = 1
}
} else {
nume = 0
deno = 1
}
if num != 0 || nume == 0 {
p.formulaTostring += strconv.FormatInt(num, 10)
}
if nume != 0 {
if num != 0 {
p.formulaTostring += "'"
}
p.formulaTostring += strconv.FormatInt(nume, 10)
p.formulaTostring += "/"
p.formulaTostring += strconv.FormatInt(deno, 10)
}
//fmt.Println(num, nume, deno)
f := Model(num, nume, deno)
l.PushBack(&Entry{
kind: 1,
value: f,
})
}
将运算式转换成逆波兰式
func (p *Problem) TransPostfixExpress() {
var ll *list.List
ll = new(list.List)
for ele := p.formula.Front(); ele != nil; ele = ele.Next() {
v := ele.Value.(*Entry)
if v.kind == 1 {
p.postfixExpress.PushBack(v)
} else {
symbol := v.value.(*Sign)
if ll.Len() == 0 || symbol.s == '(' {
ll.PushBack(v)
continue
}
if symbol.s == ')' {
for val := ll.Back(); val != nil; val = ll.Back() {
x := val.Value.(*Entry).value.(*Sign)
if x.s == '(' {
ll.Remove(val)
break
}
p.postfixExpress.PushBack(val.Value.(*Entry))
ll.Remove(val)
}
continue
}
x := ll.Back().Value.(*Entry).value.(*Sign)
if x.s == '(' {
ll.PushBack(v)
continue
}
if symbol.s == '-' || symbol.s == '+' {
for val := ll.Back(); val != nil; val = ll.Back() {
x := val.Value.(*Entry).value.(*Sign)
if x.s != '(' {
p.postfixExpress.PushBack(val.Value.(*Entry))
ll.Remove(val)
} else {
ll.PushBack(v)
break
}
}
if ll.Len() == 0 {
ll.PushBack(v)
}
continue
}
if symbol.s == '×' || symbol.s == '÷' {
for val := ll.Back(); val != nil; val = ll.Back() {
x := val.Value.(*Entry).value.(*Sign)
if x.s == '÷' || x.s == '×' {
p.postfixExpress.PushBack(val.Value.(*Entry))
ll.Remove(val)
} else {
ll.PushBack(v)
break
}
}
if ll.Len() == 0 {
ll.PushBack(v)
}
}
}
}
for val := ll.Back(); val != nil; val = ll.Back() {
p.postfixExpress.PushBack(val.Value.(*Entry))
ll.Remove(val)
}
}
计算结果,逆波兰式计算
func (p *Problem) Cal() *FAL {
var ll *list.List
//cnt := 0
ll = new(list.List)
for ele := p.postfixExpress.Front(); ele != nil; ele = ele.Next() {
//cnt++
kv := ele.Value.(*Entry)
if kv.kind == 1 {//kind=1,代表该元素不是运算符号
ll.PushBack(kv)
} else {
//fmt.Println(ll.Len(), kv.value.(*Sign).s)
ele1 := ll.Back()
ll.Remove(ele1)
ele2 := ll.Back()
ll.Remove(ele2)
kv1 := ele1.Value.(*Entry)
kv2 := ele2.Value.(*Entry)
fal1 := kv1.value.(*FAL)
fal2 := kv2.value.(*FAL)
sign := kv.value.(*Sign)
//fmt.Println(fal1, fal2)
switch sign.s {
case '-':
fal2 = Sub(fal2, fal1)
case '+':
fal2 = Add(fal2, fal1)
case '×':
fal2 = Mul(fal2, fal1)
case '÷':
fal2 = Div(fal2, fal1)
}
//将结果压入栈中
ll.PushBack(&Entry{
kind: 1,
value: fal2,
})
}
}
//fmt.Println(cnt)
return ll.Back().Value.(*Entry).value.(*FAL)
}
统计正确和错误的题目
//exercise是存储了题目Problem实例的切片,所以v就是Problem的实例,answer是提交的答案的字符串切片,通过比较字符串来判断是否正确
for index, v := range exercise {
//fmt.Println(v.formulaTostring, v.answer)
//v.answer.String()是将FAL格式化输出为字符串。
if answer[index] == v.answer.String() {
correct = append(correct, index+1)
} else {
wrong = append(wrong, index+1)
}
}
==========================
// Format output
func (s FAL) String() string { // 格式化输出
if s.Nume == 0 {
return fmt.Sprintf("%v", s.Num)
}
if s.Num == 0 {
return fmt.Sprintf("%v/%v", s.Nume, s.Deno)
}
return fmt.Sprintf("%v'%v/%v", s.Num, s.Nume, s.Deno)
}
主函数
func main() {
s := time.Now()
rand.Seed(time.Now().UnixNano())
len := len(os.Args)
//必须输入两个参数-n -r或者-e -a
if len < 5 {
Usage()
}
ParseArgs()
//生成题目
if n > 0 {
generateProblems()
}
//阅卷模式
if exercisefile != "" && answerfile != "" {
check()
}
fmt.Println(time.Since(s))
}
五、运行测试
生成1w道题目


阅卷模式


参数输入错误时的处理:

六、项目小结
陈松坤:这次结对项目是我经历的第一次合作开发的项目,在开发的过程中,我们通过积极讨论和查阅资料,解决了很多的问题(靠队友),在团队合作中体会到了效率的提升,深刻地理解到了团队合作的好处。
纪培浩:四则运算这个项目细节需要考虑的比较多,符号优先级和字符串的处理,在团队开发过程中需要细心分析积极讨论,在开发中我们因为没有完成一个模块就进行一次测试,所以花费了较长时间去调试,下次一定吸取教训,做到更好。
浙公网安备 33010602011771号