zoukankan      html  css  js  c++  java
  • 结对项目——四则运算题目生成器

    一、GitHub项目地址 :

    https://github.com/OurPrograming/Exercises

    结对项目成员:软三 陈纪禧(3118005043)、软三 程伟杰(3118005045)

    二、PSP预计时间:

    PSP2.1

    Personal Software Process Stages

    预估耗时(分钟)

    实际耗时(分钟)

    Planning

    计划

     60

    · Estimate

    · 估计这个任务需要多少时间

     60

    Development

    开发

     1680

    · Analysis

    · 需求分析 (包括学习新技术)

     120

    · Design Spec

    · 生成设计文档

     30

    · Design Review

    · 设计复审 (和同事审核设计文档)

     10

    · Coding Standard

    · 代码规范 (为目前的开发制定合适的规范)

     20

    · Design

    · 具体设计

     120

    · Coding

    · 具体编码

     1200

    · Code Review

    · 代码复审

     60

    · Test

    · 测试(自我测试,修改代码,提交修改)

     120

    Reporting

    报告

     150

    · Test Report

    · 测试报告

     60

    · Size Measurement

    · 计算工作量

     30

    · Postmortem & Process Improvement Plan

    · 事后总结, 并提出过程改进计划

     60

    合计

     1890

    三、效能分析:

     

     分析:由效能分析易知,判重函数占用程序时间最多,后续优化可从这方面入手。

    四、设计实现过程:

    结构设计:

            设计6个类,不同类实现不同功能,说明如下:

                                                              Parameter  //处理命令行中传来的参数

                                                              Generate    //生成表达式

                                                              Statistics    //统计类,对于给定题目答案的对错进行统计

                                                              BinaryTree //二叉树类

                                                              Calculator  //计算类

                                                              Fraction     //分数类,所有运算数都是此类型

    类与函数的调用关系图:(注:调用→被调用)

    五、部分关键代码设计与说明:

    Parameter  //处理命令行中传来的参数

    表达式查重:

     1 void Parameter::generateExercises(int num, int range)
     2 {
     3     if (num == 0 || range == 0)
     4         return;
     5     
     6     //打开文件
     7     exercises.open("Exercises.txt", std::ios::out | std::ios::trunc);
     8     answers.open("Answers.txt", std::ios::out | std::ios::trunc);
     9     if (!exercises.is_open() || !answers.is_open())
    10         return;
    11     
    12     //每生成一个表达式都与其他元素比较,不重复就入组
    13     vector<BinaryTreeNode *> exe;            //用于存放表达式树
    14     exe.reserve(num);                        //预留空间避免过多扩容
    15 
    16     vector<BinaryTreeNode *>::iterator ite;
    17     Generate *generate = generate = new Generate(range);
    18     BinaryTree *tree = nullptr;
    19     bool flag = false;                    //标志是否重复
    20 
    21     for (int i = 0; i < num; i++)            //生成num个
    22     {
    23         do
    24         {
    25             tree = generate->genExercise();
    26             for (ite = exe.begin(); ite != exe.end(); ite++)
    27             {
    28                 flag = BinaryTree::compare(tree->pRoot, *ite);
    29                 if (flag == true)        //匹配到重复就跳出判断,避免标志被更改
    30                 {
    31                     //重复就删除生成的树
    32                     delete tree;
    33                     tree = nullptr;
    34                     break;
    35                 }
    36             }            
    37         } while (flag == true);
    38 
    39         if (flag == false)        //未重复就push
    40         {
    41             exe.push_back(tree->pRoot);
    42         }
    43     }
    44 
    45     for (int i = 0; i < (int)exe.size(); i++)
    46     {
    47         string strExercises = generate->getExercise(exe.at(i));
    48         exercises << i + 1 << "." << strExercises.c_str() << endl;
    49         
    50         //转成计算器计算结果避免出错
    51         BinaryTreeNode *result = Calculator::toTree(Calculator::toReversePolish(strExercises));
    52         string strAnswers = Calculator::calcResult(result).display();
    53         answers << i + 1 << "." << strAnswers.c_str() << endl;
    54     }
    55 
    56     cout << "题目与答案已写入到文件" << endl;
    57 
    58     delete generate;
    59     //关闭文件
    60     exercises.close();
    61     answers.close();
    62 }

    Generate类    //生成表达式

    表达式建树:

     1 BinaryTree * Generate::genExercise()
     2 {
     3     //3 --> 2 --> 2
     4     //     -> 3 --> 1
     5     
     6     //运算符个数
     7     std::uniform_int_distribution<int> opeCount(1, 3);
     8     int opeNum = opeCount(mt);
     9 
    10     //先新建出一颗最小树
    11     BinaryTreeNode *left = new BinaryTreeNode(getFraction().display());
    12     BinaryTreeNode *right = new BinaryTreeNode(getFraction().display());
    13     string *data = new string(1, getOperator());
    14     BinaryTreeNode *root = new BinaryTreeNode(*data, left, right);
    15     standardization(root);
    16     opeNum--;
    17 
    18     if (opeNum == 0)        //如果运算符只有一个直接返回
    19     {
    20         BinaryTree *tree = new BinaryTree(root);
    21         return tree;
    22     }
    23 
    24     //用于二选一的随机数
    25     std::uniform_int_distribution<int> randBool(0, 1);
    26 
    27     //选择下一步是2或3
    28     if (randBool(mt) == 0 && opeNum == 2)    //再生成一个最小树并连接
    29     {
    30         left = new BinaryTreeNode(getFraction().display());
    31         right = new BinaryTreeNode(getFraction().display());
    32         data = new string(1, getOperator());
    33         BinaryTreeNode *tempRoot = new BinaryTreeNode(*data, left, right);
    34         standardization(tempRoot);
    35         opeNum--;
    36 
    37         //选择左右顺序
    38         if (randBool(mt) == 0)    //新树在左
    39         {
    40             left = tempRoot;
    41             right = root;
    42         }
    43         else
    44         {
    45             left = root;
    46             right = tempRoot;
    47         }
    48         //连接
    49         data = new string(1, getOperator());
    50         root = new BinaryTreeNode(*data, left, right);
    51         standardization(root);
    52         opeNum--;
    53 
    54         BinaryTree *tree = new BinaryTree(root);
    55         return tree;
    56     }
    57     else        //往上插
    58     {
    59         do
    60         {    
    61             //二选一 左上or右上
    62             if (randBool(mt) == 0)    //左上
    63             {
    64                 left = new BinaryTreeNode(getFraction().display());
    65                 right = root;
    66             }
    67             else            //右上
    68             {
    69                 left = root;
    70                 right = new BinaryTreeNode(getFraction().display());
    71             }
    72             data = new string(1, getOperator());
    73             root = new BinaryTreeNode(*data, left, right);
    74             standardization(root);
    75             opeNum--;
    76         } while (opeNum > 0);
    77 
    78         BinaryTree *tree = new BinaryTree(root);
    79         return tree;
    80     }
    81     return nullptr;
    82 }

     表达式加括号:

     1 bool Generate::toString(BinaryTreeNode * root, string &strExercise)
     2 {
     3     if (root == nullptr)
     4     {
     5         return true;
     6     }
     7 
     8     bool lChildBracket = false, rChildBracket = false;            //标志括号
     9     int leftPriority = 0, curPriority = 0, rightPriority = 0;    //优先级
    10 
    11     //中序遍历
    12     if (root->leftChild != nullptr)
    13     {
    14         //左子树是操作符加括号
    15         if (Calculator::isOperator(root->leftChild->data.at(0)))
    16         {
    17             //得到优先级
    18             leftPriority = Calculator::getPriority(root->leftChild->data.at(0));
    19             curPriority = Calculator::getPriority(root->data.at(0));
    20             if (leftPriority == 0 || curPriority == 0)
    21             {
    22                 return false;
    23             }
    24             if (curPriority > leftPriority)        //优先级大加括号
    25             {
    26                 strExercise = strExercise +  "(";
    27                 lChildBracket = true;
    28             }
    29         }
    30         //遍历左子树
    31         toString(root->leftChild, strExercise);
    32         //补上括号
    33         if (lChildBracket == true)
    34         {
    35             strExercise = strExercise + ")";
    36             lChildBracket = false;
    37         }
    38     }
    39 
    40     //
    41     if (Calculator::isOperator(root->data.at(0)))
    42         strExercise = strExercise + " " + root->data + " ";
    43     else
    44         strExercise = strExercise + root->data;
    45 
    46     //
    47     if (root->rightChild != nullptr)
    48     {
    49         //右子树是操作符加括号
    50         if (Calculator::isOperator(root->rightChild->data.at(0)))
    51         {
    52             //得到优先级
    53             rightPriority = Calculator::getPriority(root->rightChild->data.at(0));
    54             curPriority = Calculator::getPriority(root->data.at(0));
    55             if (rightPriority == 0 || curPriority == 0)
    56             {
    57                 return false;
    58             }
    59             if (curPriority > rightPriority)        //优先级大加括号
    60             {
    61                 strExercise = strExercise + "(";
    62                 rChildBracket = true;
    63             }
    64         }
    65         //遍历右子树
    66         toString(root->rightChild, strExercise);
    67         //补上括号
    68         if (rChildBracket == true)
    69         {
    70             strExercise = strExercise + ")";
    71             rChildBracket = false;
    72         }
    73     }
    74     return true;
    75 }

     Calculator  //计算类

    传入中缀表达式转为逆波兰式:

     1 string Calculator::toReversePolish(string expression)  //传入中缀表达式转为逆波兰式
     2 {
     3     std::stack<string> S1;  //运算符栈
     4     std::stack<string> S2;  //逆波兰式栈
     5     string *ope;     //运算符,分数
     6     string *exp = new string;  //逆波兰式
     7     string *leftbracket = new string(1, '(');    //左括号
     8 
     9     for (int i = 0; i < expression.length(); i++)  //从expression中逐个取出字符
    10     {
    11         if (isOperator(expression.at(i)))  //若取出运算符
    12         {
    13             if (S1.empty() || expression.at(i) == '(')
    14             {
    15                 ope = new string(1, expression.at(i));
    16                 S1.push(*ope);   
    17             }
    18             else if(expression.at(i) == ')')
    19             {
    20                 ope = new string;
    21                 *ope = S1.top();   S1.pop();
    22                 while ((*ope).at(0) != '(')  //取出'('后停止循环,并抛弃'('
    23                 {
    24                     if ((*ope).at(0) != '(')
    25                     {
    26                         S2.push(*ope);
    27                     }
    28                     ope = new string;
    29                     *ope = S1.top();   S1.pop();
    30                 }
    31                 i++;
    32             }
    33             else if (getPriority(expression.at(i)) > getPriority(S1.top().at(0)))  //比较优先级
    34             {
    35                 ope = new string(1, expression.at(i));
    36                 S1.push(*ope);  //该运算符优先级比栈顶运算符优先级高,直接入栈
    37             }
    38             else
    39             {
    40                 if (!S1.empty())
    41                 {
    42                     while (getPriority(expression.at(i)) <= getPriority(S1.top().at(0)))
    43                     {   //从运算符栈S1逐个取出运算符并压入表达式栈S2,直到该运算符优先级比栈顶运算符优先级高
    44                         ope = new string;
    45                         *ope = S1.top();   S1.pop();
    46                         S2.push(*ope);
    47                         if (S1.empty()) break;
    48                     }
    49                 }
    50                 ope = new string(1, expression.at(i));
    51                 S1.push(*ope);   //完成操作后该运算符优先级比栈顶运算符优先级高,入运算符栈S1
    52             }
    53         }
    54         else
    55         { 
    56             if (expression.at(i) == ' ')  i++;  //隔离第一个空格
    57             string fat;
    58             while (expression.at(i) != ' ' && expression.at(i) != '(' && expression.at(i) != ')')
    59             {   //保存分数字符串
    60                 fat.push_back(expression.at(i));
    61                 i++;
    62                 if (i >= expression.length()) break;
    63             }
    64             if (!fat.empty())
    65             {
    66                 S2.push(fat);    //生成分数字符串,并压入表达式栈S2
    67             }
    68             if (i < expression.length())
    69             {
    70                 if (expression.at(i) == '(' || expression.at(i) == ')')  i--;
    71             }
    72         }
    73     }
    74     while (!S2.empty())   //将表达式栈逆序
    75     {
    76         string op;
    77         op = S2.top();   S2.pop();
    78         S1.push(op);
    79     }
    80     exp = new string;
    81     while(!S1.empty())  //组装逆波兰式
    82     {
    83         string str = S1.top();  S1.pop();
    84         if (!S1.empty())
    85         {
    86             *exp = *exp + str + ' ';
    87         }
    88         else
    89         {
    90             *exp = *exp + str;
    91         }
    92     }
    93     return *exp;  //返回逆波兰式
    94 }

    逆波兰式转为树:

     1 BinaryTreeNode * Calculator::toTree(string exp)  //逆波兰式转为树
     2 {
     3     std::stack<BinaryTreeNode*> num;    //数字栈
     4     BinaryTreeNode *exp_ptr = nullptr;  //结点指针
     5     BinaryTreeNode *right = nullptr;    //右孩子指针
     6     BinaryTreeNode *left = nullptr;     //左孩子指针
     7     string *data_exp;         //结点数据
     8 
     9     for (int i = 0; i < exp.length(); i++)  //逐个字符读取逆波兰式
    10     {
    11         if (isOperator(exp.at(i)))     //读取到运算符
    12         {
    13             right = num.top();  num.pop(); 
    14             left = num.top();  num.pop();
    15             data_exp = new string(1,exp.at(i));     //将运算符转化为string类型,并赋给结点数据data
    16 
    17             BinaryTreeNode *exp_root = new BinaryTreeNode(*data_exp, left, right);  //构建新树
    18             num.push(exp_root);  
    19         }
    20         else
    21         {   //读取到数字,生成完整分数字符串,并压入数字栈num
    22             if (exp.at(i) == ' ')   i++;   //隔离第一个空格
    23             if (!isOperator(exp.at(i)))
    24             {
    25                 string fat;
    26                 while (exp.at(i) != ' ')
    27                 {   //保存分数字符串
    28                     fat.push_back(exp.at(i));
    29                     i++;
    30                     if (i >= exp.length()) break;
    31                 }
    32                 exp_ptr = new BinaryTreeNode;
    33                 if (!fat.empty())
    34                 {
    35                     exp_ptr->data = fat;
    36                     num.push(exp_ptr);
    37                 }
    38             }
    39             else
    40             {
    41                 i--;
    42             }
    43         }
    44     }
    45     exp_ptr = num.top();  num.pop();  //从栈顶取出建好的树的根结点
    46     return exp_ptr;   //返回树的根结点
    47 }

    六、测试运行:

    1.生成一万道题目与答案:

     

    2.批改一万道题目:

     3.修改1,4,6题的答案再次批改:

     

           

    七、完整PSP:

    PSP2.1

    Personal Software Process Stages

    预估耗时(分钟)

    实际耗时(分钟)

    Planning

    计划

     60

     90

    · Estimate

    · 估计这个任务需要多少时间

     60

     90

    Development

    开发

     1680

     1600

    · Analysis

    · 需求分析 (包括学习新技术)

     120

     160

    · Design Spec

    · 生成设计文档

     30

     30

    · Design Review

    · 设计复审 (和同事审核设计文档)

     10

     10

    · Coding Standard

    · 代码规范 (为目前的开发制定合适的规范)

     20

     20

    · Design

    · 具体设计

     120

     120

    · Coding

    · 具体编码

     1200

     1080

    · Code Review

    · 代码复审

     60

     60

    · Test

    · 测试(自我测试,修改代码,提交修改)

     120

     120

    Reporting

    报告

     150

     120

    · Test Report

    · 测试报告

     60

     30

    · Size Measurement

    · 计算工作量

     30

     30

    · Postmortem & Process Improvement Plan

    · 事后总结, 并提出过程改进计划

     60

     60

    合计

     1890

     1810

    八、项目小结:

    开发初:

    本来我们想使用VS上的插件live share进行结对项目的开发,但后面发现这个插件不符合我们的想法,后转用Github团队仓库进行开发。

    遇到的一大困难是对Github团队仓库的使用,仔细研究之后才初步学会使用,了解了分支、更改、同步、拉取、推送等功能,解决难题后也算是一大收获。

    项目本身:

    由于我们两人编程水平相差较大,纪禧很强而伟杰只有c语言基础,开发过程比较坎坷。后来也是通过不断交流,不断翻阅博客,终于实现了这个项目。

    优点:前期结构框架定得很好,所以后续实现和改动比较方便。

    难点:中缀表达式转逆波兰式建树的过程中需要考虑的因素很多,对各种情况的判断及解决比较复杂,刚写好时总是出现越界问题,后用断点不断调试,终于改好。

    结对感受:

    合作比单人更有动力,交流更多,犯错更少,学到的东西也更多,更迅速。

  • 相关阅读:
    指定的参数已超出有效值的范围。参数名:sit ,先仔细看看错误和我的一样不一样
    简单说下C#变量的作用域
    C#常用的字符串处理方法
    驼峰命名、帕斯卡命名、匈牙利命名--三种命名方法
    Python的安装
    Python下numpy的使用
    命名法:骆驼(Camel)、帕斯卡(pascal)、匈牙利(Hungarian)、下划线(_)
    PHP中高级面试问题集锦
    利用python,生成word
    python实现网页截图
  • 原文地址:https://www.cnblogs.com/clsgghost/p/12605005.html
Copyright © 2011-2022 走看看