软件工程第3次作业
【https://git.coding.net/yu_mai/f4.git】
三、 结对的伙伴
买倩玉:【https://www.cnblogs.com/maiqyBlog/】
编程思路:我们一开始决定使用C/C++,但是由于对C++掌握基础不够牢,最后决定使用python语言。在作业一中我也尝试使用了python语言但是没有写出来,所以从上次到现在也一直再学习python语言。
三、 结对的伙伴
买倩玉:【https://www.cnblogs.com/maiqyBlog/】
编程思路:我们一开始决定使用C/C++,但是由于对C++掌握基础不够牢,最后决定使用python语言。在作业一中我也尝试使用了python语言但是没有写出来,所以从上次到现在也一直再学习python语言。
这次通过结对,我们顺利的完成了作业,也让我应用了所学到的新知识,对python语言的理解加深了许多,很开心能把学到的东西真正运用起来。(也算是一点点小进步吧o(* ̄▽ ̄*)ブ)
通过分析和讨论,我们列出了以下几个步骤:
(1)随机算式的生成(随机函数的应用)
(2)随机算式的运算(中缀表达式转换成后缀表达式,逆波兰表达式的应用)
(3)基于控制台命令行参数
(4)按照需求调整输出格式
四、代码结构设计
f4类 | 基于控制台的命令行输入,调用其它函数 |
createProblem类 | 生成随机表达式 |
calculate类 | 将中缀表达式转换成后缀表达式 |
五、重点和难点
(1)随机算式的生成(功能一)
我们调用了随机函数random来生成随机表达式。对于除数为0的情况,则将除数加上一个1-9之间的随机数,降低了算法复杂度,提高了程序运行效率。
arithList = [] for i in range(7): if i % 2 == 0: arithNum = random.randint(0, 9) if len(arithList) > 1: while (arithNum == 0) and arithList[len(arithList) - 1] == '/': arithNum += random.randint(1,9) arithList.append(arithNum) else: arithList.append(create.createOperator(self))
(2)随机算式的生成(功能二和功能三)
功能二、三里的运算符加上了括号。对于表达式,括号必须是成对出现,并且加在最外面的括号没有意义,经过讨论,我的结对搭档提出了如下处理方式:
def createBrackets(self, exp, operatorNum): expression = [] if exp: expLength = len(exp) leftPosition = random.randint(0, int(operatorNum / 2)) rightPosition = random.randint(leftPosition + 1, int(operatorNum / 2) + 1) mark = -1 for i in range(expLength): if exp[i] in ['+', '-', '*', '/']: expression.append(exp[i]) else: mark += 1 if mark == leftPosition: expression.append('(') expression.append(exp[i]) elif mark == rightPosition: expression.append(exp[i]) expression.append(')') else: expression.append(exp[i]) # 如果生成的括号表达式位置为第一个运算数的左侧与最后一个运算数的右侧,则重新生成 if expression[0] == '(' and expression[-1] == ')': expression = self.createBrackets(exp, 4) return expression return expression
(3)无限小数的判断
对于无限小数的判断,查阅资料可知结论:将分数化为最简分数
后,分母的全部因数
(除去1和其自身)没有为2或5以外的数,则该分数就不是无限循环小数
;否则为无限循环小数。
所以一开始我是这样想的:利用python的fractions模块进行分数运算,然后将最终结果(分数)约分,利用上述结论判断是否是无限循环小数然后再进行输出。但是这个想法没有实现,我们最后直接暴力判断结果长度是否>20,如果>20,就使用round函数来保留小数点后三位小数(四舍五入)。
if len((str)(ansR)) > 20 : if (float(ans1) == round((float)(ansR), 3)): print("回答正确。") rightAnswer += 1 else: print("回答错误,正确答案是", round(ansR, 3)) wrongAnswer += 1 else: if (float(ans1) == float(ansR)): print("回答正确。") rightAnswer += 1 else: print("回答错误,正确答案是", ansR) wrongAnswer += 1
还有一点是由于python语言不需要对变量进行数据类型的定义,所以在运算上我们使用了Decimal模块来进行精确浮点数运算。
def operate(self, number1, number2, operator): number1 = Decimal(number1) number2 = Decimal(number2) if operator == '+': return number2 + number1 if operator == '-': return number2 - number1 if operator == '*': return number2 * number1 if operator == '/': return number2 / number1
(4)中缀表达式转换成后缀表达式(即逆波兰表达式)
大二学数据结构的时候曾经做过逆波兰表达式的OJ题,现在又捡起来利用了一下。加上网上的相关代码和资料,我们解决了这一难点。
查阅资料可知:逆波兰表达式处理算式具有高效性,因为逆波兰表达式在计算机中只用两种简单操作,入栈和出栈,而且这两个操作就可以搞定任何普通表达式(仅包含:+-*/和()的表达式)的运算。操作规律:如果当前字符为变量或者为数字,则压栈;如果是运算符,则将栈顶两个元素弹出作相应运算,结果再入栈,最后当表达式扫描完后,栈里的就是结果。例如:a+(b-c)*d ------> abc-d*+ 编译器在处理时候按照从左至右的顺序读取逆波兰表达式,遇到a,b,c等运算对象直接压入堆栈,遇到运算符就从堆栈提取后进的两个对象进行计算。
def generatePostfixEpression(self, expression): # 去除表达式空格 expression = expression.replace(' ', '') # 创建运算符栈和表达式栈 operatorStack = list() expressionStack = list() for element in expression: if element in self.operators: if not operatorStack: operatorStack.append(element) else: if element == ')': for top in operatorStack[::-1]: if top != '(': expressionStack.append(top) operatorStack.pop() else: operatorStack.pop() break else: for top in operatorStack[::-1]: # 左括号只有遇到右括号才出栈 if self.priority[top] >= self.priority[element] and top != '(': expressionStack.append(top) operatorStack.pop() else: operatorStack.append(element) break if not operatorStack: operatorStack.append(element) else: expressionStack.append(element) # 将操作符栈中的操作符入到表达式栈中 for i in range(len(operatorStack)): expressionStack.append(operatorStack.pop()) return expressionStack def calculate(self, expression): # 后缀表达式生成 expressionResult = self.generatePostfixEpression(expression) calcalateStack = list() # 遍历后缀表达式 for element in expressionResult: if element not in self.operators: calcalateStack.append(element) else: number1 = calcalateStack.pop() number2 = calcalateStack.pop() result = self.operate(number1, number2, element) calcalateStack.append(result) return calcalateStack[0]
这里是浪费时间最多的过程之一了……数据结构和算法真的是编程的核心和基础,作为一个计算机系的同学,更不能把它们“弃之脑后”,应该时常利用、翻阅、温故知新。
(5)基于控制台命令行参数的输入
上次用java写程序的时候就没有写好这个……这次利用python,一开始用了OptionParser,但是没有搞清楚怎么和输入联系起来……
一个使用失败的例子:
parser.add_option("-n", type="int", help='需要输入的题目个数(无括号)') parser.add_option("-c", type="int", help='需要输入的题目个数(有括号)')
所以后来直接使用了字符串输入以及按空格切割的方式,但是我觉得上述方法的用户体验很高,因为有help参数,可以让不明白的用户说明清楚,希望这次作业以后能够再加完善,学会这个函数的用法。
controlIn = input() arg = controlIn.split( ) # print(arg) # print(len(arg)) # print(arg[0]) # print(arg[1]) flag = 0 if len(arg) == 2: if(arg[0] == '-n'): flag = 1 elif(arg[0] == '-c'): flag = 2 elif arg[0] == '-c' and arg[2] == '-f': flag = 3
更正:经过仔细审题,我们发现如果使用上述方法到控制台里是不正确的,必须要先进入f4.exe才能输入。举个例子,类似:f4 -n 3这种作业要求的输入方式,按照上面的方法必须要 f4所在路径+f4,回车,f4 -n 3才可以实现功能。但是题目仅要求 类似:f4所在路径+f4 -n 3这种形式。所以必须使用python提供的OptionParser,但经过查阅,发现python3对OptionParser已经停止更新,一个更好的、类似的、可以替代它的库叫做argparse。故经过学习,我做出了如下更改:
其中在main函数里:
import argparse parser = argparse.ArgumentParser() parser.add_argument("-n", "--num") parser.add_argument("-c", "--cin") parser.add_argument("-f", "--fil") args = parser.parse_args() if args.num != None : f4().flag1(args.num) if args.cin != None : if args.fil == None : f4().flag2(args.cin) else: f4().flag3(args.cin, args.fil)
在f4类里,分别定义了flag1、flag2、flag3函数分别实现功能一、功能二、功能三。用flag1举例如下:
def flag1(self, num1): cl = calculator() ca1 = create() try: num1 = int(num1) rightAnswer = 0 wrongAnswer = 0 if isinstance(num1, int): if num1 <= 0: print("题目数量必须是 正整数。") else: for i in range(num1): expList = ca1.createArithmetic(0) print(expList) print('?', end='') ansR = cl.calculate(expList) #print(ansR) ans1 = input() if len((str)(ansR)) > 20 : if (float(ans1) == round((float)(ansR), 3)): print("回答正确。") rightAnswer += 1 else: print("回答错误,正确答案是", round(ansR, 3)) wrongAnswer += 1 else: if (float(ans1) == float(ansR)): print("回答正确。") rightAnswer += 1 else: print("回答错误,正确答案是", ansR) wrongAnswer += 1 print("总共" , num1 , "道题,你答对" , rightAnswer , "道题。") else: print("题目数量必须是 正整数。") except: print("题目数量必须是 正整数。")
六、 运行结果截图
(1)控制台截图
功能一:
功能二:
功能三:
(2)CMD运行截图
功能一:
功能二:
功能三:
七、 个人总结
我们在结对编程之前读了《构建之法》第四章两人合作这一部分知识,从领航员和驾驶员两个角色定位出发,为我们两个人的结对编程打下了基础。我觉得我们两个人没有绝对的领航员、驾驶员之分,更多的是相互领航、相互驾驶,达到了一个很好的沟通、合作效果。同时,在对方的身上也能学到很多东西。代码复审的时候,自己检查自己的真的查不出错,查别人的却很迅速,这可能就是“当局者迷,旁观者清”的另一种解释吧。
我们在结对编程之前读了《构建之法》第四章两人合作这一部分知识,从领航员和驾驶员两个角色定位出发,为我们两个人的结对编程打下了基础。我觉得我们两个人没有绝对的领航员、驾驶员之分,更多的是相互领航、相互驾驶,达到了一个很好的沟通、合作效果。同时,在对方的身上也能学到很多东西。代码复审的时候,自己检查自己的真的查不出错,查别人的却很迅速,这可能就是“当局者迷,旁观者清”的另一种解释吧。
本次作业中,我主要负责的部分是随机算式(功能一)的生成、格式输出的调整、无限循环小数的判断以及中缀表达式转换成后缀表达式,我的搭档负责的部分是随机算式(功能二、三)。
个人来讲还是比较喜欢结对编程的,在编程中收获了知识,提升了友谊,对编程、代码之美的感受更近了一部。和一起工作的小伙伴快乐的编程,有问题一起想,这就是一种编程之美吧o(* ̄▽ ̄*)ブ。
八、 结对编程照片
九、C/C++尝试记录
有点羞愧,C是自己接触的第一门语言,C++接触比较少但是作为算法、数据结构课程的常用语言也应该掌握。所以还是决定记录一下自己未完成的代码。希望有一天能够真正实现。
#include <iostream> #include <sstream> #include <cstdlib> #include <cstring> #include <ctime> #include <fstream> #define maxsize 1024 using namespace std; //获取运算符 char getOp() { char op[4] = {'+', '-', '*', '/'}; return op[rand()%(strlen(op))]; } //获取运算数 float getNumber(int maxNum) { return rand()%maxNum+1; } //判断答案正误 void judgeAnswer(int s1, int s2, float a[2]) { if (s2 == s1) { cout << "回答正确。" << endl; a[0]++; } else { cout << "回答错误,正确答案是" << s1 << endl; a[1]++; } } //获取随机算式 string getExp() { int n = 3; const int MAX = 10; int expNum, len; string str = ""; string temp; for(int i = 0; i < 7; i++) { if((i % 2) == 0) { expNum = getNumber(MAX); len = str.size(); while(str[len - 1] == '/' && expNum == 0 && len > 0) expNum += 1; stringstream str1; str1 << expNum; str1 >> temp; str += temp; } else { str += getOp(); } } return str; } typedef struct { float data[maxsize]; int top; }stack1; typedef struct { char data[maxsize]; int top; }stack2; void initStack1(stack1 *s) { s = (stack1*)malloc(sizeof(stack1)); s->top = -1; } int push1(stack1 *s, float ch) { if (s->top == maxsize - 1) return 0; else { s->top++; s->data[s->top] = ch; return 1; } } int pop1(stack1 *s, float ch) { if (s->top == -1) printf("栈上溢出!\n"); else ch = s->data[s->top]; s->top--; return 1; } void initStack2(stack2 *s) { s = (stack2*)malloc(sizeof(stack2)); s->top = -1; } int push2(stack2 *s, char ch) { if (s->top == maxsize - 1) return 0; else { s->top++; s->data[s->top] = ch; return 1; } } int pop2(stack2 *s, char ch) { if (s->top == -1) printf("栈上溢出!\n"); else ch = s->data[s->top]; s->top--; return 1; } int isOp(char ch) { switch (ch) { case'+': case'-': case'*': case'/': case'(': case')': case'#': return 1; default: return 0; } } char prior[7][7] = { // '+' '-' '*' '/' '(' ')' '#' /*'+'*/{ '>', '>', '<', '<', '<', '>', '>' }, /*'-'*/{ '>', '>', '<', '<', '<', '>', '>' }, /*'*'*/{ '>', '>', '>', '>', '<', '>', '>' }, /*'/'*/{ '>', '>', '>', '>', '<', '>', '>' }, /*'('*/{ '<', '<', '<', '<', '<', '=', '< '}, /*')'*/{ '>', '>', '>', '>', ' ', '>', '>' }, /*'#'*/{ '<', '<', '<', '<', '<', '> ', '=' }, }; int opId(char op1) { switch (op1) { case'+':return 0; case'-':return 1; case'*':return 2; case'/':return 3; case'(':return 4; case')':return 5; case'#':return 6; default:return -123456; } } char precede(char op1, char op2) { int a, b; a = opId(op1); b = opId(op2); return(prior[a][b]); } float operation(float a, char op, float b) { switch (op) { case '+': return b + a; case '-': return b - a; case '*': return b * a; case '/': return b / a; default: return -123456; } } void createExpression(const char *exp) { if (exp == NULL) { exit(1); } string str2 = getExp(); cout<<str2; exp = str2.data(); } void transmitExpression(char *exp, char postexp[]) { stack2 FZ; initStack2(&FZ); int i = 0; char x; FZ.top = -1; push2(&FZ, '#'); FZ.data[FZ.top] = '#'; while (*exp != '\0') { if (!isOp(*exp)) { while (*exp >= '0'&&*exp <= '9') { postexp[i++] = *exp; exp++; } postexp[i++] = '#'; } else switch (precede(FZ.data[FZ.top], *exp)) { case'<': push2(&FZ, *exp); exp++; break; case'=': x = FZ.data[FZ.top]; pop2(&FZ, x); exp++; break; case'>': postexp[i++] = FZ.data[FZ.top]; x = FZ.data[FZ.top]; pop2(&FZ, x); break; } } while (FZ.data[FZ.top] != '#') { postexp[i++] = FZ.data[FZ.top]; x = FZ.data[FZ.top]; pop2(&FZ, x); } postexp[i] = '\0'; } float evaluateExpression(char *postexp) { stack1 SZ; initStack1(&SZ); float a, b, d; SZ.top = -1; while (*postexp != '\0') { switch (*postexp) { case'+': case'-': case'*': case'/': a = SZ.data[SZ.top]; pop1(&SZ, a); b = SZ.data[SZ.top]; pop1(&SZ, b); push1(&SZ, operation(a, *postexp, b)); break; default: d = 0; while (*postexp >= '0'&&*postexp <= '9') { d = 10 * d + *postexp - '0'; postexp++; } push1(&SZ, d); SZ.data[SZ.top] = d; break; } postexp++; } return(SZ.data[SZ.top]); } int err(char *exp) { int i = 0; while (exp[i] != '\0') { if ( ((exp[i] == '+' || exp[i] == '-' || exp[i] == '*' || exp[i] == '/') && (exp[i + 1] == ')')) || ((exp[i] == '+' || exp[i] == '-' || exp[i] == '*' || exp[i] == '/') && (exp[i - 1] == '(')) || (exp[i] == ')'&&exp[i + 1] == '(') || (exp[i] == '('&&exp[i + 1] == ')') || ((exp[i] == ')') && exp[i + 1] >= '0'&&exp[i + 1] <= '9') || (exp[i] >= '0'&&exp[i] <= '9'&&exp[i + 1] == '(') || (exp[0] == '+' || exp[0] == '-' || exp[0] == '*' || exp[0] == '/' || exp[0] == ')') || ((exp[i] == '+' || exp[i] == '-' || exp[i] == '*' || exp[i] == '/') && (exp[i + 1] == '+' || exp[i + 1] == '-' || exp[i + 1] == '*' || exp[i + 1] == '/')) || (exp[i] == '/'&&exp[i + 1] == '0') ) return 1; else if (exp[i] == '\0') return 0; i++; } return 0; } int main() { char c; char exp[maxsize]; char postexp[maxsize] = { 0 }; while (1) { sr : int n; scanf("%d",&n); int i = 0; for(int j = 0; j < n; j++) { string str2 = getExp(); for(i = 0;i < str2.length();i++) exp[i] = str2[i]; exp[i] = '\0'; if (!err(exp)) { transmitExpression(exp, postexp); printf("%s\n", exp); float numnum; scanf("%g",&numnum); if(numnum==evaluateExpression(postexp)) cout<<"正确"<<endl; else printf("=%g\n", evaluateExpression(postexp)); } else if (err(exp)) { printf("您输入的表达式有误!\n"); goto sr;//goto语句在循环体里进行跳转 } } } system("pause"); return 0; }