一、前言
在本次互评中,我有幸审阅了zjx同学的项目。zjx同学的代码风格严谨,对于代码规范的遵循和对于项目需求的理解都让人印象深刻。以下是我对他的项目的评价和建议。
二、测试
1. 黑盒测试
我首先进行了黑盒测试,也就是从用户的角度,不考虑程序内部结构和属性,只关注程序的输入与输出结果。
(1) 账户管理功能测试
我首先测试了账户管理功能,这包括账户的登录与退出。我尝试创使用不同的用户名和密码进行登录,并尝试了账户退出。当输入错误时会有提示, 而且切换用户登录的功能也能正常工作。
2) 题目生成功能测试
接着,我测试了题目生成功能。这包括了不同难度、不同类型题目的生成。我设定了多种条件进行试验,无论是高中, 小学还是初中, 程序都能生成符合预期的题目。同时,对于不合法的生成条件(超出范围或切换范围错误),程序也能给出正确的错误提示。
生成的题目相当随机, 其中不乏一些根本做不出来的题目.
2. 白盒测试
接着,我进行了白盒测试,也就是从程序员的角度,考虑程序的内部逻辑结构和运行过程。由于zjx同学的代码量较大, 我只选择了最精髓的部分, 即Lex, sort, IsEquivalent
(1) "Lex"函数测试
Lex函数的目的是将输入的字符串 the_question
转换为一个 n-ary 树,类似于语法树。每个树节点代表 the_question
中的一个值或操作符。构建树的方式考虑了操作符的优先级和括号嵌套关系。
// Converts the string `the_question` into a n-ary tree, similar to a syntax tree. // Each node in the tree represents a value or an operator from `the_question`. // The tree is built in a way that respects the operator precedence and bracket nesting in `the_question`. tree::nary_tree<TreeNode> Lex(string the_question) { int index=0,status=0,times=0; stack<int> status_stack; tree::nary_tree<TreeNode> result(TreeNode("root")); auto curr_root=result.GetRoot(); while(++times<200) { string curr_word=NextOperator(status,the_question,index); if (curr_word=="") break; if (status>=0) index+=curr_word.size(); else status=-status-1; if (curr_word==")") { SubLevel(0,status,curr_root,status_stack,false); if (GetNextWord(the_question,index)!="") SubLevel(GetOperatorStatus(GetNextWord(the_question,index)), status, curr_root, status_stack, true); } else { string next_word=GetNextWord(the_question,index); index+=next_word.size(); if (next_word[0]<0x30 || next_word[0]>0x39) {//complex int end_bracket_index=GetEndBracket(the_question,index); string end_string=GetNextWord(the_question,end_bracket_index); if (end_bracket_index==the_question.size() || GetOperatorStatus(end_string)<=status) { curr_root=result.insert(curr_root,TreeNode(curr_word+next_word+")")); status_stack.push(status); if (the_question[index]!='-') status=-1; } else { AddLevel(result,curr_root,curr_word,status_stack,status,index,next_word); } } else { string undetermined_word=GetNextWord(the_question,index); int undetermined_status=GetOperatorStatus(undetermined_word); if (undetermined_word=="" || undetermined_status==status || undetermined_status==-1) { result.insert(curr_root,TreeNode(curr_word,std::stoi(next_word),kLeaf)); } else if (undetermined_status<status) { result.insert(curr_root,TreeNode(curr_word,std::stoi(next_word),kLeaf)); SubLevel(undetermined_status,status,curr_root,status_stack,false); status=GetOperatorStatus(undetermined_word); } else if (undetermined_status>status) { AddLevel(result,curr_root,curr_word,status_stack,status,index,next_word); } } } } return result; }View Code
- 它会逐个读取输入表达式中的字符,分析它们是数字还是操作符。
- 如果是操作符,它会将操作符放入树的适当位置,考虑到了操作符的优先级和括号的影响。
- 如果是数字,它将数字也放入树中,确保树的结构正确反映了数学表达式的含义。
(2) "TestIfLegal"函数解析
函数 TestIfLegal 检查 the_question 是否适用于目前登录的用户。如果问题通过以下所有测试,则被视为适用:1. 操作符与用户所在年级相匹配。 2. 它不包含多个连续的 "^" 操作符。 3. 用户之前生成的题目没有包含它。 如果问题合法,则返回 true;否则返回 false。
bool TestIfLegal(Account* the_account,string the_question) { bool legal=TestIfHasEssentialOperator(the_account->GetType(),the_question); legal|=TestIfMultipleExp(the_question); legal|=TestIfDuplicated(the_account,(the_question[0]=='-')?the_question:("+"+the_question)); return legal; }
(3) "sort_tree"函数解析
sort_tree的目的是对以 root
为根的 n-ary 树的子树进行排序。排序的具体标准没有在函数签名中指定,而是取决于实际的实现。通常,这个函数用于重新排列树中的节点,以便更轻松地进行处理或更高效的计算。
void sort_tree(std::shared_ptr<tree::nary_tree<TreeNode>::Node<TreeNode>> root) { if (!root || root->value.GetType()==kLeaf) return; for (const auto& i:root->children) sort_tree(i); std::sort(root->children.begin(), root->children.end(), [](const std::shared_ptr<tree::nary_tree<TreeNode>::Node<TreeNode>>& a, const std::shared_ptr<tree::nary_tree<TreeNode>::Node<TreeNode>>& b) { if (a->value!=b->value) { return a->value<b->value; } else { if (a->children.size()!=b->children.size()) return a->children.size()<b->children.size(); for (size_t i=0;i<a->children.size();++i) { if (a->children[i]->value!=b->children[i]->value) return a->children[i]->value<b->children[i]->value; } return false; } } ); }View Code
-
首先,函数会检查根节点
root
是否存在且不是叶子节点。如果不存在或者是叶子节点,它将不做任何操作。 -
接着,它会递归地对所有子节点进行排序,确保整棵子树的节点都被处理。
-
最重要的部分是使用
std::sort
函数对子节点进行排序。排序规则由一个函数来定义,这个函数会比较两个节点,决定它们的顺序。 -
比较的规则是:首先,根据节点的值来排序,小的排在前面。如果节点的值相同,就比较它们子节点的数量,少的排在前面。如果子节点数量也相同,就逐个比较它们的子节点,直到找到不同的节点为止。
-
最终,这个函数会将树的子节点按照上述规则重新排列,使得整棵树按照一定的次序组织起来。
三、评价与建议
1. 代码风格和规范
zjx同学的代码风格严谨,遵循了Google的编码规范,包括行数要求、类的继承关系、函数注释、命名规则等。他的代码整洁有序,易于阅读和理解。
在注释方面,zjx同学还细心地为代码编译选项添加了注释:
/** * @file main.cpp * @brief This is the main file. * * This file uses UTF-8 encoding for the source code and GBK encoding * for the execution. When compiling, use the following options: * -finput-charset=UTF-8 -fexec-charset=GBK */
2. nary_tree.h文件
zjx同学编写的这个多叉树文件非常出色,他实现的模板类nary_tree功能强大,显示了他在数据结构和算法设计上的深厚功底。不愧是能手写编译器的人
3. 题目生成方式
zjx同学的题目生成方式新颖,他采用生成后判断合法的方式,真正做到了随机生成. 这种方式更加公平和随机,能够生成更多样化的题目。
虽然zjx同学的项目非常出色,但还有一些可以改进的地方:
zjx同学的main函数缺少退出程序的功能,这可能会在某些情况下导致程序难以结束。建议添加退出程序的功能。
四、总结
这次的互评过程让我对zjx同学的编程技能有了更深的认识。他在项目中展现出的代码规范性、创新性思维以及对需求的深入理解都让人印象深刻。尽管有些小的问题需要改进,但这都不影响他的项目整体质量。我希望这次的互评能对他有所帮助,也期待在未来的学习、工作中,我们能有更多的交流和学习。