以下是陶俊同学对冯若轩同学个人项目的代码分析:
整体
冯若轩同学首先对题目的需求非常的清晰。根据题目的要求,设计了database、paper、teacher和file_and_io四个模块,每个模块都各司其职,分别对用户数据读取、试卷生成、教师用户操作以及文件的读取进行了详细的编程,对于伙伴进行代码查看而言非常方便和快速。
模块分析
登录
登录的检测用了一个类似于database的形式的文档,已存在的用户信息都可以从里面获得,这样的一个做法使用户和其类型一一对应,有良好的扩展性,非常有利于软件未来的开发。
// 调用GetAllUser函数,从users.txt中读取全部账号信息
Database::Database() { all_user_ = GetAllUser(); }
// 遍历数据库,如果存在一个用户与输入的用户名和密码均对应,则登陆成功,返回该账号
// 的类型;不成功则返回空类型标志‘N‘
char Database::LoginCheck(const string& username, const string& password) {
int i;
for (i = 0; i < all_user_.size(); i++) {
if (username == all_user_[i].username &&
password == all_user_[i].password) {
return all_user_[i].type;
}
}
return ‘N‘;
}
数学题目的生成
这个部分先从函数符号的预设开始进行,一目了然,通过类型要求,将每个需要的部分预先准备好,放置到一个数组中,最后再将这个数组中的所有元素拼接起来,这样的思想和做法非常好,在错误检测和未来的扩展性上都有了周到的考虑,美中不足的地方就是没有想到特殊的情况,例如角度符号的添加以及九十度角不可以出现这样的情况。
// 加减乘除基本操作符数组
const string operators_basic[4] = {"+", "-", "*", "/"};
// 开根号与开平方初中符号数组
const string operators_middle[2] = {"√", "^2"};
// sin、cos和tan高中符号数组
const string operators_high[3] = {"sin", "cos", "tan"};
// 生成num个大小范围为[min,max]的不重复整数,保存在一个list中
list<int> GenerateRandomList(int min, int max, int num) {
list<int> result;
while (result.size() < num) {
result.push_back(rand() % (max - min + 1) + min);
result.sort();
result.unique(); // 每次只添加1个数,如果有重复,排序后一定是相邻的两个数
}
return result;
}
// 根据需要的类型type,生成不同的试卷。生成时将题目以操作数为基点拆分成组,
// 一个操作数及其前后括号位、前后自操作符号位和基本操作符位为一组
// 根据类型随机生成并逐一填充后,将每位上的子字符串拼接为完整的题目字符串
string MakeQuestion(char type) {
int i, operand_num, random_number;
// bracket_begin为括号起始位,bracket_end为括号终止位
int bracket_begin = -1, bracket_end = -1;
vector<string> result_pieces(200, ""); // 题目拆分为不同的位
bool have_bracket = false;
string result = ""; // 生成的题目字符串
if (type == ‘X‘) {
// 小学试卷有2-5个操作数,随机生成操作数数量
operand_num = rand() % 4 + 2;
result_pieces.resize(4 * operand_num - 1, "");
// 在除最后一个操作数外的每个操作数后随机添加基本操作符
for (i = 0; i < operand_num - 1; i++) {
random_number = rand() % 4;
result_pieces[i * 4 + 3] = operators_basic[random_number];
}
// 随机生成操作数并插入
for (i = 0; i < operand_num; i++) {
random_number = rand() % 100 + 1;
result_pieces[i * 4 + 1] = to_string(random_number);
}
// 操作数大于2的题目有50%的概率存在一个括号
random_number = rand() % 2;
if (random_number && operand_num > 2) {
have_bracket = true;
bracket_begin = rand() % (operand_num - 1);
// 如果括号起始于第一个操作数前,那么就不应结尾在最后一个操作数后,否则无意义
if (bracket_begin == 0) {
bracket_end = rand() % (operand_num - 2) + 1;
} else {
bracket_end =
rand() % (operand_num - 1 - bracket_begin) + bracket_begin + 1;
}
result_pieces[bracket_begin * 4] = "(";
result_pieces[bracket_end * 4 + 2] = ")";
}
} else if (type == ‘C‘) {
list<int> operators_middle_index; // 开平方和平方符号随机生成的位置
list<int>::iterator iter;
// 初中试卷有1-5个操作数,随机生成操作数数量
operand_num = rand() % 5 + 1;
result_pieces.resize(6 * operand_num - 1, "");
// 在除最后一个操作数外的每个操作数后随机添加基本操作符
for (i = 0; i < operand_num - 1; i++) {
random_number = rand() % 4;
result_pieces[i * 6 + 5] = operators_basic[random_number];
}
// 随机生成操作数并插入
for (i = 0; i < operand_num; i++) {
random_number = rand() % 100 + 1;
result_pieces[i * 6 + 2] = to_string(random_number);
}
// 随机生成至少1个,至多操作数个初中操作符,每个操作数至多添加一个初中操作符
// 每个添加位置上添加开平方和平方符号的概率各为50%
operators_middle_index =
GenerateRandomList(0, operand_num - 1, rand() % operand_num + 1);
for (iter = operators_middle_index.begin();
iter != operators_middle_index.end(); ++iter) {
random_number = rand() % 2;
if (random_number == 0) {
result_pieces[(*iter) * 6 + 1] = operators_middle[random_number];
} else {
result_pieces[(*iter) * 6 + 3] = operators_middle[random_number];
}
}
} else {
list<int> operators_high_index; // sin、cos和tan符号随机生成的位置
list<int>::iterator iter;
// 高中试卷有1-5个操作数,随机生成操作数数量
operand_num = rand() % 5 + 1;
result_pieces.resize(6 * operand_num - 1, "");
// 在除最后一个操作数外的每个操作数后随机添加基本操作符
for (i = 0; i < operand_num - 1; i++) {
random_number = rand() % 4;
result_pieces[i * 6 + 5] = operators_basic[random_number];
}
// 随机生成操作数并插入
for (i = 0; i < operand_num; i++) {
random_number = rand() % 100 + 1;
result_pieces[i * 6 + 2] = to_string(random_number);
}
// 随机生成至少1个,至多操作数个高中操作符,每个操作数至多添加一个高中操作符
// 每个添加位置上添加sin、cos或tan符号的概率各为1/3
operators_high_index =
GenerateRandomList(0, operand_num - 1, rand() % operand_num + 1);
for (iter = operators_high_index.begin();
iter != operators_high_index.end(); ++iter) {
random_number = rand() % 3;
result_pieces[(*iter) * 6 + 1] = operators_high[random_number];
}
// 在没有添加高中操作符的操作数上随机添加初中操作符,每个操作数的添加概率为50%,
// 在确定添加时,添加开平方和平方符号的概率各为50%
for (i = 0; i < operand_num; i++) {
random_number = rand() % 2;
if (result_pieces[i * 6 + 1] == "" && random_number == 1) {
random_number = rand() % 2;
if (random_number == 0) {
result_pieces[i * 6 + 1] = operators_middle[random_number];
} else {
result_pieces[i * 6 + 3] = operators_middle[random_number];
}
}
}
}
// 将各位上的子字符串拼接为完整的题目,并加上等于号
for (i = 0; i < result_pieces.size(); i++) {
result += result_pieces[i];
}
result += "=";
return result;
}
文件产生和查重
这个部分这位同学做的非常棒,他将每一处需要做的地方用函数分段处理,即 将文件名称产生、产生在哪个文件夹中、文件夹内所有文件的查看等等功能分开写,一目了然,且功能上实现的非常成功,下面是其从对应用户的对应文件夹中读取所有txt文件的代码,解决的非常漂亮。
// 从该用户对应文件夹中读取所有txt文件,从中读取出全部题目,保存在
// 一个vector容器中并返回。读取所有txt文件通过_findfirst和_findnext
// 实现
vector<string> GetQuestionsFromFile(string name) {
vector<string> all_question;
struct _finddata_t file_info;
long handle;
int symbol_index; // 隔开序号和题目的"、"在整行中的位置下标
FILE* file_ptr;
string path = name + "\\*.txt", file_path = "";
string temp_question, pure_question;
temp_question.resize(200);
// 调用_findfirst()和_findnext()来遍历文件夹中所有.txt后缀的文件
if ((handle = _findfirst(path.c_str(), &file_info)) == -1L) {
all_question.clear();
} else {
file_path = name + "/" + file_info.name;
file_ptr = fopen(file_path.c_str(), "r");
while (fscanf(file_ptr, "%s", &temp_question[0]) != EOF) {
temp_question.resize(strlen(temp_question.c_str()));
symbol_index = temp_question.find("、"); // 找到分隔顿号所在位置
// "、"占2字节,从其后将题目本身与序号分割
if (symbol_index != temp_question.npos) {
pure_question = temp_question.substr(symbol_index + 2);
all_question.push_back(pure_question);
}
temp_question.resize(200);
}
fclose(file_ptr);
// 继续遍历直到没有更多.txt文件,执行上述相同操作
while (!(_findnext(handle, &file_info))) {
file_path = name + "/" + file_info.name;
file_ptr = fopen(file_path.c_str(), "r");
while (fscanf(file_ptr, "%s", &temp_question[0]) != EOF) {
temp_question.resize(strlen(temp_question.c_str()));
symbol_index = temp_question.find("、");
if (symbol_index != temp_question.npos) {
pure_question = temp_question.substr(symbol_index + 2);
all_question.push_back(pure_question);
}
temp_question.resize(200);
}
fclose(file_ptr);
}
}
_findclose(handle);
return all_question;
}
总的来说,我的伙伴的代码是十分令人赏心悦目和满意的,其中功能的完善和细节上都完成的非常好,让我不由感慨,啧啧称赞。其中稍微有些不足的地方就是计算中产生的试卷的本身难度上和更细节的地方可以有更好的展开,但这也是瑕不掩瑜的部分罢了,我非常高兴能够对这样的一个个人项目进行评价,也希望能在接下来的结对项目中从他身上学习到更多。
原文:https://www.cnblogs.com/T-Yebin/p/15350807.html