这个作业属于哪个课程 | 班级的链接 |
---|---|
这个作业要求在哪里 | 作业要求的链接 |
这个作业的目标 | 实现四则运算自动生成程序,结对协作开发 |
姓名 | 学号 |
---|---|
柳浩 | 3122004444 |
洪吉潮 |
PSP表格
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 20 | 25 |
Estimate | 估计这个任务需要多少时间 | 60 | 65 |
Development | 开发 | 200 | 200 |
Analysis | 需求分析 (包括学习新技术) | 20 | 30 |
Design Spec | 生成设计文档 | 50 | 45 |
Design Review | 设计复审 | 30 | 30 |
Coding Standard | 代码规范 (为目前的开发制定合适的规范) | 20 | 20 |
Design | 具体设计 | 30 | 35 |
Coding | 具体编码 | 150 | 160 |
Code Review | 代码复审 | 30 | 35 |
Test | 测试(自我测试,修改代码,提交修改) | 30 | 35 |
Reporting | 报告 | 50 | 60 |
Test Repor | 测试报告 | 20 | 25 |
Size Measurement | 计算工作量 | 10 | 10 |
Postmortem & Process Improvement Plan | 事后总结,并提出过程改进计划 | 30 | 30 |
合计 | 750 | 805 |
- 设计实现过程
代码功能通过三个类去实现:Fraction,Question,Generator
三个类的大致结构如下图: - Fraction类:
Fraction类主要作用是对分数进行管理,提供分数的自动调整和计算接口给上层类调用,如:约分,格式转化(字符流转化为数字存储以及已有分数转化为字符串便于写入文件等),以及分数 之间的运算的实现等,
Fraction类大致结构如下:
class Fraction //分数管理类
{
private:
int numerator; // 分子
int denominator; // 分母
int gcd(int a, int b);// 计算最大公约数,用于约分
void reduce();// 约分分数
public:
std::string get_str_fra();//得到分数的字符串形式
float get_frac()const;//得到分数的浮点数形式
Fraction(int n = 0, int d = 1);//通过传入分子和分母构造Fraction
Fraction(std::string& s);//通过传入string字符串生成分数
Fraction operator+(const Fraction& other) const;//分数加法
Fraction operator-(const Fraction& other) const;//分数减法
Fraction operator*(const Fraction& other) const;//分数乘法
Fraction operator/(const Fraction& other) const;//分数除法
bool operator>(const Fraction& other) const;//重载大于比较
bool operator<(const Fraction& other) const;//重载小于比较
bool operator!=(const Fraction& other) const;//重载不等比较
bool operator==(const Fraction& other) const;//重载等于比较
friend std::ostream& operator<<(std::ostream& os, const Fraction& f);//重载分数的流提取运算符
friend std::istream& operator>>(std::istream& is, Fraction& f);//重载分数的流插入运算符
};
- Question类:
Question类主要作用是对单个题目进行管理,提供比较两个题目是否相同的接口便于上层类进行查重,对读取的字符流转化为相应数据结构进行计算以及将题目转化为字符流便于写入文件。还有单个题目的随机实现接口。
Question类大致结构如下:
class Question //管理一个计算题的类
{
private:
int range;//数值的范围
int num;//计算数字的个数(两个或三个)
bool par_flag;//有无括号
int par_pos;//括号的位置
std::vector<Fraction> data;//存储每个数据
std::vector<int> oper;//存储运算符
std::vector<int> step;//存储运算步骤,用于查重
Fraction result;//存储结果以Fraction类形式
std::vector<std::string> que;//存储题目,以string字符串数组的方式
std::string res;//存储结果,以string的形式
public:
friend std::ostream& operator<<(std::ostream& os, const Question& q);//重载流插入运算符,实现向文件中打印题目
Question(int _range, int _num);//以数据范围和个数生成一个题目
Question(std::vector<std::string>& vs);//从文件中读到的一个题目拆分成的字符串数组构建一个题目
void get_DataOper(std::vector<std::string>& vs);//第二种构造下用于得到数据和运算符
int get_oper(std::string& v);//用于将运算符转化为数字记录
void get_str();//根据题目生成字符串数组que
bool operator==(const Question& other) const;//重载“==”符号用于比较两个题目是否重复
void ran_oper();//生成随机运算符,用于第一种构造
void ran_data();//生成随机数据,用于第二种构造
int GetPri(int ope);//得到运算符的优先级
void get_result();//得到本题结果
void calculate();//生成运算先后步骤,被get_result()调用
Fraction _calculate(Fraction& tmp1, Fraction& tmp2, int st);//计算两个分数的运算结果,被calculate调用
Fraction get_fra_res();
std::string get_str_res();
std::vector<std::string> infixToPostfix(const std::string& infix)const;
bool areEquivalent(const std::string& expr1, const std::string& expr2)const;
int precedence(char op)const;
};
- Generator类
Generator类的主要作用是决定随机生成的题目的数量以及对题目进行判重,对从文件中读取到的内容预处理并交给下层类进行处理,最后得到准确率。将生成的题目和得到的准确度写入文件
Generator类大致结构如下:
class Generator//计算题生成器
{
private:
int number;//题目数量
int range;//题目数据的范围
std::vector<Question> questions;//存档所有题目
std::unordered_multimap<Fraction, Question, MyHash> record;//记录已生成题目,用于查重
std::string qFile;//目标题目写入/读取文件
std::string aFile;//目标答案写入/读取文件
public:
Generator(int _num, int _range, const char* _qfile = "Exercises.txt", const char* _afile = "Answers.txt");//构造函数用于生成题目
Generator(const char* _qfile, const char* _afile, const char* _rfile = "Grade.txt");//构造函数二用于检查错误题目数量
~Generator();//析构函数
void Create();//创建题目
bool judge(Question& q);//判断题目是否已有重复
void Write();//写入目标文件
void Accuracy(const char* rfile);//处理文件读取的内容生成题目计算结果,调用_accuracy()得到精确度
void _accuracy(std::vector<Question>& que, std::vector<Fraction>& ans, const char* rfile);//计算得到精确度
};
main函数:
点击查看代码
#include"arith.h"
int main(int argc, char* argv[])
{
try {
if (argc != 5)
{
std::cout << "输入参数不完整" << std::endl;
return 1;
}
std::vector<std::string> args;
for (int i = 0; i < argc; ++i)
{
args.push_back(std::string(argv[i]));
}
if (args[1] == "-n" || args[1] == "-r")
{
int num, range;
if (args[1] == "-n" && args[3] == "-r")
{
num = std::stoi(args[2]);
range = std::stoi(args[4]);
}
else if (args[3] == "-n" && args[1] == "-r")
{
num = std::stoi(args[4]);
range = std::stoi(args[2]);
}
else
{
std::cout << "输入参数不完整" << std::endl;
return 1;
}
Generator gen(num, range);
std::cout << "生成成功" << std::endl;
}
else if (args[1] == "-e" || args[1] == "-a")
{
int epos, apos;
if (args[1] == "-e" && args[3] == "-a")
{
epos = 2;
apos = 4;
}
else if (args[3] == "-e" && args[1] == "-a")
{
apos = 2;
epos = 4;
}
else
{
std::cout << "输入参数不完整" << std::endl;
return 1;
}
Generator gen(args[epos].c_str(), args[apos].c_str());
std::cout << "判题成功" << std::endl;
}
else
{
std::cout << "输入参数不完整" << std::endl;
return 1;
}
return 0;
}
catch (const std::exception& e)
{
std::cerr << "Exception caught:" << e.what() << std::endl;
}
}
代码流程图如下:
-
性能分析
使用Visual studio内置的性能分析工具得到结果如下:
改进思路:利用生成器,多线程与多进程,异步编程,编译器优化,使用RAII等等方法可以多角度对代码进行优化 -
测试
功能测试:
1.命令行参数不全测试,对于参数缺失的情况进行反馈
2.生成题目测试
3.判断题目正确率测试,第一题为设置的错误答案,结果无误
使用Microsoft测试框架进行单元测试测试:
如图,测试均通过。测试代码如下:
对Fraction类进行测试
4.测试Fraction类自动管理分数能力
TEST_METHOD(TestMethod_get_str_fra)
{
Fraction f(25, 4);
Assert::AreEqual(std::string("6'1/4"), f.get_str_fra());
}
5.测试Fraction类的数值管理正确性
TEST_METHOD(Test_get_frac)
{
int num = 6;
int den = 5;
Fraction f(num, den);
float res = (float)num / (float)den;
Assert::AreEqual(res, f.get_frac());
}
Question类的测试
6.测试自动生成题目能力
TEST_METHOD(TestMethod_ConstructorQuestion1)
{
Question q(10, 3);
Assert::AreEqual(q.range, 10);
Assert::AreEqual(q.num, 3);
Assert::AreEqual((int)q.data.size(), 3);
}
7.测试加减乘除运算的正确性
TEST_METHOD(TestMethod_ADD)
{
std::string s("1. 1/2 + 2/3 =");
std::istringstream iss(s);
std::vector<std::string> words;
std::string word;
while (iss >> word)
{
words.push_back(word);
}
Question q(words);
std::string res("1'1/6");
Fraction f(7, 6);
Assert::IsTrue(res == q.res);
Assert::IsTrue(f == q.result);
}
TEST_METHOD(TestMethod_SUB)
{
std::string s("1. 2/3 - 1/2 =");
std::istringstream iss(s);
std::vector<std::string> words;
std::string word;
while (iss >> word)
{
words.push_back(word);
}
Question q(words);
std::string res("1/6");
Fraction f(1, 6);
Assert::IsTrue(res == q.res);
Assert::IsTrue(f == q.result);
}
TEST_METHOD(TestMethod_MUL)
{
std::string s("1. 1/2 × 2/3 =");
std::istringstream iss(s);
std::vector<std::string> words;
std::string word;
while (iss >> word)
{
words.push_back(word);
}
Question q(words);
std::string res("1/3");
Fraction f(1, 3);
Assert::IsTrue(res == q.res);
Assert::IsTrue(f == q.result);
}
TEST_METHOD(TestMethod_DIV)
{
std::string s("1. 1/2 ÷ 2/3 =");
std::istringstream iss(s);
std::vector<std::string> words;
std::string word;
while (iss >> word)
{
words.push_back(word);
}
Question q(words);
std::string res("3/4");
Fraction f(3, 4);
Assert::IsTrue(res == q.res);
Assert::IsTrue(f == q.result);
}
项目总结:
本次结对项目提升了合作开发的能力,是一次令人受益匪浅的开发经历
具体各方法的实现如下:
Fraction类内方法:
点击查看代码
// 计算最大公约数,用于约分
int Fraction::gcd(int a, int b) {
return b == 0 ? a : gcd(b, a % b);
}
// 约分分数
void Fraction::reduce() {
int divisor = gcd(numerator, denominator);
numerator /= divisor;
denominator /= divisor;
// 确保分母总是正数
if (denominator < 0) {
numerator = -numerator;
denominator = -denominator;
}
}
std::string Fraction::get_str_fra()
{
std::string fra;
if (numerator > denominator)
{
fra += std::to_string(numerator / denominator);
fra.push_back('\'');
}
if (numerator % denominator == 0)
{
if (numerator == denominator)
{
return std::string("1");
}
fra.erase(fra.size() - 1);
return fra;
}
fra += std::to_string(numerator % denominator);
fra.push_back('/');
fra += std::to_string(denominator);
return fra;
}
float Fraction::get_frac()const
{
return (float)numerator / (float)denominator;
}
// 构造函数
Fraction::Fraction(int n , int d ) : numerator(n), denominator(d) {
if (d == 0) {
throw std::invalid_argument("Denominator cannot be zero.");
}
reduce();
}
Fraction::Fraction(std::string& s) : numerator(0), denominator(1)
{
int inte = 0;
size_t pos1 = s.find('\'');
size_t pos2 = s.find('/');
if (pos1 == std::string::npos && pos2 == std::string::npos)
{
std::string integer = s.substr(0);
inte = std::stoi(integer);
numerator += inte * denominator;
}
else if (pos2 != std::string::npos)
{
int begin = 0;
int len = 0;
if (pos1 != std::string::npos)
{
begin = pos1 + 1;
len = pos2 - pos1;
std::string integer = s.substr(0, pos1);
inte = std::stoi(integer);
}
else
{
len = pos2;
}
std::string integer = s.substr(begin, len);
numerator = std::stoi(integer);
integer = s.substr(pos2 + 1);
denominator = std::stoi(integer);
numerator += inte * denominator;
}
}
// 重载加法运算符
Fraction Fraction::operator+(const Fraction& other) const {
if (numerator == 0)
{
return other;
}
if (other.numerator == 0)
{
return *this;
}
return Fraction(numerator * other.denominator + other.numerator * denominator,
denominator * other.denominator);
}
// 重载减法运算符
Fraction Fraction::operator-(const Fraction& other) const {
return Fraction(numerator * other.denominator - other.numerator * denominator,
denominator * other.denominator);
}
// 重载乘法运算符
Fraction Fraction::operator*(const Fraction& other) const {
if (numerator == 0 || other.numerator == 0)
{
return Fraction(0);
}
return Fraction(numerator * other.numerator, denominator * other.denominator);
}
// 重载除法运算符
Fraction Fraction::operator/(const Fraction& other) const {
return Fraction(numerator * other.denominator, denominator * other.numerator);
}
//比较两个分数大小
bool Fraction::operator>(const Fraction& other) const {
return numerator * other.denominator > denominator * other.numerator;
}
//比较两个分数大小
bool Fraction::operator<(const Fraction& other) const {
return !(*this > other);
}
//判断相等
bool Fraction::operator==(const Fraction& other) const {
if (numerator == 0 && other.numerator == 0)
return true;
else
{
return (numerator == other.numerator) && (denominator == other.denominator);
}
}
bool Fraction::operator!=(const Fraction& other) const {
return !(*this == other);
}
// 输出流运算符重载
std::ostream& operator<<(std::ostream& os, const Fraction& f) {
os << f.numerator << "/" << f.denominator;
return os;
}
// 输入流运算符重载
std::istream& operator>>(std::istream& is, Fraction& f) {
is >> f.numerator >> f.denominator;
f.reduce();
return is;
}
点击查看代码
std::ostream& operator<<(std::ostream& os, const Question& q) {
for (auto it : q.que)
{
os << it << " ";
}
return os;
}
Question::Question(int _range, int _num)
:range(_range), num(_num), par_flag(false)
{
ran_data();//生成数字
ran_oper();//生成运算符
get_result();//得到结果
get_str();//生成字符串
}
Question::Question(std::vector<std::string>& vs)
{
if (vs.size() == 5)
{
num = 2;
Fraction d1(vs[1]);
Fraction d2(vs[3]);
data.push_back(d1);
data.push_back(d2);
oper.push_back(get_oper(vs[2]));
}
else
{
num = 3;
get_DataOper(vs);
}
get_result();//得到结果
get_str();//生成字符串
}
void Question::get_DataOper(std::vector<std::string>& vs)
{
int dp1, dp2, dp3, oper1, oper2;
if (vs[1] == "(")
{
par_flag = true;
par_pos = 0;
}
else if (vs[3] == "(")
{
par_flag = true;
par_pos = 1;
}
else
{
par_flag = false;
par_pos = 2;
}
if (par_pos == 0)
{
dp1 = 2;
dp2 = 4;
dp3 = 7;
oper1 = 3;
oper2 = 6;
}
else if (par_pos == 1)
{
dp1 = 1;
dp2 = 4;
dp3 = 6;
oper1 = 2;
oper2 = 5;
}
else{
dp1 = 1;
dp2 = 3;
dp3 = 5;
oper1 = 2;
oper2 = 4;
}
data.push_back(Fraction(vs[dp1]));
data.push_back(Fraction(vs[dp2]));
data.push_back(Fraction(vs[dp3]));
oper.push_back(get_oper(vs[oper1]));
oper.push_back(get_oper(vs[oper2]));
}
int Question::get_oper(std::string& v)
{
if (v == "+")
return 0;
if (v == "-")
return 1;
if (v == "×")
return 2;
if (v == "÷")
return 3;
}
void Question::get_str()
{
if (result == Fraction(0))
{
res = std::string("0");
}
else
res = result.get_str_fra();
for (auto e : data)
{
if (e == Fraction(0))
que.push_back(std::string("0"));
else
que.push_back(e.get_str_fra());
}
auto pos = que.begin() + 1;
for (auto it : oper)
{
switch (it)
{
case ADD:que.insert(pos, std::string("+")); break;
case SUB:que.insert(pos, std::string("-")); break;
case MUL:que.insert(pos, std::string("×")); break;
case DIV:que.insert(pos, std::string("÷")); break;
default:
break;
}
pos = que.begin() + 3;
}
if (par_flag)
{
int left_par, right_par;
if (par_pos == 0)
{
left_par = 0;
right_par = 4;
}
else
{
left_par = 2;
right_par = 6;
}
que.insert(left_par + que.begin(), std::string("("));
que.insert(right_par + que.begin(), std::string(")"));
}
que.emplace(que.end(), std::string("="));
}
//判断相等
bool Question::operator==(const Question& other) const {
std::string q1, q2;
for (int i = 1; i < que.size()-1; ++i)
{
q1 += que[i];
};
for (int i = 1; i < other.que.size() - 1; ++i)
{
q2 += other.que[i];
}
return areEquivalent(q1, q2);
}
std::vector<std::string> Question::infixToPostfix(const std::string& infix) const{
std::stack<char> operators;
std::vector<std::string> postfix;
for (char ch : infix) {
if (isdigit(ch)) {
std::string num(1, ch);
postfix.insert(postfix.end(), num);
}
else if (ch == '(') {
operators.push(ch);
}
else if (ch == ')') {
while (!operators.empty() && operators.top() != '(') {
postfix.push_back(std::string(1, operators.top()));
operators.pop();
}
operators.pop(); // Remove '(' from the stack
}
else {
while (!operators.empty() && precedence(operators.top()) >= precedence(ch)) {
postfix.push_back(std::string(1, operators.top()));
operators.pop();
}
operators.push(ch);
}
}
while (!operators.empty()) {
postfix.push_back(std::string(1, operators.top()));
operators.pop();
}
return postfix;
}
// 比较两个表达式是否等价
bool Question::areEquivalent(const std::string& expr1, const std::string& expr2) const{
auto postfix1 = infixToPostfix(expr1);
auto postfix2 = infixToPostfix(expr2);
return postfix1 == postfix2;
}
// 定义运算符的优先级
int Question::precedence(char op) const{
if (op == '+' || op == '-') return 1;
if (op == '*' || op == '/') return 2;
return 0;
}
void Question::ran_oper()
{
std::mt19937 rng(std::random_device{}());
std::uniform_int_distribution<int> dist(0, range);
if (num == 3 && dist(rng) % 2 == 0)
{
par_flag = true;
par_pos = dist(rng) % 2;
}
int _num = num;
while (--_num != 0)
{
int choice = dist(rng) % 4;
oper.push_back(choice);
}
}
void Question::ran_data()
{
std::mt19937 rng(std::random_device{}());
std::uniform_int_distribution<int> dist(0, range);
std::uniform_int_distribution<int> dist1(1, range);
int _num = num;
while (_num-- != 0)//随机出数字
{
int numerator = dist(rng);
int denominator = dist1(rng);
Fraction f(numerator, denominator);
data.push_back(f);
}
}
int Question::GetPri(int ope)
{
if (ope == ADD || ope == SUB)
return 1;
else
return 2;
}
void Question::get_result()
{
if (num == 2)
{
step.push_back(0);
}
else
{
int prev_step, next_step;
if (par_flag)
{
prev_step = par_pos;
}
else
{
if (GetPri(oper[0]) < GetPri(oper[1]))
{
prev_step = 1;
}
else
{
prev_step = 0;
}
}
next_step = (prev_step + 1) % 2;
step.push_back(prev_step);
step.push_back(next_step);
}
calculate();
}
void Question::calculate()
{
if (num == 2)
{
result = _calculate(data[0], data[1], 0);
}
else if (num == 3) {
std::stack<Fraction> inter;
int i = 0;
inter.push(data[i++]);
inter.push(data[i++]);
if (step[0] == 1)
{
inter.push(data[i++]);
}
Fraction tmp2 = inter.top();
inter.pop();
Fraction tmp1 = inter.top();
inter.pop();
tmp1 = _calculate(tmp1, tmp2, 0);
inter.push(tmp1);
if (i < num)
{
inter.push(data[i++]);
}
tmp2 = inter.top();
inter.pop();
tmp1 = inter.top();
inter.pop();
result = _calculate(tmp1, tmp2, 1);
}
}
Fraction Question::_calculate(Fraction& tmp1, Fraction& tmp2, int st)
{
if (oper[step[st]] == SUB)
{
if (tmp2 > tmp1)
{
oper[step[st]] = ADD;
}
}
if (oper[step[st]] == DIV)
{
if (tmp2 == Fraction(0))
{
oper[step[st]] = MUL;
}
}
switch (oper[step[st]])
{
case ADD:return tmp1 + tmp2;
case SUB:return tmp1 - tmp2;
case MUL:return tmp1 * tmp2;
case DIV:return tmp1 / tmp2;
default:
break;
}
}
Fraction Question::get_fra_res()
{
return result;
}
std::string Question::get_str_res()
{
return res;
}
float MyHash::operator()(const Fraction& res) const {
float result = res.get_frac();
return result;
}
点击查看代码
Generator::Generator(int _num, int _range, const char* _qfile, const char* _afile)
:number(_num), range(_range), qFile(_qfile), aFile(_afile)
{
Create();
}
Generator::Generator(const char* _qfile, const char* _afile, const char* _rfile)
:qFile(_qfile), aFile(_afile)
{
Accuracy(_rfile);
}
Generator::~Generator()
{}
//生成题目和答案
void Generator::Create()
{
int num = number;
while (num-- != 0)
{
std::mt19937 rng(std::random_device{}());
std::uniform_int_distribution<int> dist(2, 3);
int count = dist(rng);
Question q(range, count);
if (judge(q))
{
num++;
continue;
}
else
{
questions.push_back(q);
record.insert({ q.get_fra_res(), q});
}
}
Write();
}
//判断有无相同的题目
bool Generator::judge(Question& q)
{
if (record.count(q.get_fra_res()) != 0)
{
auto range = record.equal_range(q.get_fra_res());
for (auto it = range.first; it != range.second; it++)
{
if (it->second == q)
{
return true;
}
}
}
return false;
}
//将题目和答案写入文件
void Generator::Write()
{
std::ofstream QueFile;
QueFile.open(qFile);
for (int i = 0; i < questions.size(); i++)
{
QueFile << std::to_string(i + 1) << ". " << questions[i] << std::endl;
}
std::ofstream AnsFile;
AnsFile.open(aFile);
for (int i = 0; i < questions.size(); i++)
{
AnsFile << std::to_string(i + 1) << ". " << questions[i].get_str_res() << std::endl;
}
}
//根据题目和答案判断正确率
void Generator::Accuracy(const char* rfile)
{
std::ifstream QueFile(qFile);
std::ifstream ResFile(aFile);
std::string line;
std::vector<Fraction> answers;
while (getline(QueFile, line))
{
std::istringstream iss(line);
std::vector<std::string> words;
std::string word;
while (iss >> word)
{
words.push_back(word);
}
Question q(words);
questions.push_back(q);
}
while (getline(ResFile, line))
{
std::istringstream iss(line);
std::vector<std::string>words;
std::string word;
while (iss >> word)
{
words.push_back(word);
}
Fraction ans(words[1]);
answers.push_back(ans);
}
return _accuracy(questions, answers, rfile);
}
void Generator::_accuracy(std::vector<Question>& que, std::vector<Fraction>& ans,const char* rfile)
{
int right = 0;
int wrong = 0;
std::string rs("(");
std::string ws("(");
for (int i = 0; i < que.size(); i++)
{
std::stringstream ss;
if (que[i].get_fra_res() != ans[i])
{
wrong++;
ss << i + 1;
ws += ss.str();
ws += ",";
}
else
{
right++;
ss << i + 1;
rs += ss.str();
rs += ",";
}
}
if (ws.size() != 1)
ws.erase(ws.end() - 1);
if (rs.size() != 1)
rs.erase(rs.end() - 1);
ws.append(")");
rs.append(")");
std::ofstream OutFile;
OutFile.open(rfile);
OutFile << "Correct:" << right << " " << rs << std::endl;
OutFile << " wrong :" << wrong << " " << ws << std::endl;
};