这个作业属于哪个课程 | 2302软件工程 |
---|---|
这个作业要求在哪里 | 软件工程第二次作业 |
这个作业的目标 | 解析JSON文件并通过命令行进行文件的读写 |
其他参考文献 | 《构建之法》 JSON Editor Online VS 单元测试 |
目录:
1. 仓库地址
2.psp表格
PSP | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 20 | 10 |
• Estimate | • 估计这个任务需要多少时间 | 20 | 10 |
Development | 开发 | 1520 | 1760 |
• Analysis | • 需求分析 (包括学习新技术) | 180 | 120 |
• Design Spec | • 生成设计文档 | 30 | 30 |
• Design Review | • 设计复审 | 30 | 20 |
• Coding Standard | • 代码规范 (为目前的开发制定合适的规范) | 40 | 30 |
• Design | • 具体设计 | 60 | 60 |
• Coding | • 具体编码 | 1000 | 1200 |
• Code Review | • 代码复审 | 120 | 180 |
• Test | • 测试(自我测试,修改代码,提交修改) | 60 | 120 |
Reporting | 报告 | 140 | 150 |
• Test Repor | • 测试报告 | 40 | 60 |
• Size Measurement | • 计算工作量 | 40 | 30 |
• Postmortem & Process Improvement Plan | • 事后总结, 并提出过程改进计划 | 60 | 60 |
合计 | 1680 | 1920 |
3.解题思路描述
3.1 json数据解析
对于从网站爬取下来的的json数据,我们需要先了解它的结构,但是有的数据是没有缩进的,不利于阅读,所以借助JSON Editor Online网站来进行JSON数据的缩进,转化成树形结构,便于阅读。
3.2 数据读取分析
而要在代码中要读取JSON文件,我们还需要借助第三方库,这边使用的nlohmann来帮助解析JSON文件,我们可以将JSON文件视为一个多维数组,根据它的结构来进行数据读取和处理。
4.模块接口的设计与实现
4.1功能封装
程序主要分为俩个部分:DWASearch.h(头文件)和DWASearch.cpp(程序主体),主要实现了俩个功能:1.读取并输出所有选手信息;2.读取并输出比赛信息;总的来说就是一个读写文件的过程,所以构造了一个output类来负责文件的读写以及读入指令的辨别,并在该类中还有俩个内部类:PlayerHandler类和ResultHandler类来分别负责输出选手信息和比赛信息。同时设置结构体Player和Result用来暂存从文件读出的信息用于排序后输出。
class Output{
public:
PlayerHandler playerHandler;
ResultHandler resultHandler;
void event(ofstream &outputFile,const string &command);
bool isFirstWordResult(const string &str);
};
而在俩个内部类中分别设置函数来进行选手/比赛信息的处理,将具体功能封装给各个函数,通过函数的调用来进行文件的读写
//处理选手信息的类
class PlayerHandler {
public:
void printPlayers(ofstream &outputFile);//输出选手信息
static bool comparePlayers(const Player& p1, const Player& p2);//比较函数,排序选手
static string removeQuotes(const string& str);//去除引号,统一输出格式
};
// 封装处理比赛结果的类
class ResultHandler {
public:
void result(ofstream &outputFile, const string &eventName);//输出决赛信息
void resultDetail(ofstream& outputFile,const string str);//输出比赛详细信息
static void doublename(string& str);//处理有俩个运动员的情况
static string fillscore(Result& result,const json& player);//计算选手得分
static void threeDetail(json& data,vector<Result>& results);//处理有三次比赛的情况
static void twoDetail(json& data,vector<Result>& results);//处理有俩次比赛的情况
static void oneDetail(json& data,vector<Result>& results);//处理只有一次比赛的情况
static bool compareResults(const Result& a, const Result& b);//比较函数,排序比赛信息
};
4.2实现过程
5关键代码展示
5.1输出选手信息
//输出选手信息
void PlayerHandler::printPlayers(ofstream& outputFile) {
// Json读取
ifstream ifs("datas/athletes.json");
if (!ifs.is_open()){
cerr << "Error opening file! " << '\n';
return;
}
json data = json::parse(ifs);
// 存储选手信息的容器
vector<Player> players;
for (const auto& participant : data) {
Player player;
for(const auto& contestants : participant["Participations"]){
player.fullName = removeQuotes(contestants["PreferredLastName"]) + " " + removeQuotes(contestants["PreferredFirstName"]);
player.gender = (contestants["Gender"] == 0 ? "Male" : "Female");
player.country = removeQuotes(contestants["NAT"]);
players.push_back(player);
}
}
// 对选手进行排序
stable_sort(players.begin(), players.end(), comparePlayers);
// 输出排序后的选手信息
for (const auto& player : players) {
outputFile << "Full Name:" << player.fullName << "\n";
outputFile << "Gender:" << player.gender << "\n";
outputFile << "Country:" << player.country << "\n";
outputFile << "-----" << "\n";
}
return ;
}
5.2输出决赛结果
//输出比赛的决赛结果
void ResultHandler::result (ofstream& outputFile,const string &str){
// Json读取
ifstream ifs(str);
if (!ifs.is_open()){
cerr << "Error opening file! " << '\n';
return;
}
json data = json::parse(ifs);
// 获取比赛的决赛结果
json finalResults = data["Heats"][0]["Results"];
// 写入 output.txt 文件
for (const auto& result : finalResults) {
if (!result["FullName"].is_null() && !result["Rank"].is_null() && !result["TotalPoints"].is_null() && !result["Dives"].is_null()) {
string fullName = result["FullName"].get<string>();
doublename(fullName);
string rank = to_string(result["Rank"].get<int>()); // 将数字类型转换为字符串类型
string totalScore = result["TotalPoints"].get<string>();
string scoreStr;
for (const auto& dive : result["Dives"]) {
if (!dive["DivePoints"].is_null()) {
scoreStr += dive["DivePoints"].get<string>() + " + ";
}
}
scoreStr = scoreStr.substr(0, scoreStr.size() - 3) + " = " + totalScore;
outputFile << "Full Name:" << fullName << '\n';
outputFile << "Rank:" << rank << '\n';
outputFile << "Score:" << scoreStr << '\n';
outputFile << "-----" << '\n';
}
}
return ;
}
5.3输出详细结果
//输出比赛详细结果
void ResultHandler::resultDetail(ofstream& outputFile,const string& str){
ifstream ifs(str);
if (!ifs.is_open()){
cerr << "Error opening file! " << '\n';
return;
}
json data = json::parse(ifs);
vector<Result> results;
// 根据比赛场次选择不同函数处理数据
int num=data["Heats"].size();
if(num==3)
threeDetail(data,results);
if(num==2)
twoDetail(data,results);
if(num==1)
oneDetail(data,results);
// 对结果按照排名排序
sort(results.begin(), results.end(), compareResults);
//将比赛结果输出到文件中
for (const auto& result : results) {
outputFile << "Full Name:" << result.fullName << "\n";
outputFile << "Rank:" << result.rank << "\n";
outputFile << "Preliminary Score:" << result.preliminaryScore << "\n";
outputFile << "Semifinal Score:" << result.semifinalScore << "\n";
outputFile << "Final Score:" << result.finalScore << "\n";
outputFile << "-----\n";
}
}
6性能优化
1.使用引用传递参数:在函数参数传递过程中,使用引用传递可以避免不必要的拷贝
2.结果缓存:对于多次连续输入重复指令,程序对结果进行缓存,当输入连续指令时不必再次读取数据,而直接输出缓存数据,大大减少运行时间
优化前的运行时间:
优化后的运行时间:
7.单元测试
使用vs进行单元测试:
覆盖率:
8.异常处理
1.命令行异常处理:对于错误的命令行指令,反馈错误并退出程序
if(argc!=3){
cerr << "Error command." << '\n';
return 1;
}
2.文件打开错误,每次打开相关文件都会有异常处理,当未能正确打开相关文件,反馈错误并退出程序
ifstream inputFile(argv[1]);
ofstream outputFile(argv[2], ios_base::app);
if (!inputFile.is_open() || !outputFile.is_open()) {
cerr << "Error opening files." << '\n';
return 1;
}
9.心得体会
经过本次实践,了解并熟悉了c++如何解析并处理JSON数据以及通过命令行来进行文件的读写。同时学会了如何使用VS对程序进行单元测试。总的来说,本次实践我收获良多,一次次的代码提交和功能封装让我更清晰的了解了应该如何高效的去完成一个项目并具有一定的可读性。