# 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.效能分析

> 由图可以看出性能瓶颈是exression类中的evaluate函数
> 原来的问题:
>- 在乘除法处理阶段,每次删除操作都会导致向量中元素的移动,这在表达式较长时会造成较大的性能开销。
>- 循环中嵌套了删除操作,导致最坏情况下时间复杂度为O(n^2)。
优化后

> ### 优化思路
> 使用两个栈:一个存储操作数(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.测试运行
### 测试结果


### 测试部分例子


>1. 计算正确性验证
>- 运算符优先级:确保乘除法优先于加减法
>- 结果验证:每个测试用例都通过手动计算验证
>2. 约束条件
>- 非负结果:所有表达式验证确保不产生负数
>- 真分数除法:除法运算结果是真分数
>- 数值范围:所有数值都在指定范围内
运算符数量:不超过3个运算符
>3. 边界情况处理
>- 零的处理:正确处理零的运算和显示
>- 负分数:负分数的运算和显示无错误
>- 除零保护:防止除零出错