# Paired-project **Repository Path**: elysiak/Paired-project ## Basic Information - **Project Name**: Paired-project - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2025-10-18 - **Last Updated**: 2025-10-18 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README |这个作业属于哪个课程|https://edu.cnblogs.com/campus/gdgy/Class34Grade23ComputerScience| | ---- | ---- | |这个作业要求在哪里|https://edu.cnblogs.com/campus/gdgy/Class34Grade23ComputerScience/homework/13479| |这个作业的目标|实现一个自动生成小学四则运算题目的命令行程序| ## 1.github链接 https://gitee.com/elysiak/Paired-project ## 2.psp表格 | PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) | |--------|----------------------------------|------------------|------------------| | Planning | 计划 | 20 | 15 | | Estimate | 估计这个任务需要多少时间 | 20 | 15 | | Development | 开发 | 500 | 558 | | Analysis | 需求分析(包括学习新技术) | 120 | 150 | | Design Spec | 生成设计文档 | 20 | 18 | | Design Review | 设计复审 | 20 | 15 | | Coding Standard | 代码规范(为目前的开发制定合适的规范) | 10 | 5 | | Design | 具体设计 | 25 | 30 | | Coding | 具体编码 | 120 | 150 | | Code Review | 代码复审 | 20 | 30 | | Test | 测试(自我测试,修改代码,提交修改) | 30 | 25 | | Reporting | 报告 | 60 | 70 | | Test Report | 测试报告 | 30 | 30 | | Size Measurement | 计算工作量 | 15 | 15 | | Postmortem & Process Improvement Plan | 事后总结,并提交过程改进计划 | 20 | 20 | | | 合计 | 530 | 588 | ## 3.效能分析 ![屏幕截图 2025-10-16 230041](https://img2024.cnblogs.com/blog/3698451/202510/3698451-20251018204336100-1193387382.png) > 由图可以看出性能瓶颈是exression类中的evaluate函数 > 原来的问题: >- 在乘除法处理阶段,每次删除操作都会导致向量中元素的移动,这在表达式较长时会造成较大的性能开销。 >- 循环中嵌套了删除操作,导致最坏情况下时间复杂度为O(n^2)。 优化后 ![image](https://img2024.cnblogs.com/blog/3698451/202510/3698451-20251018203014706-573001660.png) > ### 优化思路 > 使用两个栈:一个存储操作数(numStack),一个存储运算符(opStack)。 > 遍历表达式中的每个元素(操作数和运算符): >- 遇到操作数,压入操作数栈。 >- 遇到运算符,与运算符栈顶的运算符比较优先级: > 如果栈顶运算符优先级不低于当前运算符,则从操作数栈弹出两个操作数,从运算符栈弹出一个运算符,进行计算,并将结果压入操作数栈。重复此过程直到栈顶运算符优先级低于当前运算符或 栈为空。然后将当前运算符压入运算符栈。 > > 遍历结束后,将运算符栈中剩余的运算符依次弹出并计算,直到运算符栈为空。 > 操作数栈中剩下的最后一个数就是表达式的结果。 ## 4.设计实现过程 类1: Fraction (分数类) 功能:表示和操作分数 方法: - 构造函数(整数、字符串) - 算术运算(+、-、×、÷) - 比较运算(==、<、>、<=、>=、!=) - 简化分数 - 字符串转换 --- 类2: Expression (表达式类) 功能:表示和计算数学表达式 核心方法: - evaluate():表达式求值(双栈算法) - isValid():表达式有效性检查 - toString():表达式字符串表示 --- 类3: ProblemGenerator (题目生成器) 职责:生成随机的有效表达式 核心方法: - generate():生成随机表达式 - generateNumber():生成随机数 - generateOperator():生成随机运算符 --- 类4: Application (应用程序) 功能:协调整个程序流程 核心方法: - generateProblems():生成题目文件 - checkAnswers():检查答案 - parseExpression():解析表达式字符串 - run():主运行逻辑 ## 5.代码说明 ### Fraction
点击查看代码 ``` class Fraction { private: int numerator; int denominator; void simplify() { if (numerator == 0) { denominator = 1; return; } int gcd_val = gcd(std::abs(numerator), denominator); numerator /= gcd_val; denominator /= gcd_val; } int gcd(int a, int b) { return b == 0 ? a : gcd(b, a % b); } public: Fraction(int num = 0, int den = 1) : numerator(num), denominator(den) { if (denominator == 0) denominator = 1; if (denominator < 0) { numerator = -numerator; denominator = -denominator; } simplify(); } Fraction(const std::string& str) { if (str.find('\'') != std::string::npos) { // 带分数格式:a'b/c size_t pos1 = str.find('\''); size_t pos2 = str.find('/'); int integer = std::stoi(str.substr(0, pos1)); int num = std::stoi(str.substr(pos1 + 1, pos2 - pos1 - 1)); int den = std::stoi(str.substr(pos2 + 1)); numerator = integer * den + (integer >= 0 ? num : -num); denominator = den; } else if (str.find('/') != std::string::npos) { // 分数格式:a/b size_t pos = str.find('/'); numerator = std::stoi(str.substr(0, pos)); denominator = std::stoi(str.substr(pos + 1)); } else { // 整数格式 numerator = std::stoi(str); denominator = 1; } simplify(); } std::string toString() const { if (numerator == 0) { return "0"; } if (denominator == 1) { return std::to_string(numerator); } int integer = numerator / denominator; int remainder = std::abs(numerator) % denominator; if (integer == 0) { return std::to_string(numerator) + "/" + std::to_string(denominator); } else if (remainder == 0) { return std::to_string(integer); } else { return std::to_string(integer) + "'" + std::to_string(remainder) + "/" + std::to_string(denominator); } } Fraction operator+(const Fraction& other) const { int num = numerator * other.denominator + other.numerator * denominator; int den = denominator * other.denominator; return Fraction(num, den); } Fraction operator-(const Fraction& other) const { int num = numerator * other.denominator - other.numerator * denominator; int den = denominator * other.denominator; return Fraction(num, den); } Fraction operator*(const Fraction& other) const { int num = numerator * other.numerator; int den = denominator * other.denominator; return Fraction(num, den); } Fraction operator/(const Fraction& other) const { if (other.numerator == 0) { return Fraction(0, 1); } int num = numerator * other.denominator; int den = denominator * other.numerator; return Fraction(num, den); } bool operator==(const Fraction& other) const { return numerator == other.numerator && denominator == other.denominator; } bool operator<(const Fraction& other) const { return numerator * other.denominator < other.numerator * denominator; } // 添加缺失的比较运算符 bool operator>(const Fraction& other) const { return other < *this; } bool operator<=(const Fraction& other) const { return !(other < *this); } bool operator>=(const Fraction& other) const { return !(*this < other); } bool operator!=(const Fraction& other) const { return !(*this == other); } bool isProper() const { return std::abs(numerator) < denominator; } bool isNonNegative() const { return numerator >= 0; } bool isZero() const { return numerator == 0; } }; ```
--- ### Expression
点击查看代码 ``` class Expression { private: std::vector numbers; std::vector operators; // 添加缓存机制 mutable Fraction cachedResult; mutable bool isResultCached; // 预分配栈空间,避免重复分配 mutable std::vector numStack; mutable std::vector opStack; // 定义运算符优先级 int getPrecedence(const std::string& op) const { if (op == "*" || op == "/") return 2; if (op == "+" || op == "-") return 1; return 0; } // 应用运算操作 Fraction applyOperation(const Fraction& left, const Fraction& right, const std::string& op) const { if (op == "+") return left + right; if (op == "-") return left - right; if (op == "*") return left * right; if (op == "/") { if (right.isZero()) return Fraction(0, 1); return left / right; } return Fraction(0, 1); } Fraction evaluate() const { // 如果已经计算过结果,直接返回缓存 if (isResultCached) { return cachedResult; } if (numbers.empty()) { cachedResult = Fraction(0, 1); isResultCached = true; return cachedResult; } // 清空栈但保留容量 numStack.clear(); opStack.clear(); // 处理第一个操作数 numStack.push_back(numbers[0]); // 遍历所有运算符和操作数 for (size_t i = 0; i < operators.size(); i++) { const std::string& currentOp = operators[i]; const Fraction& currentNum = numbers[i + 1]; // 处理运算符优先级:当栈顶运算符优先级不低于当前运算符时,先计算栈顶运算 while (!opStack.empty() && getPrecedence(opStack.back()) >= getPrecedence(currentOp)) { // 弹出并计算 Fraction right = numStack.back(); numStack.pop_back(); Fraction left = numStack.back(); numStack.pop_back(); std::string op = opStack.back(); opStack.pop_back(); Fraction result = applyOperation(left, right, op); numStack.push_back(result); } // 当前运算符和操作数入栈 opStack.push_back(currentOp); numStack.push_back(currentNum); } // 处理栈中剩余的运算 while (!opStack.empty()) { Fraction right = numStack.back(); numStack.pop_back(); Fraction left = numStack.back(); numStack.pop_back(); std::string op = opStack.back(); opStack.pop_back(); Fraction result = applyOperation(left, right, op); numStack.push_back(result); } // 栈顶即为最终结果 cachedResult = numStack.back(); isResultCached = true; return cachedResult; } std::string getDisplayOperator(const std::string& op) const { if (op == "*") return "×"; if (op == "/") return "÷"; return op; } public: Expression(const std::vector& nums, const std::vector& ops) : numbers(nums), operators(ops), isResultCached(false) { // 预分配足够的栈空间 numStack.reserve(numbers.size()); opStack.reserve(operators.size()); } std::string toString() const { std::stringstream ss; for (size_t i = 0; i < numbers.size(); i++) { if (i > 0) { ss << " " << getDisplayOperator(operators[i - 1]) << " "; } ss << numbers[i].toString(); } ss << " = "; return ss.str(); } Fraction getResult() const { return evaluate(); } bool isValid(int range) const { // 检查数值范围 for (const auto& num : numbers) { if (num < Fraction(0) || num >= Fraction(range)) { return false; } } // 检查中间结果非负 try { Fraction result = evaluate(); if (!result.isNonNegative()) return false; } catch (...) { return false; } // 检查除法结果为真分数 for (size_t i = 0; i < operators.size(); i++) { if (operators[i] == "/") { if (i + 1 >= numbers.size()) return false; Fraction left = numbers[i]; Fraction right = numbers[i + 1]; if (right.isZero()) return false; Fraction div_result = left / right; if (!div_result.isProper()) return false; } } return true; } std::string getNormalizedForm() const { std::stringstream ss; for (size_t i = 0; i < numbers.size(); i++) { if (i > 0) { ss << operators[i - 1]; } ss << numbers[i].toString(); } return ss.str(); } // 添加访问器方法用于测试 const std::vector& getNumbers() const { return numbers; } const std::vector& getOperators() const { return operators; } // 清除缓存(如果表达式被修改) void clearCache() { isResultCached = false; } }; ```
--- ### ProblemGenerator
点击查看代码 ``` class ProblemGenerator { private: int range; std::mt19937 gen; Fraction generateNumber() { std::uniform_int_distribution<> dis(1, std::max(1, range - 1)); // 70%概率生成整数,30%概率生成分数 if (range >= 2 && std::uniform_real_distribution<>(0, 1)(gen) < 0.3) { int den = std::uniform_int_distribution<>(2, range)(gen); int num = std::uniform_int_distribution<>(1, den - 1)(gen); return Fraction(num, den); } else { return Fraction(std::uniform_int_distribution<>(0, range - 1)(gen), 1); } } std::string generateOperator() { static std::vector ops = { "+", "-", "*", "/" }; std::uniform_int_distribution<> dis(0, ops.size() - 1); return ops[dis(gen)]; } public: ProblemGenerator(int r) : range(r) { std::random_device rd; gen.seed(rd()); } Expression generate() { int attempts = 0; const int maxAttempts = 1000; while (attempts < maxAttempts) { int opCount = std::uniform_int_distribution<>(1, 3)(gen); std::vector numbers; std::vector operators; for (int i = 0; i <= opCount; i++) { numbers.push_back(generateNumber()); if (i < opCount) { operators.push_back(generateOperator()); } } Expression expr(numbers, operators); if (expr.isValid(range)) { return expr; } attempts++; } // 如果多次尝试都失败,返回一个简单的表达式 return Expression({ Fraction(1), Fraction(1) }, { "+" }); } }; ```
--- ### Application
点击查看代码 ``` class Application { private: void generateProblems(int count, int range) { ProblemGenerator generator(range); std::set generatedExpressions; std::vector problems; std::vector answers; std::ofstream exerciseFile("Exercises.txt"); std::ofstream answerFile("Answers.txt"); if (!exerciseFile.is_open() || !answerFile.is_open()) { std::cerr << "无法打开输出文件!" << std::endl; return; } int generated = 0; const int maxAttempts = count * 10; int attempts = 0; while (generated < count && attempts < maxAttempts) { Expression problem = generator.generate(); std::string normalized = problem.getNormalizedForm(); // 检查重复 if (generatedExpressions.find(normalized) == generatedExpressions.end()) { generatedExpressions.insert(normalized); problems.push_back(problem); answers.push_back(problem.getResult()); generated++; } attempts++; } // 写入文件 for (size_t i = 0; i < problems.size(); i++) { exerciseFile << (i + 1) << ". " << problems[i].toString() << std::endl; answerFile << (i + 1) << ". " << answers[i].toString() << std::endl; } exerciseFile.close(); answerFile.close(); std::cout << "已生成 " << problems.size() << " 道题目到 Exercises.txt 和 Answers.txt" << std::endl; } void checkAnswers(const std::string& exerciseFile, const std::string& answerFile) { std::ifstream exFile(exerciseFile); std::ifstream ansFile(answerFile); if (!exFile.is_open() || !ansFile.is_open()) { std::cerr << "无法打开输入文件!" << std::endl; return; } std::vector correct, wrong; std::string exLine, ansLine; int lineNum = 1; while (std::getline(exFile, exLine) && std::getline(ansFile, ansLine)) { // 提取表达式和答案 size_t exPos = exLine.find(". "); size_t ansPos = ansLine.find(". "); if (exPos != std::string::npos && ansPos != std::string::npos) { std::string expressionStr = exLine.substr(exPos + 2); std::string givenAnswer = ansLine.substr(ansPos + 2); // 移除末尾的 "= " if (expressionStr.find(" = ") != std::string::npos) { expressionStr = expressionStr.substr(0, expressionStr.length() - 3); } try { // 解析表达式并计算 Expression expr = parseExpression(expressionStr); Fraction computedAnswer = expr.getResult(); Fraction givenAnswerFrac(givenAnswer); if (computedAnswer == givenAnswerFrac) { correct.push_back(lineNum); } else { wrong.push_back(lineNum); } } catch (...) { wrong.push_back(lineNum); } } lineNum++; } exFile.close(); ansFile.close(); // 输出统计结果 std::ofstream gradeFile("Grade.txt"); gradeFile << "Correct: " << correct.size() << " ("; for (size_t i = 0; i < correct.size(); i++) { gradeFile << correct[i]; if (i < correct.size() - 1) gradeFile << ", "; } gradeFile << ")" << std::endl; gradeFile << "Wrong: " << wrong.size() << " ("; for (size_t i = 0; i < wrong.size(); i++) { gradeFile << wrong[i]; if (i < wrong.size() - 1) gradeFile << ", "; } gradeFile << ")" << std::endl; gradeFile.close(); std::cout << "统计结果已输出到 Grade.txt" << std::endl; } Expression parseExpression(const std::string& expr) { std::vector numbers; std::vector operators; std::stringstream ss(expr); std::string token; while (ss >> token) { if (token == "+" || token == "-" || token == "×" || token == "÷") { // 将显示用的运算符转换为内部表示 if (token == "×") operators.push_back("*"); else if (token == "÷") operators.push_back("/"); else operators.push_back(token); } else { // 处理数字 numbers.push_back(Fraction(token)); } } return Expression(numbers, operators); } void showHelp() { std::cout << "使用方法:" << std::endl; std::cout << "生成题目: Myapp.exe -n <题目数量> -r <数值范围>" << std::endl; std::cout << "检查答案: Myapp.exe -e <题目文件> -a <答案文件>" << std::endl; std::cout << "示例:" << std::endl; std::cout << " Myapp.exe -n 10 -r 10" << std::endl; std::cout << " Myapp.exe -e Exercises.txt -a Answers.txt" << std::endl; } public: void run(int argc, char* argv[]) { if (argc < 2) { showHelp(); return; } std::map params; for (int i = 1; i < argc; i += 2) { if (i + 1 < argc) { params[argv[i]] = argv[i + 1]; } } if (params.find("-n") != params.end() && params.find("-r") != params.end()) { int count = std::stoi(params["-n"]); int range = std::stoi(params["-r"]); if (count <= 0 || range <= 0) { std::cerr << "参数必须为正整数!" << std::endl; return; } if (count > 10000) { std::cerr << "题目数量不能超过10000!" << std::endl; return; } generateProblems(count, range); } else if (params.find("-e") != params.end() && params.find("-a") != params.end()) { checkAnswers(params["-e"], params["-a"]); } else { std::cerr << "参数错误!" << std::endl; showHelp(); } } }; ```
--- ## 6.测试运行 ### 测试结果 ![屏幕截图 2025-10-16 215439](https://img2024.cnblogs.com/blog/3698451/202510/3698451-20251016215734688-863026146.png) ![image](https://img2024.cnblogs.com/blog/3698451/202510/3698451-20251016215650044-1236905193.png) ### 测试部分例子 ![image](https://img2024.cnblogs.com/blog/3698451/202510/3698451-20251018213829240-1124563978.png) ![image](https://img2024.cnblogs.com/blog/3698451/202510/3698451-20251018214323111-1095564356.png) >1. 计算正确性验证 >- 运算符优先级:确保乘除法优先于加减法 >- 结果验证:每个测试用例都通过手动计算验证 >2. 约束条件 >- 非负结果:所有表达式验证确保不产生负数 >- 真分数除法:除法运算结果是真分数 >- 数值范围:所有数值都在指定范围内 运算符数量:不超过3个运算符 >3. 边界情况处理 >- 零的处理:正确处理零的运算和显示 >- 负分数:负分数的运算和显示无错误 >- 除零保护:防止除零出错