要求0 作业地址: https://edu.cnblogs.com/campus/nenu/2016CS/homework/2266
要求1 git仓库地址:https://git.coding.net/Jingr98/f4.git
要求2
一.编程队友及博客地址:张小蕾 https://www.cnblogs.com/zhangxlBlog/p/9926518.html
二.1.解题思路
首先三个功能对应三个表达式生成函数。func1()实现功能1的时候,分奇偶情况利用rand()随机生成操作数和运算符(将4个运算符存入数组中,rand() % 4利用数组下标实现随机)。对于分母为0的特殊情况要进行检验,分母为0时换数(再次生成随机数)直到表达式合法。在func1()的基础上,func2生成的表达式中随机加入小数和小括号,小数由rand()/(double)(RAND_MAX/100)产生。最先遇到的困难就是加括号这里了,开始只考虑了单层括号的情况,因为只有4个运算数,所以单层情况的话对应了6种情况,可以利用rand() % 6随机加括号,但是仔细看了样例发现,还有多层括号嵌套的情况。。。感觉有些复杂,这个地方就去参考学习了一下别人的代码。
然后就是利用压栈的思想来计算四则运算表达式,将输入的中缀表达式用栈转化为后缀表达式,再根据后缀表达式用栈实现运算。先给定两个栈,一个用来存放数字、一个用来存放运算符,计算时把表达式拆分成一个个的数字或运算符或括号,然后从左到右遍历表达式中的每个数字和符号,遇到数字则输出,遇到运算符号,则要比较其与栈顶元素的优先级,若优先级低于栈顶元素,则将栈顶元素依次输出并将该元素入栈;若优先级高于栈顶元素则入栈;若是遇到左括号,入栈,入栈后的左括号优先级低于+ - * / ;若是遇到右括号,则将栈顶元素依次输出,直到遇到左括号,遍历结束后得到后缀表达式。最后利用栈遍历后缀表达式,遍历结束后,最终结果就在栈顶。
2.重点难点
功能一:正确结果的输出格式,尤其是保留小数的位数,不知道怎么将无限小数区分出来。。
功能二:小括号的随机生成,要考虑括号位置的合法性以及左右括号成对出现的问题
功能三:判断表达式是否是重复的(考虑交换律、结合律、分配律变换),感觉这是最困难的
3.编程收获
在这次编程过程中,遇到了很多细节性的问题,有一种每次细想一遍就会发现一个小问题的感觉。在解决问题的过程中,又重新复习了数据结构和算法程序设计的相关知识,用到了栈的存储、中缀表达式与后缀表达式的转化等。最大的感受就是在上网查阅资料的时候,看了挺多代码,了解借鉴了别人的编程思想,get到了很多用C++编程的小技巧。
4.重要代码片段
(1)用point()函数处理输出小数点后面位数的问题。因为无法将无限小数区分开来(考虑到位数很长的有限小数),所以就简单地将用以下方式对结果进行处理,若小数点后面的位数大于3,则按无限小数处理。
/**
*函数名:point()
*函数功能:判断小数点后面的位数是否超过3
*param:double num
*/
int point(double num)
{
int i,f=0;
num *= 1000;
if(num - (int)num > eps)
f = 1;
return f;
}
(2)中缀表达式转后缀表达式,运算符号入栈的过程中利用power()函数判断优先级
/**
*函数名:power()
*函数功能:将'+'、'-'、'*'、'/'、'('、')'赋予优先级
*param:char c
*/
int power(char c)
{
if (c == '-' ||c == '+')
return 1;
else if (c == '*' || c == '/')
return 2;
else if (c == '(' || c == ')')
return 0;
else
return -1;
}
(3)work()函数负责计算四则运算表达式,最终结果存储在栈顶
/**
*函数名:work()
*函数功能:利用双栈将中缀表达式转换成后缀表达式,遍历后缀表达式得到结果
*param:string s
*/
double work(string s )
{
stack<double> sNum; //存储操作数的栈
stack<char> sOp; //存储运算符的栈
int i = 0, flag = 1;
char c;
double x, y;
sOp.push('\0');
c = s[i];
while (flag)
{
if (c >= '0' && c <= '9' || c == '.') {
sNum.push(TransToNum(s, i));
}
else if (c == '\0' && sOp.top() == '\0') {
flag = 0;
}
else if (c == '(' || (power(c) > power(sOp.top()))) {
sOp.push(c);
i++;
}
else if (c == ')'&& sOp.top() == '(') { //遇到一对括号,先将括号内算式进行计算
sOp.pop();
i++;
}
else if (power(c) <= power(sOp.top())) {
x = sNum.top();
sNum.pop();
if(sNum.empty())
y = 0;
else {
y = sNum.top();
sNum.pop();
}
c = sOp.top();
if( c == '/' && fabs( x ) < eps ){
int n = rand() % 3;
c = op[n];
}
sOp.pop();
switch (c)
{
case '+':y = x + y; break;
case '-':y = y - x; break;
case '*':y = x * y; break;
case '/':y = y / x; break;
}
sNum.push(y);
}
c = s[i];
}
return sNum.top();
}
(4)func1()对应生成功能1的表达式
/**
*函数名:func1()
*函数功能:支持整数和不含括号的四则运算且表达式可以重复
*param:
*/
void func1()
{
for(int i = 0 ; i < 7 ; i++ ){
if( (i % 2) == 0 ){
int n = rand() % 10; //将生成的随机数对10取余,即获得了范围0~9的一个随机数
int len = s.size();
while( len > 0 && n == 0 && s[len - 1] == '/' )
n = rand() % 10;
stringstream ss;
ss << n; //将浮点数转换成字符串
string tmp;
ss >> tmp; //将字符串的内容放到tmp中
s += tmp;
} else {
int n = rand() % 4; ////将生成的随机数对4取余,即获得了范围0~3的一个随机数
s += op[n];
}
}
}
(5)func2()对应生成功能2的表达式
/**
*函数名:func2()
*函数功能:支持小数和含小括号的四则运算且表达式可以重复
*param:
*/
int func2()
{
int ans1 = 0 , ans2 = 0;
double a[4];
int k = 0;
for(int i = 0 ; i < 7 ; i++ ){
string tmp;
if( (i % 2) == 0 ){
int n = rand() % 10;
if( (n % 3) == 0 && n > 0 ){
double tmp_num =rand()/(double)(RAND_MAX/100);//随机小数的生成
stringstream ss;
ss << tmp_num;
ss >> tmp;
a[k++] = tmp_num;
} else {
int len = s.size();
while( len > 0 && n == 0 && s[len - 1] == '/' ) //判断分母不为0
n = rand() % 10;
stringstream ss;
ss << n;
ss >> tmp;
a[k++] = n*1.0;
}
//小括号的随机生成
if( n < 3 && i < 6 && i > 0 ){
s += "(";
ans1++;
}
s += tmp;
if( n > 6 && i > 0 && i < 6){
if( ans1 > 0 )
ans1--;
else
ans2++;
s += ")";
}
} else {
int n = rand() % 4;
s += op[n];
}
}
sort( a , a + 4 );
pdd current = mk( a[0] , a[1] , a[2] , a[3] );
if( stPdd.find(current) != stPdd.end() ){
tmp++;
return 1;
}
stPdd.insert( current );
string lft , rht;
//保证小括号成对出现
for(int i = 0 ; i < ans2 ; i++ )
lft += "(";
for(int i = 0 ; i < ans1 ; i++ )
rht += ")";
s = lft + s + rht;
return 0;
}
三.结对编程的体会
结对编程确实提高了我们的编程效率,节约了很多不必要浪费的时间。首先是算法程序的设计上,可以和自己的小伙伴一起讨论,交流彼此不同的解题思路,不断融合最后决定出一个更加准确且更加高效的设计思路。因为两个人在探讨的过程中已经发现并解决了方案中很多不合理的问题,这就减少了后续无用代码的编写量。其次就是编写代码的过程中,正如《构建之法》中所说,一个人充当驾驶员,另一个人充当领航员的角色,一旦出现编写错误,身边就会有人及时提醒,相较于单独编程的过程,结对编程真的减少了很多调bug的时间,甚至很多地方的代码都是一次通过,极大程度上提高了我们的编程效率。最后呢,我想说结对编程的体验还是很愉悦的,和我的小伙伴按阶段进行身份角色的互换,两个人轮流编程,写代码不会很累,并且在编写的过程中一起商讨,相互学习,感觉也不像以往那样枯燥。
有一点需要改进的地方就是,两个人对一个问题较真儿时容易跑偏,有点浪费时间。相信在以后不断磨合的过程中有所进步。
四.3项花费时间最长、较大收获的事件
1.设计思路的确定
俗话说“万事开头难”,由于是第一次这样结对编程,我俩在起步阶段就耗费了很多时间。原因是程序设计思路的不同,一个人急于解决表达式计算的问题,一个人想先解决表达式随机生成的问题,两个人对算法设计的整体思路出现了分歧,最总商讨下,我们确定了当下的解决方案,相互借鉴了彼此思路中的优点。
2.功能2的实现
在实现功能2时,最耗时的地方就是给表达式加括号了。原本我们按单层括号问题处理的,这样问题就简单多了,但是和小伙伴审视样例时发现可以出现括号嵌套的情况,这种既要考虑括号位置的合法性又要保证左右括号成对儿出现的问题,我俩觉得挺复杂,有点无从下手,最后就去网上查阅相关资料,读了很多别人的代码,这个学习过程挺长时间的。
3.功能3的实现
在实现功能3的时候,最最复杂的就是表达式的重复判断了,还要考虑交换律、结合律、分配律啥的。先开始我们也商讨了自己的解决方案,发现有很多漏洞,实在不知道怎么解决,最后我们就去请教别人,参考了其他同学的解决思路,即只判断数字是否重复,将数字存储到set集里,利用sort函数排序在比较是否有重复,以实现这一功能。
五.照片