第一次个人编程作业
| 软件工程 | 网络工程1934 |
|---|---|
| 这个作业要求在哪里 | 个人项目 |
| 这个作业的目标 | 学会做一个项目的各个步骤 |
Github目录
不知道为什么不能添加tags和release,初步判断是网络问题导致不能上传(毕竟用的烂电脑)。
现在已经在学校上传了。
| PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
|---|---|---|---|
| Planning | 计划 | ||
| Estimate | 估计这个任务需要多少时间 | 30 | 25 |
| Development | 开发 | ||
| Analysis | 需求分析 (包括学习新技术) | 180 | 150 |
| Design Spec | 生成设计文档 | 60 | 50 |
| Design Review | 设计复审 | 20 | 20 |
| Coding Standard | 代码规范 (为目前的开发制定合适的规范) | 10 | 5 |
| Design | 具体设计 | 30 | 30 |
| Coding | 具体编码 | 90 | 80 |
| Code Review | 代码复审 | 30 | 45 |
| Test | 测试(自我测试,修改代码,提交修改) | 120 | 150 |
| Reporting | 报告 | ||
| Test report | 测试报告 | 60 | 70 |
| Size Measurement | 计算工作量 | 20 | 10 |
| Postmortem & Process Improvement | 事后总结, 并提出过程改进计划 | 30 | 20 |
| Total | 合计 | 680 | 655 |
由于昨天(19号)比完赛之后还帮别人干活,计划有点错乱,而且现在工作的电脑不在手上,很不容易才借到了一台电脑,网络还要贼差劲,文件根本上传不了。
这导致了这个博客(和项目)还需要一点时间完善一下,现在看到的还是一个不完整的博客。
收工啦~
模块接口的设计与实现过程
设计思路
说明一下程序流程,有三个函数,分别是文本切分、特征构建和量度距离,主函数获得文件内容后,先将两个字符串进行文本切分(省略空白字符),然后对得出来的结果放入特征构建之中,再把得到的数组分别进行量度,最后进行简单的运算即可得出相似度。
文本切分
预计原型:
std::vector<std::string> textSegmentation(std::string s)
传入字符串,返回其文本切分后的结果,暂定使用unigram进行分词,视情况也会使用bigram。
特征构建
预计原型:
std::vector<int> vecConstruct(std::vector<std::string>)
传入文本切分后的数组,返回一个向量,暂定使用独热表示方法,即每一个词使用一个独特的长度为1的向量,各向量正交,也就是无关。
量度距离
预计原型:
double calcDistance(std::vector<int>)
传入特征向量,返回其离原点的距离,欧拉距离或者余弦距离都行。
主函数
主函数需要处理参数的文件名,读入字符串,文本切分后对全部文本进行离散化,还有最后通过距离计算相似度。
实现过程
文本切分
都用n-gram分词了还要想啥?直接暴力。
优点:简单易懂,毕竟看了一下这么随便的样例就没必要深度学习了吧。
时间复杂度:\(O(n)\)
空间复杂度:\(O(1)\)
std::vector<std::string> textSegmentation(std::string s)
{
vector<string> res;
int n(s.size());
if (n == 1) res.push_back(s.substr(0, 1));
for (int i = 0; i < n - 1; ++i)
{
res.push_back(s.substr(i, 2)); //bigram
}
return res;
}
特征构建
两个文本都有个桶装着所有词,还有一个集合记录所有词,每一个词独享一个维度,遍历各个桶每个词二分一下找到对应的维度+1即可,最后返回的就是该文本的特征向量。
优点:还是简单易懂,毕竟我比较萌新。
时间复杂度:\(O(nlogn)(离散化)+O(nlogn)(查询+处理)\)
空间复杂度:\(O(n)\)
离散化
for (auto it : vsori) refer.push_back(it);
for (auto it : vsdis) refer.push_back(it); //join two set
std::sort(refer.begin(), refer.end());
refer.erase(std::unique(refer.begin(), refer.end()), refer.end()); //Discretization
查询+处理
std::vector<int> vecConstruct(std::vector<std::string> vec)
{
vector<int> res;
res.resize(refer.size(), 0);
for (auto it : vec)
{
int id(std::lower_bound(refer.begin(), refer.end(), it) - refer.begin()); //get the position of 1 of this charater
if (id >= 0 && id < res.size()) ++res[id];
}
return res;
}
量度距离
欧拉距离,简单来说就是所有基数平方的和开方。
时间复杂度:\(O(n)\)
空间复杂度:\(O(1)\)
代码不用了吧,这不是是个人都会?
主函数
主要是文件读入输出和异常处理,都是些零碎的东西。
性能改进
全局效能

可以看出,时间主要是花费在了std::vector和std::string上,毕竟用的是STL库嘛,方便得来也要付出一点代价的,尝试打开O2优化,还要删除一些琐碎的东西(离散化写复杂了,导致new()和delete()的用量比预期的多了两倍以上)。
文本划分

占用时间最长的部分,因为大量的使用了std::string operator new(),STL库和分配空间速度都比较慢,导致了常数很大。
特征构建

虽然说这部分时间复杂度是最高的,但是因为样例的数据量比较小,\(logn (< 15)\) 比上述的常数小得多,所以时间上比较快。
量度距离

速度最快的一部分,毕竟这里没有怎么用到STL库,而且都是简单的算术运算。
改进后效能
已打开氧气优化

可以看到实际上是快了一倍有多的,这里没有开堆分析,开了堆分析之后是600ms,我发现这个对性能影响很大,不过测试和实际运行也不太一样。

