189
社区成员




这个作业属于哪个课程 | 构建之法-2021秋-福州大学软件工程 https://bbs.csdn.net/forums/fzuSoftwareEngineering2021 |
---|---|
这个作业要求在哪里 | 2021秋软工实践第一次个人编程作业 https://bbs.csdn.net/topics/600574694 |
这个作业的目标 | 学习GitHub和Git使用,锻炼思考以及编码能力,提升计算机素养 |
学号 | 031902609 |
GitHub地址 | 第一次个人软工作业仓库地址 https://github.com/LiangYC1021/2021-Software-Engineering-1 |
PSP | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | ||
· Estimate | · 估计这个任务需要多少时间 | 600 | 720 |
Development | 开发 | ||
· Analysis | · 需求分析 (包括学习新技术) | 60 | 40 |
· Design Spec | · 生成设计文档 | 30 | 30 |
· Design Review | · 设计复审 | 20 | 20 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 30 | 0 |
· Design | · 具体设计 | 40 | 40 |
· Coding | · 具体编码 | 180 | 180 |
· Code ReviewPlanning | · 代码复审计划 | 60 | 40 |
· Test· Estimate | · 测试(自我测试,修改代码,提交修改)· 估计这个任务需要多少时间 | 200 | 240 |
ReportingDevelopment | 报告 | 60 | 60 |
· Postmortem & Process Improvement Plan· Design Review | · 事后总结, 并提出过程改进计划· 设计复审 | 30 | 30 |
· 合计 | 1310 | 1400 |
第一次个人软工作业仓库地址 https://github.com/LiangYC1021/2021-Software-Engineering-1
我在本次作业后期才发现其实正确commit的方法是对之前上传的代码进行update,这样可以很直观的看出来和之前的版本修改之处,而不是每次都新create一份完整代码。
本次上传GitHub仓库的内容为每次更新的版本的 CPP 文件及其相应的文档(包含了修改、更新的内容)。
风格不知道怎么说。。请前往仓库查看代码!
缩进:每个花括号语句内使用相同缩进,缩进比上一级语句多一个制表符tab。
变量命名:所有变量名以在代码中的实际作用命名。
每行最多字符数:无制定,但我的代码中不会有长行,保证能在一个屏幕中可见。
函数最大行数:除主函数外,最大行数为30行。
函数、类命名:本题中没用到类、封装等。函数命名以在代码中的实际作用命名,以大写字母开头。
常量:本题中未使用到const常量。通常会在头文件下方定义一个const int N = 1e5 + 5 。
空行规则:函数之间、整体全局变量定义区与函数之间会空一行;函数内不空行。
注释规则:代码内使用英文注释防止编码乱码。但在GitHub的commit、每次更新的文档中会部分使用中文进行解释。
操作符前后空格:操作符前后无空格。
其他规则:每份代码会包含缺省源,其中有:万能头文件、循环宏定义、无穷大宏定义、longlong 类型定义、常用模数、常量大小等。
#include<bits/stdc++.h>
#define inf 1e18+5
#define rep(i,a,b) for(int i=(a);i<=(b);++i)
using namespace std;
typedef long long ll;
const int mod=998244353;
const int N=1e5+5;
后续会将代码规范单独水一贴。
第一次遇到单元测试这类的要求,不太清楚是要干啥,我当前的理解是对每个函数进行解释与测试。
关键代码内容也在该部分!
以下函数按照程序运行逻辑的顺序给出。
1.Pre()函数:预处理读入的每行代码,删除其中的“字符串“和注释中的内容。
inline string Pre(string s)
{
string res="";
for(int i=0;i<s.size();i++){
if(s[i]=='"'){
for(int j=i+1;i<s.size();j++){
if(s[j]=='"'){
s.erase(i,j-i+1);
break;
}
}
}
}
for(int i=0;i<s.size();i++){
if(s[i]=='/'&&s[i+1]=='/'){
return res;
}
res+=s[i];
}
return res;
}
输入一行代码测试,输出处理后的结果。正确。
2.Match(string s)函数:对于每次分离出来的一部分子串,暴力枚举32个关键字判断是否相同,相同时对某些关键字执行相应操作。返回一个bool变量。
inline bool Match(string s)
{
string res="NULL";
rep(i,1,32){
if(s==Key[i]){
res=Key[i];
break;
}
}
if(res=="switch"&&Case_num){
Swt.push_back(Case_num);
Case_num=0;
}
if(res=="case")Case_num++;
if(res=="if" || res=="else")ie.push_back(res);
if(res!="NULL"){
Tot_num++;
return true;
}
return false;
}
过于简单,与后续函数一同测试。
3.check(string s)函数。该函数对getline获得的每一行代码进行操作。提取出其中的花括号和关键字,便于后续的判别操作。
由于32个关键字的长度为2~8,于是很简单的对每个相应长度的子串拿去执行上方提到的Match函数。从长度8至2枚举的原因是有类似于"do" "double"这种情况,若先判断了长度为2的"do"关键字,但实际情况为"double",则会出错。
inline void check(string s)
{
int len=s.size();
s+="$$$$$$$$$";
//Prevent overflow
for(int i=0;i<len;i++){
if(s[i]=='{')ie.push_back("{");
if(s[i]=='}')ie.push_back("}");
for(int j=8;j>=2;j--){
if(Match(s.substr(i,j))){
i+=j-1;
break;
}
}
}
}
在其中加一句代码用于测试,检测到一个正确的关键字就输出:
cout<<s.substr(i,j)<<endl;
输入一行正确的代码语句。正确。
4.Special_Check(string s) 函数。用于判断,将一对"else" "if" 替换成 "else_if"。
inline void Special_Check(string s)
{
int len=s.size();
s+="$$$$";
int temp_if=0,temp_else=0;
for(int i=0;i<len;i++){
if(s.substr(i,2)=="if")temp_if++;
if(s.substr(i,4)=="else")temp_else++;
}
if(temp_else && temp_if){
stack<string>tmp;
bool f1=0,f2=0;
while(f1==0 && f2==0){
string bk=ie.back();
ie.pop_back();
if(bk=="if")f2=1;
if(bk=="else")f1=1;
if(bk!="if"&&bk!="else")tmp.push(bk);
}
ie.pop_back();
ie.push_back("else_if");
while(!tmp.empty()){
ie.push_back(tmp.top());
tmp.pop();
}
//Replace "else" "if" with "else_if"
}
}
对于读入的一行,获取的关键字及花括号为:
正确。
5.Count_ie_num()函数。该函数较为关键,用于匹配每个if else对、if else_if else对。它可以处理许多特判!
我的主要思路是对于刚刚提取的if else else_if 及左右花括号 { }这几个关键字,用栈这一数据结构来进行他们的匹配(灵感来自于括号匹配题目)。
对于每个右花括号 ‘}’ ,都处理到前一个左花括号‘{‘ 之间的关键字对。由于题目、老师、助教的解释,不是由else结尾的if else对、if else_if else对都是不予计数的,因此在这俩花括号之间,对于每个”else“,都往前匹配,若存在else_if ,则if else_if else对数增加,只有if与其相匹配则if else对数增加。
inline void Count_ie_num()
{
for(int i=0;i<ie.size();i++){
if(ie[i]=="}"){
while(!stk.empty()){
string tp=stk.top();
stk.pop();
if(tp=="{")break;
else if(tp=="else"){
bool flag=0;
while(!stk.empty()){
tp=stk.top();
stk.pop();
if(tp=="else_if")flag=1;
if(tp=="if")break;
}
if(flag)elseif_num++;
else else_num++;
}
}
}
else stk.push(ie[i]);
}
}
对于长成这样的结构,其中的if else_if else对是不被计数的。运行结果正确。
在这个样例中,红色代表if else对,绿色代表if else_if else对。运行结果正确。
6.Out(int level) 函数。该函数较为简单,只是对于每次输入的等级,输出对应等级的内容。
附加功能为有:①当输入等级不在[1,4]内时,会报错,提示输入正确等级。②当没有switch case结构时,会输出没有该结构。
inline void Out(int level)
{
if(!(1<=level && level<=4)){
cout<<"This is an incorrect level, please enter a level between 1 and 4."<<endl;
return;
}
if(level>=1)cout<<"total num: "<<Tot_num<<endl;
if(level>=2){
if(Swt.size()>0){
cout<<"switch num: "<<Swt.size()<<endl;
cout<<"case num: ";
for(int i=0;i<Swt.size();i++){
if(i)cout<<" ";
cout<<Swt[i];
}
}
else cout<<"There is no Switch structure";
cout<<endl;
}
if(level>=3)cout<<"if-else num: "<<else_num<<endl;
if(level>=4)cout<<"if-elseif-else num: "<<elseif_num<<endl;
}
输出测试放在后续完整代码运行时体现。
完整代码以及从头开始的多个版本可前往 本次作业的GitHub仓库 https://github.com/LiangYC1021/2021-Software-Engineering-1 查看!
Level4-5为目前的最终版本。
任务可大致分为前置部分,编码部分,寻找特判、更改代码部分以及博客记录部分。
前置部分:制作代码规范,制作 PSP 表格,分析作业要求,寻找解题思路等。因为很久没用GitHub了所以查阅大量资料才完成上传操作。
编码部分:编写每个函数、整合等。
寻找特判、更改代码部分:因为很多同学跟我说了很多种奇怪的情况,之后对此进行调整。
博客记录部分:按照作业要求编写博客。
见博客最上方。
本次遇到的困难主要有GitHub仓库的上传和很多特判的情况。
该程序无oj评测,只能通过手造样例来判断正确性。
样例1:作业页面样例,输入了不同的等级,输出相应等级要求。
样例2:
该输入其实不是正确的可编译代码,只是为了造特判而随便写的一个例子。
判断了:①字符串内的关键字不计②不以else结尾的 "if else_if else"结构不计数。
样例3:
该样例只包含switch-case结构,不论是否以“default"结尾的switch都能判断。
样例4:
这个样例的第一块if那边看起来有点奇怪,其实那部分是一个"if else_if else"结构。
本次作业采用朴素算法,按复杂度分析来说是O(n^2^)的。但由于本题的 n 都偏小,就算采用O(n^3^)的算法可能也不会跑到4、5秒。
起初这种多模式串匹配的问题一拍脑袋想到的是自动机做法,后来朴素做法写着写着就发现好像没时间写优化做法了,重构有点麻烦,后续有空会添加至博客!