math_test_generator1 #1

Merged
hnu202326010224 merged 2 commits from develop into main 4 months ago

@ -0,0 +1,2 @@
# math_test_generator

@ -0,0 +1,18 @@
{
"configurations": [
{
"name": "windows-gcc-x64",
"includePath": [
"${workspaceFolder}/**"
],
"compilerPath": "D:/MinGW/bin/gcc.exe",
"cStandard": "${default}",
"cppStandard": "${default}",
"intelliSenseMode": "windows-gcc-x64",
"compilerArgs": [
""
]
}
],
"version": 4
}

@ -0,0 +1,24 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "C/C++ Runner: Debug Session",
"type": "cppdbg",
"request": "launch",
"args": [],
"stopAtEntry": false,
"externalConsole": true,
"cwd": "d:/软件导论/src",
"program": "d:/软件导论/src/build/Debug/outDebug",
"MIMode": "gdb",
"miDebuggerPath": "gdb",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
}
]
}
]
}

@ -0,0 +1,59 @@
{
"C_Cpp_Runner.cCompilerPath": "gcc",
"C_Cpp_Runner.cppCompilerPath": "g++",
"C_Cpp_Runner.debuggerPath": "gdb",
"C_Cpp_Runner.cStandard": "",
"C_Cpp_Runner.cppStandard": "",
"C_Cpp_Runner.msvcBatchPath": "C:/Program Files/Microsoft Visual Studio/VR_NR/Community/VC/Auxiliary/Build/vcvarsall.bat",
"C_Cpp_Runner.useMsvc": false,
"C_Cpp_Runner.warnings": [
"-Wall",
"-Wextra",
"-Wpedantic",
"-Wshadow",
"-Wformat=2",
"-Wcast-align",
"-Wconversion",
"-Wsign-conversion",
"-Wnull-dereference"
],
"C_Cpp_Runner.msvcWarnings": [
"/W4",
"/permissive-",
"/w14242",
"/w14287",
"/w14296",
"/w14311",
"/w14826",
"/w44062",
"/w44242",
"/w14905",
"/w14906",
"/w14263",
"/w44265",
"/w14928"
],
"C_Cpp_Runner.enableWarnings": true,
"C_Cpp_Runner.warningsAsError": false,
"C_Cpp_Runner.compilerArgs": [],
"C_Cpp_Runner.linkerArgs": [],
"C_Cpp_Runner.includePaths": [],
"C_Cpp_Runner.includeSearch": [
"*",
"**/*"
],
"C_Cpp_Runner.excludeSearch": [
"**/build",
"**/build/**",
"**/.*",
"**/.*/**",
"**/.vscode",
"**/.vscode/**"
],
"C_Cpp_Runner.useAddressSanitizer": false,
"C_Cpp_Runner.useUndefinedSanitizer": false,
"C_Cpp_Runner.useLeakSanitizer": false,
"C_Cpp_Runner.showCompilationTime": false,
"C_Cpp_Runner.useLinkTimeOptimization": false,
"C_Cpp_Runner.msvcSecureNoWarnings": false
}

@ -0,0 +1,29 @@
{
"tasks": [
{
"type": "cppbuild",
"label": "C/C++: gcc.exe build active file",
"command": "D:/MinGW/bin/gcc.exe",
"args": [
"-fdiagnostics-color=always",
"-g",
"${file}",
"-o",
"${fileDirname}\\${fileBasenameNoExtension}.exe",
""
],
"options": {
"cwd": "D:/MinGW/bin"
},
"problemMatcher": [
"$gcc"
],
"group": {
"kind": "build",
"isDefault": true
},
"detail": "Task generated by Debugger."
}
],
"version": "2.0.0"
}

