结对项目

| 这个作业属于哪个课程 | <网络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道题目

阅卷模式

参数输入错误时的处理:

六、项目小结

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

posted @ 2021-10-24 11:55  haogee  阅读(93)  评论(0)    收藏  举报