软件工程第二次作业-个人实战
作业基本信息
| 这个作业属于哪个课程 | 软件工程实践-2023学年-W班社区-CSDN社区云 |
|---|---|
| 这个作业要求在哪里 | 软件工程实践第二次作业——个人实战-CSDN社区 |
| 这个作业的目标 | 在文章开头给出新建的Gitcode项目地址 详细阅读作业要求 完成代码编写并进行测试 撰写博客 描述解题思路 设计实现过程 关键代码展示 PSP表格 |
| 其他参考文献 | https://www.cnblogs.com/xinz/archive/2011/11/20/2255830.html |
## 仓库地址
ImBloodGirl / 222100306 · GitCode
PSP表格
| PSP | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
| Planning | 计划 | ||
| • Estimate | • 估计这个任务需要多少时间 | 5 | 5 |
| Development | 开发 | ||
| • Analysis | • 需求分析 (包括学习新技术) | 20 | 660(这个时间都在学C++爬虫,还没学会......)+400(改用python在调爬虫bug...) |
| • Design Spec | • 生成设计文档 | 45 | 120 |
| • Design Review | • 设计复审 | 45 | 60 |
| • Coding Standard | • 代码规范 (为目前的开发制定合适的规范) | 28 | 60 |
| • Design | • 具体设计 | 45 | 70 |
| • Coding | • 具体编码 | 60 | 500(感觉大部分时间是在写爬虫......) |
| • Code Review | • 代码复审 | 30 | 300 |
| • Test | • 测试(自我测试,修改代码,提交修改) | 60 | 300 |
| Reporting | 报告 | ||
| • Test Repor | • 测试报告 | 30 | 45 |
| • Size Measurement | • 计算工作量 | 10 | 20 |
| • Postmortem & Process Improvement Plan | • 事后总结, 并提出过程改进计划 | 45 | 60 |
| 合计 | 378 | 2620 |
解题思路
首先,要对数据进行爬取。爬取后进行数据清洗。数据清洗后需要提取并整理出样例给出的数据格式。根据题目要求,我们需要获取选手的名字、性别、国籍、比赛项目、排名、成绩。
比赛结果 |世界游泳官方 (worldaquatics.com)
比赛运动员 |世界游泳官方 (worldaquatics.com)
(一)需要函数
-
文件读取函数
-
数据爬取
-
数据清洗
-
数据格式整理
-
-
文件输出函数
-
错误反馈处理
(二)数据库结构
(1)比赛表event_results
本来的数据库表结构是这样的:
| Column Name | Data Type | Description |
|---|---|---|
| id | Integer | Primary key, autoincrement |
| EventName | String(255) | Name of the event |
| DisciplineName | String(255) | Name of the discipline |
| EventResultDate | Date | Date of the event result |
| OverallRank | Integer | Overall rank of the athlete |
| Country | String(255) | Country of the athlete |
| Athlete | String(255) | Name of the athlete |
| Age | Integer | Age of the athlete |
| Points | Float | Points earned by the athlete |
| PtsBehind | Float | Points behind the leader |
(2)选手表athlete_info
| Column Name | Data Type | Description |
|---|---|---|
| id | Integer | Primary key, autoincrement |
| Country | String(255) | Country of the athlete |
| Athlete | String(255) | Name of the athlete |
| Gender | String(10) | Gender of the athlete (e.g., 'Male', 'Female') |
| DOB | Date | Date of birth of the athlete |
| Discipline | String(255) | Discipline of the athlete (e.g., 'Swimming', 'Athletics') |
(3) 具体得分表envent_details
| Column Name | Data Type | Description |
|---|---|---|
| id | Integer | Primary key, autoincrement |
| event_result_id | Integer | Foreign key, references the event result |
| DiveOrder | Integer | Order of the dive |
| DivePoints | Float | Points awarded for the dive |
【改进】但失败
所有不同项目是存放在同一个表下的,但是认为这样会使查询速度减慢,因此,如果改为以比赛项目名为名字存储数据,建立多个表。
修改失败,搞不了动态创建表。
因为C++的IDE问题难以修复,不能使用第三方库,只能把数据库文件存为csv,读取文件。
(三)整体思路
-
读取文件
-
识别文件内容
-
如果是
players,就调用输出选手所有数据的函数 -
如果是
result ...
,则进一步处理:
-
result必须是独立的单词。 -
如果格式为
result ... detail,就调用输出详细比赛结果的函数。 -
如果格式为
result ...(不含detail),就调用输出相应比赛结果的函数。 -
如果
result后面的条件不符合要求,就输出N/A。
-
-
如果无法识别是
players还是result,则输出Error。
-
-
整理格式
详细思路见实现过程
实现过程
(一)爬取数据
1. 编译平台
Python语言,Pycharm软件
2. 爬取结果展示
3. 爬取过程
(1)根据结构建立表
# 创建数据库连接引擎
engine = create_engine('mysql+pymysql://root:123456@localhost:3306/swim_cmpt')
# 创建一个基类
Base = declarative_base()
# 定义比赛结果表的ORM类
class EventResult(Base):
__tablename__ = 'event_results'
id = Column(Integer, primary_key=True, autoincrement=True)
EventName = Column(String(255))
DisciplineName = Column(String(255))
EventResultDate = Column(Date)
OverallRank = Column(Integer)
Country = Column(String(255))
Athlete = Column(String(255))
Age = Column(Integer)
Points = Column(Float)
PtsBehind = Column(Float)
# 创建表
Base.metadata.create_all(engine)
# 创建一个Session类
Session = sessionmaker(bind=engine)
(2)根据含有各比赛id的url爬出此特定id
def get_competition_events():
url = "https://api.worldaquatics.com/fina/competitions/3337/events"
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 Edg/122.0.0.0"
}
# 发送 GET 请求
response = requests.get(url, headers=headers)
# 检查请求是否成功
if response.status_code == 200:
# 解析 JSON 响应
data = response.json()
# print(data)
# 提取比赛信息
competitions = data.get('Sports', [])[0].get("DisciplineList",[])
print(competitions)
for competition in competitions:
competition_id = competition.get('Id')
competition_name = competition.get('DisciplineName')
# 构建比赛对应的URL
competition_url = f"https://api.worldaquatics.com/fina/events/{competition_id}"
# 获取比赛结果数据并处理
get_event_results(competition_url, competition_name)
else:
print("Failed to fetch data from the API")
print("获取比赛信息失败!")
类似以上,根据获取的特定id访问比赛具体信息url,爬取信息存入数据库
(3)将爬取到的信息存为csv
(二)功能一
1. 解析数据文件
-
读取并解析
athlete_info.csv文件,获取选手的全名、性别和国籍信息。
2. 实现主功能
-
根据
input.txt中的指令内容,决定执行哪个功能。对于功能1,当内容为players时,调用输出所有选手信息的函数outputPlayersData。
3. 输出格式
-
输出内容格式化为指定格式,并写入到
output.txt文件中。每个选手的信息后面跟随一行-----作为分隔符。
(三)功能二
1. 解析数据文件
-
读取并解析
event_results.csv文件,获取各个赛事的比赛结果。
2. 实现主功能
-
根据
input.txt
中的指令内容,决定执行哪个功能。
-
对于内容为
result ...
的指令:
-
result必须是独立的单词。 -
如果格式为
result ... detail,调用输出详细比赛结果的函数outputDetailedResult。 -
如果格式为
result ...(不含detail),调用输出相应比赛结果的函数outputEventResult。
-
-
3. 输出格式
-
输出内容格式化为指定格式,并写入到
output.txt文件中。每个结果信息后面跟随一行-----作为分隔符。
(四))附加功能
1. 详细比赛结果
-
读取并解析更详细的比赛结果文件,获取初赛、半决赛和决赛的详细得分。
2. 实现主功能
-
当指令内容为
result ... detail时,调用输出详细比赛结果的函数outputDetailedResult。
3. 输出格式
-
输出内容格式化为指定格式,并写入到
output.txt文件中。每个详细结果信息后面跟随一行-----作为分隔符。
(五)错误处理
1. 处理无法识别的指令
-
如果无法识别是
players还是result,则输出Error,并写入到output.txt文件中。
2. 处理无效的赛事条件
-
如果
result后面的条件不符合要求(由isValidEvent函数判断),则输出N/A,并写入到output.txt文件中。
接口设计和实现过程
(一)接口信息
接口类:DivingChampionShshiy
运行命令:
g++ src\DWASearch.cpp src\DivingChampionship.cpp -o DWASearch
DWASearch src\input.txt src\output.txt src\data\athlete_info.csv src\data\event_results2.csv src\data\event_details.csv
接口功能:
-
处理命令:根据命令内容执行相应的操作。
-
验证事件:验证赛事类型的有效性。
-
验证命令:验证命令格式的有效性。
-
读取数据文件:读取不同格式的 CSV 数据文件。
-
格式化输出:格式化比赛结果并输出到文件。
(二)接口函数
#ifndef DIVING_CHAMPIONSHIP_H
#define DIVING_CHAMPIONSHIP_H
#include <string>
#include <vector>
#include <map>
// 结构体
struct Athlete {
std::string fullName;
std::string lastName;
std::string gender;
std::string country;
};
struct AthleteRank {
std::string preliminaryRank;
std::string semifinalRank;
std::string finalRank;
};
struct DetailedAthlete {
std::string fullName;
std::string rank;
std::string preliminaryScore;
std::string semifinalScore;
std::string finalScore;
};
struct EventResult {
int id;
std::string eventName;
std::string disciplineName;
std::string eventResultDate;
std::string heatName;
int overallRank;
std::string country;
std::string athlete;
std::string age;
double points;
std::string ptsBehind;
};
class DivingChampionship {
public:
// 处理命令,根据命令内容调用相应的处理函数
void processCommand(const std::string& command, const std::string& athleteInfoFile, const std::string& eventResultsFile, const std::string& diveScoresFile, const std::string& outputFile);
// 验证赛事类型的有效性
bool isValidEvent(const std::string& event);
// 验证命令格式的有效性
bool isValidResultCommand(const std::string& command);
private:
// 读取CSV文件并存储数据
void readCSV(const std::string& filename, std::vector<std::vector<std::string>>& data);
// 格式化得分
std::string formatScore(const std::vector<double>& scores);
// 提取选手的姓氏
std::string extractLastName(const std::string& fullName);
// 读取选手信息
std::vector<Athlete> readAthleteInfo(const std::string& filename);
// 读取赛事结果
std::vector<EventResult> readEventResults(const std::string& filename);
// 读取跳水得分
std::map<int, std::vector<double>> readDiveScores(const std::string& filename);
// 写入赛事结果到文件
void writeEventResult(const std::string& filename, const std::vector<EventResult>& results, const std::map<int, std::vector<double>>& diveScores);
// 写入选手信息到文件
void writeAthleteInfo(const std::string& filename, const std::vector<Athlete>& athletes);
// 写入详细赛事结果到文件
void writeDetailedEventResult(const std::string& filename, const std::vector<DetailedAthlete>& athletes);
// 转换字符串为小写
std::string toLower(const std::string& str);
// 输出错误信息到文件
void outputError(const std::string& outputFile);
// 输出N/A到文件
void outputNA(const std::string& outputFile);
};
#endif // DIVING_CHAMPIONSHIP_H
(三)函数逻辑
#include "DivingChampionship.h"
#include <iostream>
#include <fstream>
#include <sstream>
#include <algorithm>
#include <unordered_set>
/**
* @brief 读取CSV文件并存储数据
*
* @param filename CSV文件名
* @param data 用于存储读取数据的二维字符串向量
*/
void DivingChampionship::readCSV(const std::string& filename, std::vector<std::vector<std::string>>& data) {
// 打开文件并读取内容
// 将每行数据分割成多个单词并存储到data中
}
/**
* @brief 格式化得分
*
* @param scores 跳水得分向量
* @return 格式化后的得分字符串
*/
std::string DivingChampionship::formatScore(const std::vector<double>& scores) {
// 遍历得分向量并计算总分
// 将得分和总分格式化为字符串
return formattedScore;
}
/**
* @brief 提取选手的姓氏
*
* @param fullName 选手全名
* @return 选手的姓氏
*/
std::string DivingChampionship::extractLastName(const std::string& fullName) {
// 分割全名字符串并提取最后一个单词作为姓氏
return lastName;
}
/**
* @brief 读取选手信息
*
* @param filename CSV文件名
* @return 选手信息向量
*/
std::vector<Athlete> DivingChampionship::readAthleteInfo(const std::string& filename) {
// 打开文件并读取选手信息
// 将读取的每行数据解析成Athlete结构体并存储到向量中
return athletes;
}
/**
* @brief 读取赛事结果
*
* @param filename CSV文件名
* @return 赛事结果向量
*/
std::vector<EventResult> DivingChampionship::readEventResults(const std::string& filename) {
// 打开文件并读取赛事结果
// 将读取的每行数据解析成EventResult结构体并存储到向量中
return results;
}
/**
* @brief 读取跳水得分
*
* @param filename CSV文件名
* @return 以赛事ID为键,得分向量为值的映射表
*/
std::map<int, std::vector<double>> DivingChampionship::readDiveScores(const std::string& filename) {
// 打开文件并读取跳水得分
// 将读取的每行数据解析成键值对并存储到映射表中
return diveScores;
}
/**
* @brief 写入赛事结果到文件
*
* @param filename 输出文件名
* @param results 赛事结果向量
* @param diveScores 跳水得分映射表
*/
void DivingChampionship::writeEventResult(const std::string& filename, const std::vector<EventResult>& results, const std::map<int, std::vector<double>>& diveScores) {
// 打开输出文件并将赛事结果和得分写入文件
}
/**
* @brief 写入选手信息到文件
*
* @param filename 输出文件名
* @param athletes 选手信息向量
*/
void DivingChampionship::writeAthleteInfo(const std::string& filename, const std::vector<Athlete>& athletes) {
// 打开输出文件并将选手信息写入文件
}
/**
* @brief 写入详细赛事结果到文件
*
* @param filename 输出文件名
* @param athletes 详细赛事结果向量
*/
void DivingChampionship::writeDetailedEventResult(const std::string& filename, const std::vector<DetailedAthlete>& athletes) {
// 打开输出文件并将详细赛事结果写入文件
}
/**
* @brief 转换字符串为小写
*
* @param str 输入字符串
* @return 转换后的字符串
*/
std::string DivingChampionship::toLower(const std::string& str) {
// 将输入字符串中的每个字符转换为小写
return lowerStr;
}
/**
* @brief 验证赛事类型的有效性
*
* @param event 赛事类型字符串
* @return 赛事类型是否合法
*/
bool DivingChampionship::isValidEvent(const std::string& event) {
// 检查赛事类型是否在预定义的有效赛事类型集合中
return valid;
}
/**
* @brief 验证命令格式的有效性
*
* @param command 命令字符串
* @return 命令格式是否合法
*/
bool DivingChampionship::isValidResultCommand(const std::string& command) {
// 检查命令格式是否符合预期,包括是否包含有效的赛事类型和详细标志
return valid;
}
/**
* @brief 处理命令,根据命令内容调用相应的处理函数
*
* @param command 命令字符串
* @param athleteInfoFile 选手信息文件名
* @param eventResultsFile 赛事结果文件名
* @param diveScoresFile 跳水得分文件名
* @param outputFile 输出文件名
*/
void DivingChampionship::processCommand(const std::string& command, const std::string& athleteInfoFile, const std::string& eventResultsFile, const std::string& diveScoresFile, const std::string& outputFile) {
if (command == "players") {
// 读取选手信息并写入输出文件
} else if (!isValidResultCommand(command)) {
// 输出N/A到文件
} else if (command.rfind("result ", 0) == 0 && command.find("detail") == std::string::npos) {
// 读取赛事结果并写入输出文件
} else if (command.rfind("result ", 0) == 0 && command.find("detail") != std::string::npos) {
// 读取详细赛事结果并写入输出文件
} else {
// 输出错误信息到文件
}
}
关键代码展示
总分类命令的逻辑
这里处理命令的逻辑来进行命令的识别/报错和分类。不同情况下的代码流。
bool isValidResultCommand(const std::string& command) {
if (command.rfind("result ", 0) != 0 && command.find("result") == 0) {
return false; // If it starts with "result" but not followed by space, it is an error
}
std::string eventType;
if (command.find(" detail") != std::string::npos) {
eventType = command.substr(7, command.find(" detail") - 7);
return isValidEvent(eventType);
} else {
eventType = command.substr(7);
return isValidEvent(eventType);
}
}
void processCommand(const std::string& command, std::ofstream& outFile) {
if (command == "players") {
outputPlayersData(outFile);
} else if (command.find("result") == 0) {
std::string event = command.substr(7); // 去掉 "result " 部分
std::string detailSuffix = "detail";
// 处理详细结果
if (event.size() > detailSuffix.size() &&
event.compare(event.size() - detailSuffix.size(), detailSuffix.size(), detailSuffix) == 0) {
std::string eventType = event.substr(0, event.size() - detailSuffix.size() - 1);
if (isValidEvent(eventType)) {
outputDetailedResult(outFile, eventType);
} else {
outputNA(outFile);
}
}
// 处理普通结果
else if (isValidEvent(event)) {
outputEventResult(outFile, event);
} else {
outputNA(outFile);
}
} else {
outputError(outFile);
}
}
bool isValidEvent(const std::string& event) {
static const std::vector<std::string> validEvents = {
"women 1m springboard",
"women 3m springboard",
"women 10m platform",
"women 3m synchronised",
"women 10m synchronised",
"men 1m springboard",
"men 3m springboard",
"men 10m platform",
"men 3m synchronised",
"men 10m synchronised"
};
return std::find(validEvents.begin(), validEvents.end(), event) != validEvents.end();
}
int main(int argc, char* argv[]) {
if (argc != 3) {
std::cerr << "Usage: " << argv[0] << " <input_file> <output_file>\n";
return 1;
}
std::ifstream inFile(argv[1]);
std::ofstream outFile(argv[2]);
if (!inFile) {
std::cerr << "Error opening input file\n";
return 1;
}
if (!outFile) {
std::cerr << "Error opening output file\n";
return 1;
}
std::string command;
while (std::getline(inFile, command)) {
processCommand(command, outFile);
}
return 0;
}
下面是合法条件的确定:
bool isValidEvent(const std::string& event) {
std::unordered_set<std::string> validEvents = {
"women 1m springboard", "women 3m springboard", "women 10m platform",
"women 3m synchronised", "women 10m synchronised", "men 1m springboard",
"men 3m springboard", "men 10m platform", "men 3m synchronised", "men 10m synchronised"
};
return validEvents.find(event) != validEvents.end();
}
性能改进
单元测试展示
-
先让程序正常读取文件。
异常处理说明
1. 处理无法识别的指令
当输入文件中的指令既不是 players 也不是以 result 开头时,程序会输出 Error。这是为了确保程序能在遇到无效输入时,提供明确的反馈,而不会崩溃或产生不可预期的行为。
具体实现方法如下:
-
在
processCommand函数中,首先检查指令是否为players,如果是则调用outputPlayersData函数。 -
如果指令不是
players,则检查它是否以result开头。 -
如果既不是
players也不是result,则调用outputError函数,向输出文件写入Error。
2. 处理无效的赛事条件
在处理以 result 开头的指令时,需要进一步检查指令的详细内容是否有效。具体的检查步骤如下:
-
如果指令格式为
result ... detail,即包含detail关键字,程序会提取出赛事条件并检查其有效性。如果条件无效,则输出N/A。 -
如果指令格式为
result ...(不含detail),程序同样会检查赛事条件的有效性。如果条件无效,也会输出N/A。
为了实现上述检查,定义了一个辅助函数 isValidEvent 来验证赛事条件的有效性。这个函数包含了所有有效的赛事条件,并检查给定的条件是否在其中。
3. 处理文件读取和写入错误
为了确保程序在读取和写入文件时能够处理可能出现的错误,程序会在打开文件时检查文件是否成功打开。如果文件无法打开,程序将输出相应的错误信息,并终止执行。
具体实现方法如下:
-
在
main函数中,尝试打开输入文件和输出文件。 -
如果输入文件无法打开,输出错误信息
Error opening input file。 -
如果输出文件无法打开,输出错误信息
Error opening output file。
心得体会
遇到的问题
(一)输出等级时总是读不到Rank1的内容
原因:在csv中rank1的pst behind值为空,导致读取失败
反思和经验
(一)爬取数据之失败的尝试
尝试学习用C++爬取网页数据,实在不理解,尽管看了很多网课,大部分都是爬取图片,讲解者隐晦带着黄色的腔调令人不适。
所以我用Python爬取了网页数据,并用C++处理数据。
(二)Planning时间与预期相差甚远,以及爬虫的学习经验
-
理想:我用python写过爬虫,无非是
-
请求网页,抓取html内容
-
用正则清洗数据
-
将数据安放到数据库
而已,用到的库大概有
requests或urllib库。 -
-
现实:但在 C++ 中抓取网页内容相对 Python 来说复杂一些。
-
因为 C++ 并没有内置像 Python 中
requests或urllib这样的方便的库 。 -
在 C++ 中,我需要使用一大堆第三方库来发送 HTTP 请求和处理响应。常用的库包括 cURL、libcurl、WinINet 等。
-
哎理想很丰满,现实很骨感,我以为爬虫是个很简单的作业,但是由于C++爬虫和Python有很大不同,还是得重新学习,学习爬虫的原理也是个曲折的过程......
-
其他:另外,我发现仅仅只熟悉一个编译器,是远远不够的,因为有时候编译器因为各种原因无法使用。
-
比如,我的Cion许可证过期了,需要邮件到校园邮箱验证,而我给邮箱管理员发的邮件一周都没回,网站也登不进去......
-
这时我想到可以用vscode,但发现自己并不熟悉库的安装,还要另外下载vspkg才能安装库......
-
(三)学习是耗时最长的
学习原来是耗时最长的......
烦......
有些东西我们总是看起来“会”、“有思路”,做起来比预期时间要长得多,所以应该预留时间,未雨绸缪,才有望按时交付。
(四)
把所有困难的事情看作自我成长,就不会那么困难了。
比如一开始不知道数据到底要爬什么,虽然会用python爬虫,但是着手取数据的时候还是纠结半天。
现在知道看到作业给出两个链接,只要爬两个链接下相关的数据即可。不需要想太多复杂的。
相关推荐
【软件工程】大作业——软件设计说明书,高分大作业
南京理工大学紫金学院,《软件工程》这门课最后的大作业,是每个小组做一份软件设计说明书,这份就是我们当时的高分作业。
软件工程作业,个人作业,设有密码。
软件工程作业,个人作业,设有密码。软件工程作业,个人作业,设有密码。
软件工程第二次作业 计算机学院
软件工程第二次作业 计算机学院
山东大学软件工程实验报告及作业
1.项目是数据库课设做的火车票售票系统 2.写的SSR 3.没有复习资料,只有老师布置的作业题的答案。
软件工程大作业.doc
《软件工程》 大作业 学期: 2015秋季学期 班号: 学号: 姓名: 成绩: 子与信息工程学院 2015年10月 目 录 第1章 系统概述 2 1.1系统介绍 2 1.2开发的目的与意义 2 第2章 可行性分析 4 2.1经济可行性 4 2.2 技术可行性 4 2.3 系统业务分析 5 第3章 需求分析 6 3.1 结构化需求分析 6 3.2 系统需求说明 6 3.3 系统数据流图和数据字典 7 第4章 系统设计 10 4.1 系统ER图 10 4.2 系统功能结构图 12 第5章 系统实现 13 5.1用户模块 13 5.2用户注册功能 14 第6章 系统测试 15 第1章 系统概述 随着计算机网络技术和信息技术的不断发展,人类信息资源实现高度共享,从根本上 改变了人类生活的方式,网上购物作为一种新型的购物方式,以其快捷方便的特点成为 了人们购物的主要选择。网上支付的不断完善,加上物流业的配套建设,使得用户能够 足不出户就能挑选商品,节省了时间和金钱,这种网上购物方式越来越受到人们的青睐 。 1.1系统介绍 网上商城系统是一个可以方便各类人群所需,省时省力全方位的大型购物网上商城,