@ -0,0 +1,520 @@
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <map>
#include <set>
#include <random>
#include <ctime>
#include <sstream>
#include <iomanip>
#include <algorithm>
#include <filesystem>
#include <stdexcept>
#include <limits>
#include <cstring>
#include <windows.h>
namespace math_test_generator {
// 用户账户结构体严格匹配文档附表1小学/初中/高中各3个账户
struct UserAccount {
std::string username; // 用户名(如"李四2"
std::string password; // 密码(统一为"123"
std::string user_type; // 账户类型(小学/初中/高中)
};
class MathTestGenerator {
public:
// 构造函数:初始化随机数与预设账户(符合文档初始化需求)
MathTestGenerator() : is_logged_in_(false) {
std::random_device rd;
rng_.seed(rd());
InitializePresetAccounts();
}
// 程序主入口(按文档流程:登录→输入数量→生成/切换→保存)
void Run() {
std::cout << "=== 中小学数学卷子自动生成程序 ===" << std::endl;
while (true) {
if (!is_logged_in_) {
HandleLogin(); // 未登录:处理用户名密码输入
} else {
HandleLoggedInOperations(); // 已登录:处理题目生成
}
}
}
private:
// 成员变量(按文档需求定义)
std::vector<UserAccount> preset_accounts_; // 预设账户文档附表1
UserAccount current_user_; // 当前登录用户
bool is_logged_in_; // 登录状态标记
std::map<std::string, std::set<std::string>> question_history_; // 题目去重历史文档需求3
std::mt19937 rng_; // 随机数生成器
// 常量严格匹配文档需求3、5
static constexpr int kMinQuestionCount = 10; // 最小题目数文档需求3
static constexpr int kMaxQuestionCount = 30; // 最大题目数文档需求3
static constexpr int kExitLoginCode = -1; // 退出登录指令文档需求2
static constexpr int kMaxRetryCount = 1000; // 题目去重最大尝试次数
// 初始化预设账户文档附表1小学3个/初中3个/高中3个
void InitializePresetAccounts() {
// 小学账户
preset_accounts_.push_back({"张三1", "123", "小学"});
preset_accounts_.push_back({"张三2", "123", "小学"});
preset_accounts_.push_back({"张三3", "123", "小学"});
// 初中账户
preset_accounts_.push_back({"李四1", "123", "初中"});
preset_accounts_.push_back({"李四2", "123", "初中"});
preset_accounts_.push_back({"李四3", "123", "初中"});
// 高中账户
preset_accounts_.push_back({"王五1", "123", "高中"});
preset_accounts_.push_back({"王五2", "123", "高中"});
preset_accounts_.push_back({"王五3", "123", "高中"});
}
// 处理登录文档需求1命令行输入用户名密码空格分隔
void HandleLogin() {
std::string username, password;
std::cout << "请输入用户名和密码(用空格隔开): ";
// 处理输入格式错误
while (!(std::cin >> username >> password)) {
std::cin.clear();
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
std::cout << "输入格式错误,请重新输入用户名和密码(用空格隔开): ";
}
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
// 验证登录并反馈文档需求1成功则显示"当前选择为XX出题"
if (VerifyLogin(username, password)) {
std::cout << "登录成功!当前选择为" << current_user_.user_type << "出题" << std::endl;
} else {
std::cout << "请输入正确的用户名、密码" << std::endl;
}
}
// 登录验证文档需求1匹配成功后预创建用户文件夹符合需求5
bool VerifyLogin(const std::string& username, const std::string& password) {
for (const auto& account : preset_accounts_) {
if (account.username == username && account.password == password) {
current_user_ = account;
is_logged_in_ = true;
// 登录成功后立即创建用户文件夹文档需求5每个账号一个文件夹
std::string user_dir = GetUserDirectory();
if (CreateUserDirectory(user_dir)) {
LoadQuestionHistory(); // 加载历史题目文档需求3去重
}
return true;
}
}
return false;
}
// 已登录操作文档需求2/3/4输入数量、切换类型、生成题目
void HandleLoggedInOperations() {
int question_count = 0;
// 提示输入题目数量文档需求2显示"准备生成XX数学题目"
std::cout << "准备生成" << current_user_.user_type
<< "数学题目,请输入生成题目数量(输入-1将退出当前用户重新登录: ";
// 处理非数字输入
while (!(std::cin >> question_count)) {
std::cin.clear();
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
std::cout << "输入格式错误请输入有效数字10-30或-1: ";
}
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
// 退出登录文档需求2输入-1退出
if (question_count == kExitLoginCode) {
Logout();
return;
}
// 验证题目数量文档需求310-30含边界
if (!IsValidQuestionCount(question_count)) {
std::cout << "题目数量有效范围是10-30请重新输入" << std::endl;
return;
}
// 处理类型切换文档需求4输入"切换为XX"切换类型)
std::string command;
std::cout << "请输入命令(直接回车生成题目,或输入'切换为XX'切换类型): ";
std::getline(std::cin, command);
if (!command.empty() && !HandleTypeSwitch(command)) {
std::cout << "切换失败,将按原类型生成题目" << std::endl;
}
// 生成并保存试卷文档需求3/5不重复题目、账号文件夹存储
GenerateAndSaveTestPaper(question_count);
}
// 验证题目数量文档需求3
bool IsValidQuestionCount(int count) const {
return count >= kMinQuestionCount && count <= kMaxQuestionCount;
}
// 处理类型切换文档需求4仅支持小学/初中/高中)
bool HandleTypeSwitch(const std::string& command) {
const std::string kSwitchPrefix = "切换为";
// 检查命令格式
if (command.substr(0, kSwitchPrefix.size()) != kSwitchPrefix) {
std::cout << "请输入小学、初中和高中三个选项中的一个" << std::endl;
return false;
}
// 提取并验证目标类型
std::string target_type = command.substr(kSwitchPrefix.size());
if (target_type != "小学" && target_type != "初中" && target_type != "高中") {
std::cout << "请输入小学、初中和高中三个选项中的一个" << std::endl;
return false;
}
// 切换类型并提示文档需求4
current_user_.user_type = target_type;
std::cout << "系统提示:准备生成" << target_type << "数学题目,请输入生成题目数量" << std::endl;
return true;
}
// 生成试卷并保存文档需求3/5
void GenerateAndSaveTestPaper(int question_count) {
std::vector<std::string> questions;
int retry_count = 0;
// 生成不重复题目文档需求3同一老师题目不重复
while (questions.size() < static_cast<size_t>(question_count) && retry_count < kMaxRetryCount) {
std::string question = GenerateQuestionByType(current_user_.user_type);
if (!IsQuestionDuplicate(question)) {
questions.push_back(question);
AddQuestionToHistory(question);
}
retry_count++;
}
// 处理生成不足问题
if (questions.size() < static_cast<size_t>(question_count)) {
std::cerr << "警告:无法生成足够的不重复题目,仅生成" << questions.size() << "" << std::endl;
}
// 保存题目文档需求5按格式保存到账号文件夹
if (SaveQuestionsToFile(questions)) {
std::cout << "题目生成完成,已保存到文件" << std::endl;
} else {
std::cerr << "错误:题目保存失败" << std::endl;
}
}
// 按类型生成题目文档附表2小学/初中/高中难度差异)
std::string GenerateQuestionByType(const std::string& user_type) {
if (user_type == "小学") return GeneratePrimaryQuestion();
if (user_type == "初中") return GenerateJuniorQuestion();
return GenerateSeniorQuestion(); // 高中
}
// 小学题目文档附表2仅+,-,*,/和()操作数1-100
std::string GeneratePrimaryQuestion() {
const int operand_count = GenerateRandomInt(1, 5); // 1-5个操作数文档需求6
std::vector<int> operands;
for (int i = 0; i < operand_count; i++) {
operands.push_back(GenerateRandomInt(1, 100)); // 操作数范围1-100文档需求6
}
const std::vector<std::string> kOps = {"+", "-", "*", "/"};
std::vector<std::string> selected_ops;
for (int i = 0; i < operand_count - 1; i++) {
selected_ops.push_back(kOps[GenerateRandomInt(0, 3)]);
}
// 构建题目50%概率加括号文档附表2
std::stringstream ss;
if (operand_count > 2 && GenerateRandomInt(0, 1)) {
const int paren_start = GenerateRandomInt(0, operand_count - 3);
ss << operands[0];
for (int i = 1; i < operand_count; i++) {
if (i == paren_start + 1) ss << selected_ops[i-1] << "(" << operands[i];
else if (i == paren_start + 2) ss << selected_ops[i-1] << operands[i] << ")";
else ss << selected_ops[i-1] << operands[i];
}
} else {
ss << operands[0];
for (int i = 1; i < operand_count; i++) {
ss << selected_ops[i-1] << operands[i];
}
}
ss << " = ?";
return ss.str();
}
// 初中题目文档附表2含平方/开根号至少1个特殊运算符
std::string GenerateJuniorQuestion() {
std::string question;
while (true) {
const int operand_count = GenerateRandomInt(1, 5);
std::vector<int> operands;
for (int i = 0; i < operand_count; i++) {
operands.push_back(GenerateRandomInt(1, 100));
}
const std::vector<std::string> kOps = {"+", "-", "*", "/", "^2", ""};
bool has_special = false; // 标记是否含平方/开根号
std::stringstream ss;
// 处理第一个操作数(可能带特殊运算符)
const int first_op = GenerateRandomInt(0, 2);
if (first_op == 1) { ss << operands[0] << "^2"; has_special = true; }
else if (first_op == 2) { ss << "" << operands[0]; has_special = true; }
else ss << operands[0];
// 处理后续操作数
for (int i = 1; i < operand_count; i++) {
const std::string op = kOps[GenerateRandomInt(0, 5)];
if (op == "^2" || op == "") has_special = true;
ss << op;
if (op == "") ss << operands[i];
else { ss << operands[i]; if (op == "^2") ss << "^2"; }
}
ss << " = ?";
question = ss.str();
if (has_special) break; // 确保含特殊运算符文档附表2
}
return question;
}
// 高中题目文档附表2含sin/cos/tan至少1个三角函数
std::string GenerateSeniorQuestion() {
std::string question;
while (true) {
const int operand_count = GenerateRandomInt(1, 5);
std::vector<int> operands;
for (int i = 0; i < operand_count; i++) {
operands.push_back(GenerateRandomInt(0, 180)); // 角度0-180°
}
const std::vector<std::string> kOps = {"+", "-", "*", "/", "sin", "cos", "tan"};
bool has_trig = false; // 标记是否含三角函数
std::stringstream ss;
// 第一个操作数必为三角函数确保至少一个文档附表2
const std::string trig_op = kOps[GenerateRandomInt(4, 6)];
ss << trig_op << "(" << operands[0] << "°)";
has_trig = true;
// 处理后续操作数
for (int i = 1; i < operand_count; i++) {
const std::string op = kOps[GenerateRandomInt(0, 6)];
if (op == "sin" || op == "cos" || op == "tan") {
ss << op << "(" << operands[i] << "°)";
} else {
ss << op << operands[i];
}
}
ss << " = ?";
question = ss.str();
if (has_trig) break; // 确保含三角函数文档附表2
}
return question;
}
// 题目去重文档需求3基于用户历史记录
bool IsQuestionDuplicate(const std::string& question) const {
const auto it = question_history_.find(current_user_.username);
return it != question_history_.end() && it->second.count(question);
}
// 添加题目到历史记录文档需求3
void AddQuestionToHistory(const std::string& question) {
question_history_[current_user_.username].insert(question);
}
// 加载历史题目文档需求3从账号文件夹读取已有文件
void LoadQuestionHistory() {
const std::string user_dir = GetUserDirectory();
if (!std::filesystem::exists(user_dir)) return;
// 遍历账号文件夹下所有txt文件文档需求5
for (const auto& entry : std::filesystem::directory_iterator(user_dir)) {
if (entry.is_regular_file() && entry.path().extension() == ".txt") {
LoadQuestionsFromFile(entry.path().string());
}
}
}
// 从文件加载题目到历史记录文档需求3
void LoadQuestionsFromFile(const std::string& file_path) {
std::ifstream file(file_path);
if (!file.is_open()) {
std::cerr << "警告:无法读取历史文件" << file_path << std::endl;
return;
}
std::string line;
while (std::getline(file, line)) {
if (line.empty()) continue; // 跳过空行文档需求5
const size_t dot_pos = line.find('.');
if (dot_pos != std::string::npos) {
// 提取题目内容(去除题号)
std::string q = line.substr(dot_pos + 1);
q.erase(0, q.find_first_not_of(" \t"));
q.erase(q.find_last_not_of(" \t") + 1);
if (!q.empty()) AddQuestionToHistory(q);
}
}
file.close();
}
// 保存题目到文件(核心修复:解决中文路径编码与错误码判断)
bool SaveQuestionsToFile(const std::vector<std::string>& questions) {
const std::string user_dir = GetUserDirectory();
// 确保用户文件夹存在文档需求5
if (!std::filesystem::exists(user_dir)) {
std::cerr << "错误:用户文件夹" << user_dir << "不存在" << std::endl;
return false;
}
// 生成合规文件名文档需求5年-月-日-时-分-秒.txt
const std::string timestamp = GetCurrentTimestamp();
const std::string rel_path = user_dir + timestamp + ".txt";
// 转为Windows宽字符路径修复中文编码问题
std::wstring wide_path = Utf8ToWide(rel_path);
// 使用Windows API创建文件避免C++标准库中文路径问题)
HANDLE file_handle = CreateFileW(
wide_path.c_str(),
GENERIC_WRITE,
0,
nullptr,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
nullptr
);
if (file_handle == INVALID_HANDLE_VALUE) {
std::cerr << "错误:无法创建文件" << rel_path
<< ",系统错误码:" << GetLastError() << std::endl;
return false;
}
// 按文档需求5写入题目带题号、每题空一行
std::string content;
for (size_t i = 0; i < questions.size(); i++) {
content += std::to_string(i + 1) + ". " + questions[i] + "\r\n";
if (i < questions.size() - 1) content += "\r\n"; // 题目间空一行
}
// 写入文件UTF-8编码避免中文乱码
DWORD bytes_written = 0;
bool write_ok = WriteFile(
file_handle,
content.c_str(),
static_cast<DWORD>(content.size()),
&bytes_written,
nullptr
);
if (!write_ok || bytes_written != content.size()) {
std::cerr << "错误:文件写入失败,系统错误码:" << GetLastError() << std::endl;
CloseHandle(file_handle);
return false;
}
CloseHandle(file_handle);
std::cout << "文件保存成功,路径:" << rel_path << std::endl;
return true;
}
// 获取用户文件夹路径文档需求5用户名/
std::string GetUserDirectory() const {
return current_user_.username + "/";
}
// 【核心修复】创建用户文件夹:解决中文路径与错误码判断
bool CreateUserDirectory(const std::string& dir_path) const {
// 转为Windows宽字符路径修复中文编码问题
std::wstring wide_dir = Utf8ToWide(dir_path);
// 使用Windows API创建目录避免C++标准库中文问题)
if (CreateDirectoryW(wide_dir.c_str(), nullptr)) {
std::cout << "成功:创建用户文件夹" << dir_path << std::endl;
return true;
} else {
DWORD err_code = GetLastError();
// 错误码183目录已存在视为成功文档允许复用文件夹
if (err_code == ERROR_ALREADY_EXISTS) {
std::cout << "提示:用户文件夹" << dir_path << "已存在" << std::endl;
return true;
}
// 其他错误(如权限不足)
std::cerr << "错误:创建文件夹" << dir_path << "失败,系统错误码:" << err_code << std::endl;
return false;
}
}
// 【辅助修复】UTF-8转Windows宽字符解决中文路径编码
std::wstring Utf8ToWide(const std::string& utf8_str) const {
int wide_len = MultiByteToWideChar(
CP_UTF8,
0,
utf8_str.c_str(),
-1,
nullptr,
0
);
if (wide_len <= 0) return L"";
std::wstring wide_str(wide_len, 0);
MultiByteToWideChar(
CP_UTF8,
0,
utf8_str.c_str(),
-1,
&wide_str[0],
wide_len
);
return wide_str;
}
// 获取时间戳文档需求5年-月-日-时-分-秒,无非法字符)
std::string GetCurrentTimestamp() const {
const auto now = std::time(nullptr);
const auto local = *std::localtime(&now);
std::stringstream ss;
ss << std::put_time(&local, "%Y-%m-%d-%H-%M-%S"); // 无冒号符合Windows命名
return ss.str();
}
// 退出登录文档需求2
void Logout() {
is_logged_in_ = false;
current_user_ = UserAccount();
std::cout << "已退出当前用户,请重新登录" << std::endl;
}
// 生成随机整数
int GenerateRandomInt(int min, int max) {
std::uniform_int_distribution<int> dist(min, max);
return dist(rng_);
}
};
} // namespace math_test_generator
// 程序入口设置控制台UTF-8编码解决中文显示
int main() {
#ifdef _WIN32
SetConsoleOutputCP(CP_UTF8);
SetConsoleCP(CP_UTF8);
#endif
try {
math_test_generator::MathTestGenerator generator;
generator.Run();
} catch (const std::exception& e) {
std::cerr << "程序异常退出:" << e.what() << std::endl;
return 1;
}
return 0;
}

Binary file not shown.
Loading…
Cancel
Save