第一次个人编程作业

作业概述

这个作业属于哪个课程 班级链接
这个作业要求在哪里 作业链接
这个作业的目标 实现论文查重作业,掌握单元测试,以及commit语法

一.github作业链接

链接

二.PSP表格

这个作业属于哪个课程 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
Planning 计划 20 30
·Estimate 估计这个任务需要多少时间 250 270
Development 开发 100 120
·Analysis ·需求分析(包括学习新技术) 30 25
·Design Sepc ·生成设计文档 15 14
·Design Review ·设计复审 20 25
·Coding Standard ·代码规范(为目前的开发制定 合适的规范) 60 45
·Design ·具体设计 20 25
·Coding ·具体编码 150 160
·Coding Review ·代码复审 40 45
·Test ·测试(自我测试,修改代码,提交修改) 50 60
Reporting 报告 30 40
·Test Report ·测试报告 60 65
·Size Measurement ·计算工作量 20 25
·Postmortem & Process Improvement Plan ·事后总结,并提出进程修改计划 30 35
·合计 895 984

三.实现思路

程序使用KMP算法在第一个文本中寻找第二个文本的子串,KMP算法通过构建部分匹配表(Partial Match Table)来避免不必要的回溯,提高字符串匹配的效率,然后程序使用动态规划算法计算两个文本之间的编辑距离。动态规划算法通过填充一个二维数组来记录状态值,其中dp[i][j]表示将第一个文本的前i个字符转换为第二个文本的前j个字符所需的最少操作次数。
具体计算步骤如下:

  1. 初始化数组的第一行和第一列,表示将一个空字符串转换为另一个字符串所需的操作次数。
  2. 从第二行和第二列开始,遍历数组的每个元素。
  3. 如果第一个文本的第i个字符等于第二个文本的第j个字符,则dp[i][j]等于dp[i-1][j-1],表示不需要进行任何操作。
  4. 如果第一个文本的第i个字符不等于第二个文本的第j个字符,则dp[i][j]等于左边、上边和左上角三个元素中的最小值加1,表示需要进行插入、删除或替换操作。
  5. 最后,dp[len1][len2]即为两个文本之间的编辑距离,其中len1和len2分别表示两个文本的长度。

四.模块实现

chesischeck类的重要函数:

  • 传入命令行参数
    ChesisCheck(const std::string& origTxtPath, const std::string& orig_addTxtPath, const std::string& ansTxtPath);
  • 计算编辑距离
    float CalEditDIstance(const wstring& str1,const wstring& str2);
  • 计算运行时间
    void CalRunTime();

main函数

int main(int argc,char*argv[])
{
	ChesisCheck chesischeck(argv[1],argv[2],argv[3]);
	chesischeck.Init(argc,  argv);
	chesischeck.Start();

	return 0;
}

计算编辑距离

