【个人项目总结】C#四则运算表达式生成程序
S1&2.个人项目时间估算
PSP表格如下:
|
S3.优化程序
优化所耗费的时间大致占据代码复审和测试阶段的40%,具体时间参照上表
优化点:
A. 存储潜在的冗余表达式
在表达式重复性方面,笔者考虑采用牺牲空间换取时间的方式,使用Dictionary类管理程序生成表达式过程中产生的参考数据,具体数据模式如下:
<表达式结果,List<以此为结果的所有表达式>>
这里强调的所有有两点含义,一是指程序输出的表达式中,所有以键值为结果的表达式,而也包括所有与这些表达式重复的式子(即通过交换律可以互相转换)。
例如:
1
↓-----映射----->
{(1+7)÷(2×4)
(1×2)-(3-2)//这两个式子由程序生成并输出,它们有相同的结果但并不重复
(7+1)÷ (2×4)
(7+1)÷ (4×2)
(1+7)÷ (4×2)
(2×1)- (3-2)//这四个式子与前面两个构成了重复,它们在程序运行过程中产生但不输出,不过由于其值为1,所以也添加到此List中
就以实现这一点为目标,程序进行了两方面优化:
一是在生成表达式的过程中,同时生成与该表达式重复的所有式子并通过ref修饰的List<String>参数予以输出,一旦输出便添加到Dictionary中作为下一个表达式的参考;
另一则是以String的形式存储该表,而不是直接存储表达式对象,通过重写显示转换方法,实现Expression类与String类的快速转换
代码如下:
public static implicit operator Expression(String expStr)
{
String result = MathCore.calculate(expStr); //计算表达式结果
String pattern = "(\\+)||(-)|(×)|(÷)|";
Regex regex = new Regex(pattern);
MatchCollection list = regex.Matches(expStr); //利用正则匹配得出表达式中的运算符个数
int opnum = list.Count;
return new Expression(expStr, result, opnum);
}
这样,本程序回避重复性的算法思路就很清晰了:
生成表达式并获得结果——>通过交换律生成与之等价的表达式列——>通过结果进行查表,如果有该结果,则再查有无重复式子(如无该结果,则表达式必然有效,直接插表即可)——>如有,则放弃该表达式 ; 如无,则将该表达式的表达式列插入,继续生成直至达到规定数目
如何证明程序中真的没有出现重复的式子?
根据这个算法就很简单了,显然我们可以将输出的所有表达式和相应生成的重复表达式一并输出(本项目中该功能通过-i开启,通过CheckSame.exe对当前目录下Exercise.txt进行检测),因为输出的表达式也与自身重复,所以只有输出的表达式会在输出文件中出现两次,这样如果输出的表达式中有两个重复了,即一个式子与另一个式子生成的重复表达式相同,该表达式就会出现两次以上,这样就很容易通过查重程序检测出来了。只要理解的重复性规则正确,保证对于每个表达式充分生成了与之重复的所有表达式,该检测方法即可有效对结果的重复性进行检测。
B. 表达式结果伴随而表达式生成,与计算模块独立
当我得知,还需要为这个程序设计一个计算表达式结果并与答案表比对的模块时,我的第一反应是直接用这个计算模块去求生成的有效表达式的值,因为这样程序的编码,设计会简单很多。但是,相应的,生成一遍、再读一遍、再出栈入栈各种算一遍,耗费的时间资源就高了起来。此外,生成的表达式,其计算顺序未必与生成顺序一致。
比如:e => e1 - e2 => e1 - e5 - e6 => e3×e4 - e5 - e6 => 5×2 - 6 -1
如果按照生成时从后往前的顺序,先算e2:6-1,再算e1:5×2,最后算e1-e2: 5×2 - 6 -1 ,结果必然是错的;
为了解决此问题,我标准化了表达式的生成规范,即:对于产生的任何二元表达式,都为其在外面加上括号,通过括号,就完整的保留了整个表达式生成过程中各个部分的出现次序,运算的优先级也与生成次序统一到了一起,这样就可以做到生成一个子表达式时,迅速得到其结果并反馈给母表达式使用,且无优先级影响,不会出错。得到一元式或二元式的结果很容易,一级一级向上传递结果,最终结果计算就得到了简化,而试图进行括号匹配,或对原式一点点做拆分、优先级分析是很麻烦的(不过在计算模块中,我们仍然必须实现这一点),这样比产生运算式答案的效率上就大大提升了
C. 使用long而不是int
虽然本程序目标对象是小学生,不过这样随机产生的运算式仍有可能因为通分问题产生数值非常大的分子,这里引用我所在团队一位同学的论断
我们刚才提到了值域的扩大对于最终结果的最大值的影响是十分之大的,其能高达r^8倍。比如下面这个例子,如果我们的值域是20:
( 16'11/15 ÷ 17'4/19 ) × ( 6'1/2 ÷ 12'1/15 )
这里还有一个坑的地方,在于值域的上限。上面提到分子中最高有可能到r^8的水准是有计算依据的。我们设想存在这样一个数字,其分子接近r^2,分母接近r,但是分子与分母互质。设这个数为x,那么
x * x * x * x 完全可以达到r^8的数量级。按这样计算,如果使用int
定义分子分母的类型,那么只要 r 达到 15 ,就可能出现超过 int 型范围的数字,最后导致结果为 负数。
大家可以去参看他的博客(http://www.cnblogs.com/SivilTaram/p/4828591.html),在本次工程项目中,我很多想法都来自于他的启示。
因此,在分数类中,我们需要将分子分母定义为long,也即长整形,这样便可以使得程序支持的值域达到200以上,基本可以满足需求了。
由于Math类的很多方法是针对整形的,我们需要针对长整形重新写一下方法,如Abs取绝对值,Max取最大值,以及计算两个长整形数的GCD算法,这些笔者都整合到MathCore这个静态方法类里了,前面提到用于计算表达是值的calculate()方法自然也是该类的成员
D.计算模块的简化
数字表达式计算式是本项目里面另一个需要投入精力的地方,笔者自己写了一个计算程序,并非最优,但通过采用栈的数据结构,也做到了一定程度上对计算过程的简化(无中缀后缀表达式转换)
算法思路如下:首先针对本体的表达式规范(无负数,但有带分数,仅含四则运算符和括号),通过用正则表达式获得其各个元素,代码如下:
String pattern = "(\\d+'\\d+\\/\\d+)|(\\d+\\/\\d+)|(\\d+)|(\\+)|(-)|(×)|(÷)|(\\()|(\\))";//列举所有可能的操作数或操作符形式
Regex reg = new Regex(pattern);
exp = "(" + exp.Replace("\\s+", "") + ")";//通过加括号,我们可以保证任何计算过程回溯的终点为“(”,这有助于处理形式统一
MatchCollection ele = reg.Matches(exp);
然后通过建栈,逐一压入操作元素,对于不同操作符,作如下处理:
1.如遇到×
÷且后一个元素为操作数的话(即非“(”),取当前栈顶操作数与之运算并将结果压入栈中;
2.如遇到“)”则自后向前(由于栈是后进先出的)运算至栈顶出现“(”并将其取出
(注意考虑 -e1-e2 、-e1+e2的情况,自后向前运算时不仅要取运算符和前一个操作数,当运算为加减法时前一个操作符是否为-号也是需要去处理的)
取出并算出结果后,如前一个运算符(当前栈顶)为×
÷,则此结果与前一个操作数运算后,继续取栈顶,循环判断至前一个运算符为+-或"("时,将最终运算结果压入栈顶结束过程
生成时所加的括号规范作用在这里就体现出来了,对于该算法,计算出最后结果的时候,整个式子必须有至少一对括号才可以。
E. 除法减法有效性处理
本程序设计一大核心思想就在于尽可能的避免冗余性处理,对于除法或减法无效的情况(减法出现负数,除法整除或者分母为0)均可以通过调换操作数(减数和被减数,分子和分母)的位置进行处理,当然,对于操作数相等的情况应尽量避免
关于性能分析,因为VS2012为早期的桌面免费版(社区版?),似乎并没有这样的工具,所以只好借同学的VS生成了一张
S4 项目测试
首先分享程序本体及测试文件,重复性检测程序,在配有的Readme文档中,我作了相应的说明。(下载 密码:7ck5)
测试用命令行参数:(Defauts: -r 10 -n 10000 -o 3)
1.ExpressionOutputer.exe
Time Cost: 00:00:00.1730099
2.ExpressionOutputer.exe -e ./Exercise.txt -a ./Answer.txt (Depend on exercise file)
Time Cost(Defauts Setting): 00:00:05.0092865
3.ExpressionOutputer.exe -e ./Exercise2.txt -a ./Answer2.txt (Depend on exercise file)
Time Cost(Defauts Setting): 00:00:05.1052920
4.ExpressionOutputer.exe -r 1 -n 30
Time Cost: 00:00:00.0150009
5.ExpressionOutputer.exe -r 2 -n 500
Time Cost: 00:00:00.0160009
6.ExpressionOutputer.exe -r 3 -n 10000
Time Cost: 00:00:00.3540203
7.ExpressionOutputer.exe -r 5 -n 20000 -o 4
Time Cost: 00:00:00.4230242
8.ExpressionOutputer.exe -r 10 -n 100000 -i
Time Cost: 00:00:03.2331849
9.ExpressionOutputer.exe -r 10 -n 100000 -o 5 -i
Time Cost: 00:00:04.5312591
10.ExpressionOutputer.exe -r 255 -n 1000000
Time Cost: 00:00:12.8217333
-o与-i是笔者程序引入的两个新参数,均在readme中有所介绍,-o为限制操作符个数,-i为开启冗余表达式输出
存在问题:
1.程序对于输入处理,在报错机制上尚不完善,因此仍需规范输入,对于负数表达式的支持不完全(支持负数结果但不支持负操作数)
2.对于除法,实际上程序允许被除数与除数相等的情况,因此使得低范围下生成题目的数量和速度得到了大幅提升,对于除法表达式中必须为真分数这一规定的意义持疑问态度,依据博文中的规定:
- 真分数:1/2, 1/3, 2/3, 1/4, 1’1/2, …
实际上规定的是带分数,带分数是假分数的另一种表达形式,而假分数允许取值为1,并非特例
3.对于命令行参数的处理尚不完善,还有很多规范参数输入的地方没有做,如取值范围与生成表达式数目之间应当遵循的一定关系
由于时间原因这些问题遗留了下来,还请见谅
S5 收获与体会
通过本次的个人项目练习,我收获了很多有关C#的高级运用技巧,如运算符重载,传出参数,静态方法类,IO流,Diretionary类等等,也许是因为C#的语法特点与JAVA的面向对象思想十分相似,这门语言上手还是蛮快的。在设计项目,进行编程的时候充分考虑用户需求并试图做一些优化,这样的事情在上学期的OO课中已经得到了充分锻炼,因此一些处理方法,如方法正确性证明,前后置条件覆盖性分析,根据用户需求规划各个类的功能部,工厂模式等在软工这样的项目型作业中就能够学以致用。不过个人项目毕竟是个人项目,代码风格,可读性,以及团队协作等方面仍需通过结对项目和团队项目进行锻炼,笔者将会在后续的课程任务中继续努力,逐步达到并尝试超越 软件工程这门课程对于学生在能力上的要求。
以上 by kibbon
【个人项目总结】C#四则运算表达式生成程序的更多相关文章
- 【2015 软件工程 个人项目 PJ1】四则运算题目生成程序
1.开发时间预估 PSP2.1 Personal Software Process Stages Time Planning 计划 · Estimate · 估计这个任务需要多少时间 2day Dev ...
- 数据结构课程设计四则运算表达式求值(C语言版)
本系统为四则运算表达式求值系统,用于带小括号的一定范围内正负数的四则运算标准(中缀)表达式的求值.注意事项: 1.请保证输入的四则表达式的合法性.输入的中缀表达式中只能含有英文符号"+ ...
- Demo005 小学四则运算自动生成程序
目录 小学四则运算自动生成程序 0.传送门 1.题目要求 2.功能实现 2.1 总体设计 2.2 用户欢迎界面 2.3 用户功能界面 2.4 屏幕输出 2.5 文本输出 2.6 获取时间 2.7 用户 ...
- sql server编写简洁四则运算表达式脚本实现计算批次功能(C#等其它编程语言也能直接用此通用表达式)
问题: 在数据库编程开发中,有时会遇到数据量比较大的情况,如果直接大批量进行添加数据.修改数据.删除数据,就会是比较大的事务,事务日志也比较大,耗时久的话会对正常操作造成一定的阻塞.虽不至于达到删库跑 ...
- WUSTOJ 1208: 计算整数四则运算表达式的结果(Java)
1208: 计算整数四则运算表达式的结果 参考资料 数据结构(C语言版)严蔚敏 吴伟民 编著----表达式求值 题目 简单四则运算.更多内容点击标题. 保证表达式合法. 运算符只包含:加(+),减 ...
- 高级软件工程2017第3次作业——结对项目:四则运算题目生成程序(基于GUI)
Deadline:2017-10-11(周三)21:00pm (注:以下内容参考集大作业 ) 前言 想过和别人一起探索世界吗?多么希望,遇到困难时,有人能一起探讨:想要懈怠时,有人推你一把:当你专注于 ...
- 第六次作业——利用MFC实现计算器图形界面以及简单四则运算表达式批处理
参考资料: 1.MFC响应键盘 2.计算器实例 3.MFC文件对话框 4.MFCUpdateData()函数的使用 5.MFC教程 6.wi ...
- 个人作业1——四则运算题目生成程序(基于java)
项目代码: https://git.coding.net/YJh_/11.git 题目要求: 除了整数以外,还要支持真分数的四则运算,真分数的运算,例如:/ + / = / 运算符为 +, −, ×, ...
- 【坑】关于使用 maven 创建 web 项目以后,el 表达式不被识别的解决方法
问题描述: 在学习 Ajax 的时候,使用资源路径,博主本着不要硬编码,局使用 el 表达式进行读取项目名,然后发现 el 表达式没有被识别,而是当做字符串 ${pageContext.request ...
随机推荐
- httpd服务器的真实ip获取难题
web服务器httpd中想要获取真正的ip是个难度,我们先要在配置文件中定义错误日志的格式:如下所示: 然后获取ip 上面三条输出日志中,第一条是直接访问http://172.16.213.157/i ...
- 获取请求Url
/// <summary> /// 获取请求Url /// 例:http://www.text:1234 /// </summary> /// <returns>& ...
- js如何获得局部变量的值
方法一: <script> var a; //全局变量 function test(){ var b=20; //局部变量 return b; //返回局部变量的值 }; a=test ...
- QT+VS2013 1配置和安装
相关参考:http://www.cnblogs.com/ranjiewen/p/5318768.html 1下载 VS2013 微软官网查找 https://www.visualstudio.com ...
- YOLO2(1)配置安装win10+openvc2413+VS2013 简单测试官例
参考官网 https://github.com/AlexeyAB/darknet#how-to-compile-on-windows https://github.com/AlexeyAB/darkn ...
- Qt+Qgis二次开发:地理实体抽象
1 概述 地理实体抽象是指点.线.面及其组合而成的,用于描述实际地物的数据结构. 其中包含几何实体和属性数据. GIS中进行几何操作,以各种实体类为基础进行操作. 在OGC中,地理实体可以由WKT表 ...
- parallel方法 异步并行执行
Promise提供了all方法, 但是状态只有2种, 第一种是所有promise实例都成功则返回值组成一个数组,传递给p的回调函数: 第二种是有一个实例被rejected,状态就变成rejected, ...
- eclipse 如何引入本地dtd
一.首先修改xml的打开方式为:XML editor 1.菜单:Window -> Preferences ->General -> Editors -> File Asso ...
- 初识Identity(二)
本文参考了[ASP.NET Identity系列教程(一)]ASP.NET Identity入门 一.Identity使用前准备项目 本文创建了一个名称为Users的项目.在创建过程中选择了“Empt ...
- 介绍一个axios调试好用的工具:axios-mock-adapter
上一篇文章中写到用promise时应注意的问题,这一篇文章继续介绍一个可以和axios库配合的好工具: axios-mock-adapter.axios-mock-adapter可以用来拦截http请 ...