从30%降到了5%左右,可能随着内存的增大,速度会越来越慢。

预估实际运行效果应该差不多,计算部分仍然是<1ms。
单元测试
单元测试的代码放在Github了,因为还没学会如何在单元测试里面给main函数传递参数,所以只分别测试了每一个函数,一共有6个单元测试。
这里提一下其中一个错误。

std::vector operator [] 越界了,给更改向量增加了一个限制条件后通过了:

还有个问题就是VS里用不了中文,如果使用GBK编码的话在测试环境里会乱码,如果使用UTF-8的话就不能用标点符号。所以需要用到比对的都用了英文样例而不是中文样例。
异常处理
其实我直接放在了main里了,毕竟我不大会用cpp的try-catch块(我才不会说cpp里的函数能不throw就不throw呢)。不过这样的话有一些异常应该我没能想到,Java还是挺方便的说。
放一下部分异常处理的代码,错误样例在Github里的Example目录下,写了个批处理文件执行程序。
if (argc < 4) //Exception: Not enough Arguments.
{
puts("Not Enough Arguments!");
return 0;
}
else if (argc > 4) //Exception: Too many Arguments.
{
puts("Too Many Arguments!");
return 0;
}
std::ifstream fori(argv[1]);
if (!fori.is_open()) //Exception: file 1 cannot open.
{
printf("%s is not exist or cannot open!",argv[1]);
return 0;
}
std::ifstream fdis(argv[2]);
if (!fdis.is_open()) //Exception: file 2 cannot open.
{
printf("%s is not exist or cannot open!", argv[2]);
return 0;
}
std::ofstream fout(argv[3]);
if (!fdis.is_open()) //Exception: file 3 cannot open.
{
printf("%s is not exist or cannot open!", argv[3]);
return 0;
}
if (vsori.empty()) //Exception: file 1 is empty.
{
printf("%s is empty!", argv[1]);
return 0;
}
if (vsdis.empty()) //Exception: file 2 is empty.
{
printf("%s is empty!", argv[2]);
return 0;
}
if (ori_0 < 0.0000001) ans = 0.0; //Exception: divide 0

批处理文件的源代码,是有那么一丁点难看的啦……
rem this is a test list.
rem test: No Argument.
C:\Users\juseice\Desktop\streamtest\main.exe
rem test: No output file.
C:\Users\juseice\Desktop\streamtest\main.exe C:\Users\juseice\Desktop\streamtest\f0.txt C:\Users\juseice\Desktop\streamtest\s0.txt
rem test: Wrong input file name.
C:\Users\juseice\Desktop\streamtest\main.exe C:\Users\juseice\Desktop\streamtest\f5000.txt C:\Users\juseice\Desktop\streamtest\s0.txt C:\Users\juseice\Desktop\streamtest\ans0.txt
rem test: Empty file.
C:\Users\juseice\Desktop\streamtest\main.exe C:\Users\juseice\Desktop\streamtest\f0.txt C:\Users\juseice\Desktop\streamtest\s0.txt C:\Users\juseice\Desktop\streamtest\ans0.txt
rem test 1:
C:\Users\juseice\Desktop\streamtest\main.exe C:\Users\juseice\Desktop\streamtest\f1.txt C:\Users\juseice\Desktop\streamtest\s1.txt C:\Users\juseice\Desktop\streamtest\ans1.txt
rem test 2:
C:\Users\juseice\Desktop\streamtest\main.exe C:\Users\juseice\Desktop\streamtest\f2.txt C:\Users\juseice\Desktop\streamtest\s2.txt C:\Users\juseice\Desktop\streamtest\ans2.txt
rem test 3:
C:\Users\juseice\Desktop\streamtest\main.exe C:\Users\juseice\Desktop\streamtest\f3.txt C:\Users\juseice\Desktop\streamtest\s3.txt C:\Users\juseice\Desktop\streamtest\ans3.txt
rem test 4:
C:\Users\juseice\Desktop\streamtest\main.exe C:\Users\juseice\Desktop\streamtest\orig.txt C:\Users\juseice\Desktop\streamtest\orig_0.8_add.txt C:\Users\juseice\Desktop\streamtest\ans_orig_0.8_add.txt
rem test 5:
C:\Users\juseice\Desktop\streamtest\main.exe C:\Users\juseice\Desktop\streamtest\orig.txt C:\Users\juseice\Desktop\streamtest\orig_0.8_del.txt C:\Users\juseice\Desktop\streamtest\ans_orig_0.8_del.txt
rem test 6:
C:\Users\juseice\Desktop\streamtest\main.exe C:\Users\juseice\Desktop\streamtest\orig.txt C:\Users\juseice\Desktop\streamtest\orig_0.8_dis_1.txt C:\Users\juseice\Desktop\streamtest\ans_orig_0.8_dis_1.txt
rem test 7:
C:\Users\juseice\Desktop\streamtest\main.exe C:\Users\juseice\Desktop\streamtest\orig.txt C:\Users\juseice\Desktop\streamtest\orig_0.8_dis_10.txt C:\Users\juseice\Desktop\streamtest\ans_orig_0.8_dis_10.txt
rem test 8:
C:\Users\juseice\Desktop\streamtest\main.exe C:\Users\juseice\Desktop\streamtest\orig.txt C:\Users\juseice\Desktop\streamtest\orig_0.8_dis_15.txt C:\Users\juseice\Desktop\streamtest\ans_orig_0.8_dis_15.txt
pause

浙公网安备 33010602011771号