任务:实现一个自动生成小学四则运算题目并具有题目答案修改功能的程序。(http://www.cnblogs.com/jiel/p/4810756.html)
各模块预计和实际耗费时间
|
PSP2.1 |
Personal Software Process Stages |
Time |
|
Planning |
计划 |
|
|
· Estimate |
· 估计这个任务需要多少时间 |
24h |
|
Development |
开发 |
|
|
· Analysis |
· 需求分析 (包括学习新技术) |
30min |
|
· Design Spec |
· 生成设计文档 |
1h |
|
· Design Review |
· 设计复审 (和同事审核设计文档) |
30min |
|
· Coding Standard |
· 代码规范 (为目前的开发制定合适的规范) |
1h |
|
· Design |
· 具体设计 |
3h |
|
· Coding |
· 具体编码 |
7h |
|
· Code Review |
· 代码复审 |
1h |
|
· Test |
· 测试(自我测试,修改代码,提交修改) |
5h |
|
Reporting |
报告 |
|
|
· Test Report |
· 测试报告 |
1h |
|
· Size Measurement |
· 计算工作量 |
10min |
|
· Postmortem & Process Improvement Plan |
· 事后总结, 并提出过程改进计划 |
30min |
|
合计 |
20h40min |
过程
经过对程序要求的分析,我进行了一个大致的规划,设计了Number类、Expression类,Number用于创建数的对象,包括自然数、真分数和带分数,Expression类用于创建子表达式,并存储表达式。使用C++来编写。
Number类设置了三个属性,能同时用来创建自然数,真分数和假分数。Expression类利用递归的方法存放带括号的完整表达式。
先随机生成表达式包含的数字个数,然后随机生成不同的数字,然后依旧是随机选取数字来完成括号的生成。例:1, 2/3, 1'3/4, 2三个数字和×, +, -三个个运算符号,随机选几个数字来生成括号,例:选择1, 2/3和×,则得到(1 × 2/3) + 1'3/4 - 2
不过,在实现第二个主要的功能时需要对字符串进行解析,而使用C++的话复杂度会很大,因为C++在string方面没有像JAVA、C#有split之类的函数,而这些功能函数在C++还要自己再写一遍,于是思前想后果断把代码移植到C#上去了。
遇到另一个比较棘手的问题就是对于符号‘×’ ‘÷’的输出和读取,在没有换C#之前,我也找了很多关于C++输出Unicode字符的方法,但是并不能达到预期的效果,渐渐的也就对C++失去的信心。把代码移植到C#上之后,这个问题出乎意料的迎刃而解了,而且不用费很大的功夫,只用在读文件的时候设置文件的编码格式再读,输出时直接就可以输出了。
通过上面两个问题的解决,真心地觉得在写工程之前一定要选择对的语言,不然等碰壁之后再换就少不了麻烦了。
对于性能这块儿,我并没有花太多时间来改进,因为我在写的过程中就尝试争取使用最优化的方法来写,只要我能想到的,我就都会去比较,这也就是为什么我编写的过程中也会耗费相对来说比较长的时间了。
整个工程中对整个程序的性能影响最大的应该是“查重”,因为如果要完全做到两两不相重的话,一一对比应该是最稳妥的方法了,所以如果真的要生成10000个,那么一一对比相对来说真的会拖延整个程序的运行速度。
来一段代码吧,Expression类
class Expression { private const char opr_plus = '+'; private const char opr_sub = '-'; private const char opr_multi = '\u00D7'; private const char opr_devide = '\u00F7'; public List<Expression> expressions = new List<Expression>(); public List<char> operators = new List<char>(); public Number answer = new Number(); public string oprs = ""; // for comparision public string nums = ""; // for comparision public Expression() { } public Expression(List<Expression> expressions, Random rand) { this.expressions = expressions; int n = expressions.Count(); for (int i = 0; i < n - 1; i++) { int temp = rand.Next(4); switch (temp) { case 0: operators.Add(opr_plus); break; case 1: operators.Add(opr_sub); break; case 2: operators.Add(opr_multi); break; case 3: operators.Add(opr_devide); break; } } } public Expression(String e) { if (!e.Contains(' ')) answer = new Number(e); // 1'2/3 else if (!e.Contains('(') && !e.Contains(')')) // 2/3 + 1 - 3/4 × 1 { string[] ss = e.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); for (int i = 0; i < ss.Count() / 2; i++) { expressions.Add(new Expression(ss[2 * i])); operators.Add(ss[2 * i + 1].ElementAt(0)); } expressions.Add(new Expression(ss.Last())); } else { while (e.Contains(' ')) { if (e.First() != '(') { int pos = e.IndexOf(' '); expressions.Add(new Expression(e.Substring(0, pos))); e = e.Substring(pos + 1); } else { int flag = 1; for (int i = 1; i < e.Count(); i++) { if (e.ElementAt(i) == ')') flag--; else if (e.ElementAt(i) == '(') flag++; if (flag == 0) { flag = i; break; } } expressions.Add(new Expression(e.Substring(1, flag - 1))); if (flag == e.Count() - 1) e = ""; else e = e.Substring(flag + 2); } if (e == "") break; operators.Add(e.First()); e = e.Substring(2); } if (e != "") expressions.Add(new Expression(e)); } } public Expression(Expression other) { expressions.AddRange(other.expressions); operators.AddRange(other.operators); answer = new Number(other.answer); } public Boolean calculate() { if (expressions.Count() == 0) return true; List<Expression> exp = new List<Expression>(); List<char> opr = new List<char>(); for (int i = 0; i < expressions.Count(); i++) if (expressions[i].expressions.Count() != 0) if (!expressions[i].calculate()) return false; for (int i = 0; i < expressions.Count(); i++) exp.Add(new Expression(expressions[i])); for (int i = 0; i < operators.Count(); i++) opr.Add(operators[i]); for (int i = 0; i < opr.Count(); i++) { if (opr[i] != opr_multi && opr[i] != opr_devide) continue; if (opr[i] == opr_multi) exp[i].answer.multi(exp[i + 1].answer); if (opr[i] == opr_devide) { if (exp[i + 1].answer.isZero()) return false; // devide 0 error! exp[i].answer.devide(exp[i + 1].answer); } exp.RemoveAt(i + 1); opr.RemoveAt(i); i--; } while (opr.Count() != 0) { if (opr[0] == opr_plus) exp[0].answer.plus(exp[0 + 1].answer); if (opr[0] == opr_sub) exp[0].answer.sub(exp[0 + 1].answer); if (exp[0].answer.isNegative()) return false; // a - b < 0 error! exp.RemoveAt(1); opr.RemoveAt(0); } answer = exp[0].answer; return true; } public Boolean isSimilar(Expression other) { if (!answer.isEqual(other.answer)) return false; if (other.oprs == "") other.oprsString(); oprsString(); if (!oprs.Equals(other.oprs)) return false; if (other.nums == "") other.numsString(); numsString(); if (!nums.Equals(other.nums)) return false; return true; } public String toString() { if (expressions.Count() == 0) return answer.toString(); String s = ""; int count = expressions.Count(); for (int i = 0; i < count - 1; i++) s = s + expressions[i].toString() + " " + operators[i] + " "; s = "(" + s + expressions[count - 1].toString() + ")"; return s; } private List<char> getOprs() { List<char> list = new List<char>(); list.AddRange(operators); foreach (Expression e in expressions) if (e.expressions.Count() != 0) list.AddRange(e.getOprs()); return list; } private List<Number> getNums() { List<Number> list = new List<Number>(); foreach (Expression e in expressions) { if (e.expressions.Count() != 0) list.AddRange(e.getNums()); else list.Add(e.answer); } return list; } private void oprsString() { List<char> list = getOprs(); list.Sort(); oprs = new string(list.ToArray()); } private void numsString() { List<Number> list = getNums(); List<string> a = new List<string>(); foreach (Number n in list) a.Add(n.toString()); a.Sort(); nums = string.Join("", a.ToArray()); } }