float CalEditDIstance(const wstring& str1,const wstring& str2)
{
	int len1 = str1.size();
	int len2 = str2.size();

	vector<vector<int>> dp(len1 + 1, vector<int>(len2 + 1));

	// KMP 算法
	vector<int> lps(len1);
	int len = 0;
	int i = 1;
	while (i < len1) {
		if (static_cast<int>(str1[i]) == static_cast<int>(str1[len])) {
			len++;
			lps[i] = len;
			i++;
		}
		else {
			if (len != 0) {
				len = lps[len - 1];
			}
			else {
				lps[i] = 0;
				i++;
			}
		}
	}
	for (int i = 1; i <= len1; i++) {
		for (int j = 1; j <= len2; j++) {
			if (static_cast<int>(str1[i - 1]) == static_cast<int>(str2[j - 1])) {
				dp[i][j] = dp[i - 1][j - 1];
			}
			else {
				dp[i][j] = min(min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1;
			}
		}
	}
	return dp[len1][len2];
}

程序流程图

性能测试

最初性能

最初函数用时

优化性能思路

采用智能指针来优化内存管理,同时将utf-8码转换成Unicode码,初始版本未采用kmp+动态规划算法进行设计,现采用此方法提高效率。

优化后性能

优化后函数用时

代码覆盖率

未覆盖的代码是控制异常代码

运行结果

单元测试

测试两个相同的字符之间求的编辑距离是否为0

TEST_METHOD(TestMethod4)
{
	ChesisCheck chesischeck;
	string str1 = "abc";
	string str2 = "abc";
	wstring wstr1 = chesischeck.TransUTF8toUnicode(str1);
	wstring wstr2 = chesischeck.TransUTF8toUnicode(str2);
	float res = chesischeck.CalEditDIstance(wstr1, wstr2);
	Assert::AreEqual(res, (float)0, L"testwarning");
}

测试转化“。”时得到的字符串是否为空

TEST_METHOD(TestMethod3)
{
	ChesisCheck chesischeck;
	string s1 = "。";
	wstring w = chesischeck.TransUTF8toUnicode(s1);
	bool isEmpty = w.empty();
	Assert::AreEqual(isEmpty, (bool)false, L"testwarning");
}

测试两个相同的字符之间求的运算时间是否趋近于0

TEST_METHOD(TestMethod5)
{
	clock_t start, end;
	start = clock();
	ChesisCheck chesischeck;
	string str1 = "abc";
	string str2 = "abc";
	float ans = 0;
	wstring wstr1 = chesischeck.TransUTF8toUnicode(str1);
	wstring wstr2 = chesischeck.TransUTF8toUnicode(str2);	

	end = clock();
	ans = 1.0 * (end - start) / CLOCKS_PER_SEC;
	Assert::AreEqual(ans, (float)0, L"testwarning");
}

异常处理

输入文件不对,命令参数不对

ChesisCheck::ChesisCheck(const std::string& origTxtPath, const std::string& orig_addTxtPath, const std::string& ansTxtPath) :
	origTxt(origTxtPath), orig_addTxt(orig_addTxtPath), ansTxt(ansTxtPath, std::ios::out | std::ios::app),
	origTxtPath(origTxtPath), orig_addTxtPath(orig_addTxtPath), ansTxtPath(ansTxtPath)
{
	destroy = true;
	if (!origTxt.is_open() || !orig_addTxt.is_open() || !ansTxt.is_open()) {
	
		cerr << "文件打开失败,请检查文件路径。" << std::endl;
		throw runtime_error("文件打开失败");
		
	}
}

进行文件读取时,文件不可读

float ChesisCheck::CalEditDIstance(const wstring& str1, const wstring& str2)
{
	try {
		auto len1 = str1.size();
		auto len2 = str2.size();

		vector<vector<int>> dp(len1 + 1, vector<int>(len2 + 1));

		// KMP 算法
		vector<int> lps(len1);
		int len = 0;
		int i = 1;
		while (i < len1) {
			if (static_cast<int>(str1[i]) == static_cast<int>(str1[len])) {
				len++;
				lps[i] = len;
				i++;
			}
			else {
				if (len != 0) {
					len = lps[len - 1];
				}
				else {
					lps[i] = 0;
					i++;
				}
			}
		}
		for (int i = 1; i <= len1; i++) {
			for (int j = 1; j <= len2; j++) {
				if (static_cast<int>(str1[i - 1]) == static_cast<int>(str2[j - 1])) {
					dp[i][j] = dp[i - 1][j - 1];
				}
				else {
					dp[i][j] = min(min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1;
				}
			}
		}

		return dp[len1][len2];
	}
	catch (const std::exception& e) {
		std::cerr << "发生异常: " << e.what() << std::endl;
		// 处理异常情况,例如返回一个特定的错误码或抛出新的异常
		throw;
	}
}

转码失败

wstring ChesisCheck::TransUTF8toUnicode(const string& str)
{
	wstring ans;
	int size = MultiByteToWideChar(CP_UTF8, 0, str.c_str(), -1, NULL, 0);
	if (size > 0) {
		unique_ptr<wchar_t[]> buffer(new wchar_t[size]);
		if (MultiByteToWideChar(CP_UTF8, 0, str.c_str(), -1, buffer.get(), size) > 0) {
			ans = buffer.get();
		}
		else {
			// 转换失败,抛出异常或者进行其他错误处理
			throw runtime_error("转换UTF-8到Unicode失败");
		}
	}
	else {
		// 转换失败,抛出异常或者进行其他错误处理
		throw runtime_error("获取转换后字符串长度失败");
	}
	return ans;
}

注:运行文件时要么采用cmd的命令参数输入,要么在vs中手动输入命令行参数,否则程序会报错无法执行!

posted @ 2023-09-15 21:15  只因你  阅读(70)  评论(0)    收藏  举报