软工第二次作业
Github地址
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 60 | 80 |
· Estimate | · 估计这个任务需要多少时间 | 60 | 80 |
Development | 开发 | 415 | 625 |
· Analysis | · 需求分析 (包括学习新技术) | 60 | 90 |
· Design Spec | · 生成设计文档 | 10 | 15 |
· Design Review | · 设计复审 | 10 | 10 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 5 | 5 |
· Design | · 具体设计 | 60 | 70 |
· Coding | · 具体编码 | 90 | 110 |
· Code Review | · 代码复审 | 60 | 25 |
· Test | · 测试(自我测试,修改代码,提交修改) | 120 | 300 |
Reporting | 报告 | 80 | 90 |
· Test Repor | · 测试报告 | 30 | 50 |
· Size Measurement | · 计算工作量 | 20 | 15 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 30 | 25 |
| | 合计 |555 |795
解题思路
统计行数和计算字符数量很简单,用C++的getline每次读取文件的一行存入字符串中,计算size,行数可以直接计算出来,字符数量则需要统计的字符数量加上行数;词频统计相对麻烦一点,先用输入流读取一个字符串,再去判断是否符合单词的要求。
查找资料
将代码按功能划分,主要分成三个函数,一个函数计算行数以及字符数,一个函数统计词频,一个函数对词频进行排序
主要函数的流程图
countLine函数主要计算行数以及字符数量;countWord函数利用文件输入流将单词与分割符分离,将单词和数量益键值对的形式存入undered_map中;WordSort主要将单词的键值对进行排序输入到文件当中。
单元测试弄了好久,无论是未封装的版本,还是已经封装了的版本去编写单元测试,总是出现无法解析的外部符号这个错误,在网上找了好多解决方法,比如将两个obj文件加入外部依赖项等等都没有解决,只好先放着,后面再去查找资料。单元测试没有做成,只好手动测试了,从网上copy了10篇的英文作文,在词云统计上进行统计与我的程序的统计进行对比,两者的对比结果相差无几。
在改进程序性能上至少花了四个小时,刚开始使用的map来记录键值对,但是map的性能不好,测试一篇大约70万词的圣经要用26秒的时间,在性能图上看,在map的查找与插入上花费了一半以上的时间。
后来使用set,用set来记录某个单词是否出现过,若出现过,则在map中加一,若没有则创建一个新的键值对,但是并没有提高多少效率
后来跟乐忠豪同学交流了一下,改用了undered_map来进行键值对的记录,效率直接提高了一半!
直接从之前的26秒到后面的13秒,简直是质的提升。
后面占用最多的是文件的读取占用了一半的时间,去网上查资料也不知道如何改进文件的读取速度,希望有会的大佬指点一下。
首先是记录单词的函数
void countWord(unordered_map<string, int>& words, string filename, ifstream& infile)//统计词频,传入的参数是unordered_map的引用以及文件流的引用
{
unordered_map<string, int>::iterator iter;
infile.open(filename);
string word;
while (infile >> word)//利用文件输入流将字符串与分隔符分离,将字符串输入到word里面,再来判断是否属于单词与统计
{
if (word.size() < 4)
continue;
if ((word[0] >= 'a'&&word[0] <= 'z' || word[0] >= 'A'&&word[0] <= 'Z') && \
(word[1] >= 'a'&&word[1] <= 'z' || word[1] >= 'A'&&word[1] <= 'Z') && \
(word[2] >= 'a'&&word[2] <= 'z' || word[2] >= 'A'&&word[0] <= 'Z') && \
(word[3] >= 'a'&&word[3] <= 'z' || word[3] >= 'A'&&word[3] <= 'Z'))
{
wordNum++;
for (unsigned int i = 0; i < word.size(); i++)
if (word[i] >= 'A'&&word[i] <= 'Z')
word[i] += 32;
words[word]++;
}
}
}
然后就是对键值对的排序,这里使用sort来进行排序,因为map是不能用于排序的,所以将键值对拷贝到vector中再进行排序
这里说一下noexcept,这是在进行代码分析的时候发现的
在C++11中,声明一个函数不可以抛出任何异常使用关键字noexcept.
cmp函数的作用只是来对排序进行规划,而不是实际的调用,所以不会返回任何的异常处理。简而言之,如果你知道你的函数绝对不会抛出任何异常,应该使用noexcept, 而不是throw().
int cmp(const pair<string, int>& a, const pair<string, int>& b) noexcept
{
return a.second > b.second;
}
void wordSort(unordered_map<string, int>& words, ofstream& outfile)
{
unordered_map<string, int>::iterator iter;
vector<pair<string, int>> tmp;
for (iter = words.begin(); iter != words.end(); iter++)
tmp.push_back(pair<string, int>(iter->first, iter->second));
sort(tmp.begin(), tmp.end(), cmp);
for (unsigned int i = 0; i < (tmp.size() < 10 ? tmp.size() : 10); i++)
outfile << "<" << tmp[i].first << ">:" << " " << tmp[i].second << endl;
}
最后是行数和字符数的统计,这个就简单多了,使用getline来获取文件中的每一行,将每一行输入到字符串中,每一行都有一个换行符也算字符,所以总的字符数量应该是characters+lines。
void countLine(ifstream& infile)//统计行数以及字符数
{
string s;
while (getline(infile, s))
{
if (s.size() == 0)
{
characters++;
continue;
}
characters += s.size();
lines++;
}
}
前面的是未封装的函数,后来将函数给封装成dll文件。
新建一个动态链接库(DLL)项目,将所要封装的函数的具体表示形式放进Dll.cpp当中
然后在Dll.h文件当中输入如下
#pragma once
__declspec(dllexport) pair<int, unordered_map<string, int>> countWord(string filename);
__declspec(dllexport) int number_of_word(string filename);
__declspec(dllexport) int cmp(const pair<string, int>& a, const pair<string, int>& b);
__declspec(dllexport) void wordSort(unordered_map<string, int>& words);
__declspec(dllexport) void top_10_words(string filename);
__declspec(dllexport) pair<int, int> countLine(string filename);
__declspec(dllexport) int line(string filename);
__declspec(dllexport) int character(string filename);
点击生成解决方案,就会在原文件夹当中生成dll,lib,.h文件,将他们拷贝到词频统计的项目当中,在资源文件和头文件当中添加现有项.ib和.h文件就能使用封装的函数啦。
总结:
这次的作业真的学到了非常多,代码分析,性能分析器,预编译文件stdafx的使用等等之前没有使用过的,dll的封装以及旧知识的复习,感觉自己还是有很多的不足。还有交流很重要,遇到不懂的要多去问问,集思广益,比自己埋头苦干要有效的多。