软工实践第二次作业
软工实践第二次作业
PSP表格
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 20 | 10 |
• Estimate | • 估计这个任务需要多少时间 | 20 | 10 |
Development | 开发 | 160 | 240 |
• Analysis | • 需求分析 (包括学习新技术) | 30 | 25 |
• Design Spec | • 生成设计文档 | 10 | 5 |
• Design Review | • 设计复审 | 10 | 5 |
• Coding Standard | • 代码规范 (为目前的开发制定合适的规范) | 10 | 5 |
• Design | • 具体设计 | 10 | 10 |
• Coding | • 具体编码 | 40 | 120 |
• Code Review | • 代码复审 | 20 | 30 |
• Test | • 测试(自我测试,修改代码,提交修改) | 30 | 40 |
Reporting | 报告 | 20 | 30 |
• Test Repor | • 测试报告 | 10 | 15 |
• Size Measurement | • 计算工作量 | 10 | 5 |
• Postmortem & Process Improvement Plan | • 事后总结, 并提出过程改进计划 | 15 | 10 |
合计 | 200 | 280 |
解题思路描述
看到题目的时候的直觉算法是直接从头到尾遍历一遍,每个字符向后遍历四个四个字符判断是否为英文字母,如果是则继续遍历字符将英文符号和数字添加进这个单词。继续思考和阅读需求,需要封装各个功能,为了避免多次打开文件读取造成的性能劣化,考虑将文本读取出存为一个字符串s,统计有效行数可以通过读取s中的换行符得到(需要额外处理EOF前无换行符的情况),因为输入中没有中文,所以统计字符数可以用s.length()
得到,单词和出现次数可以使用map<string,int>
来存储,关于单词的处理我的思路是从头到尾遍历,每次循环开始丢掉非字母数字(即i++),之后对出现的英文数字判断是否为四个字母开头,如果是则找到一个单词,如果不是则将这一串字母数字丢掉(i++),本次循环结束。
设计实现过程
类的定义只有一个,文件处理工具类FileTools
,内部函数为
static int count(map<string, int> mp)//计算单词总数;
static pair<int,int> countLine(string s)//计算行数;
static string getString(ifstream& in)//得到文本的字符串;
static bool isLetter(char ch)//是否为英文字母;
static bool isDigit(char ch)//是否为数字;
static vector<pair<int,string> > getSort(map<string, int> mp)//排序;
static map<string, int> countWord(string s)//得到单词映射表;
首先是通过已经open()
的ifstream
得到文本字符串,其次用这个s得到单词映射表和行数,通过单词映射表可以得到排前10的单词和单词总数。单元测试列举了一些简单的例子和假设文本无法打开的情况。
改进的思路
我在改进程序性能上的时间花费的较少,大约在半小时左右,原先是打算每个字符都向后遍历四个字符的,这样的时间是4n,后来发现如果判断不是四个英文字母开头的话其实后面的英文数字都不用考虑了,时间基本为O(n)的。基本无法再优化了,毕竟要处理字符串的话这从头到尾扫一遍的O(n)是少不了的。
最终的结果是每10万字符需要处理约1s的时间。感觉还有一些细节处是可以优化的。
代码说明
- 单元测试
namespace UnitTest
{
TEST_CLASS(UnitTest1)
{
public:
TEST_METHOD(TestMethod1)
{
//通过文件读入测试样例
char filename[] = "test1.txt";
ifstream in;
in.open(filename);
string s = FileTools::getString(in);
Assert::AreEqual(s, (string)"abcd");
}
TEST_METHOD(TestMethod2)
{
//假设文件打开失败的情况
ifstream in;
string s = FileTools::getString(in);
Assert::AreEqual(s, (string)"");
}
TEST_METHOD(TestMethod3)
{
//另一个测试样例
ifstream in;
char name[] = "test2.txt";
in.open(name);
string s = FileTools::getString(in);
pair<int,int> p = FileTools::countLine(s);
map<string, int> mp = FileTools::countWord(s);
vector<pair<int,string> > v = FileTools::getSort(mp);
Assert::AreEqual((int)s.length(), 58);
Assert::AreEqual(p.first, 4);
Assert::AreEqual(p.second, 3);
Assert::AreEqual(v[0].second, (string)"aaaa");
Assert::AreEqual(v[0].first, 2);
Assert::AreEqual(v[1].second, (string)"abcd123");
Assert::AreEqual(v[2].second, (string)"bbbb");
Assert::AreEqual((int)v.size(), 3);
Assert::AreEqual(FileTools::count(mp), 5);
}
}
- 核心代码
map<string, int> FileTools::countWord(string s)
{
map<string, int> mp;//单词-频率映射表
string temp;//用于存储单词
for (int i = 0; i < (int)s.length()-3; i++)
{
//剔出非字母数字的字符
while (i<(int)s.length()&&!isDigit(s[i]) && !isLetter(s[i]))
{
i++;
}
if (i > (int)s.length()-4)
{
break;
}
temp.clear();
bool is=true;
//判断是否为四个字母开头
for(int j=0;j<4;j++)
{
if (!isLetter(s.at(i + j)))
{
is = false;
break;
}
}
if (is)
{
for (int j = 0; j < 4; j++)
{
if (s[i + j] >= 'A'&&s[i+j]<='Z')
{
s[i + j] += ('a' - 'A');
}
temp.append(1,s.at(i+j));
}
i = i + 4;
//将后续字母数字添加进单词temp中
while (i<(int)s.length()&&(isdigit(s.at(i)) || isLetter(s.at(i))))
{
if (s[i] >= 'A'&&s[i]<='Z')
{
s[i] -= ('A' - 'a');
}
temp.append(1,s.at(i));
i++;
}
mp[temp]++;
}
//如果不是单词将这一串的字母数字剔出
while (i < s.length() && (isDigit(s[i]) || isLetter(s[i])))
{
i++;
}
i--;
}
return mp;
}
- 代码覆盖率
- 异常处理
异常处理并没有想出什么极端的情况,除了文件打开失败,助教也说明了没有中文,那么文件内部都是ASCII码,所以只对文件是否成功打开做了额外处理。
心得
最重要的一点是一定要
先理解透需求
不然后面发现需求理解错误再改代码是一件很麻烦的事情,好在这一次并没又造成多大的麻烦,只是稍微改了下逻辑。其次VS真的是我用过的最难的IDE了,这个难体现在入门难,用惯了其它IDE的我这次作业其实接近一半的时间用来研究VS的功能,还没研究透,印象最深的是找代码覆盖率这个功能,VS社区版本身是不支持的,我还找了半天,网上的资料也少的可怜,最终我还是借助同学力量得到了“opencppcoverage”这一神秘咒语才展现出代码覆盖率这一指数。
其次关于单元测试,我能理解单元测试是必要的,但不得不说我想测试样例的能力还是有些欠缺,没想出几个具有特征的样例,这是本次的待改进之处。关于程序的正确率我还是很有自信的...除非又看漏了某些条件...