福大软工1816 · 第二次作业 - 个人项目

1.在文章开头给出Github项目地址。

git项目地址:https://github.com/Stellaaa18/personal-project

2.给出PSP表格。

PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
Planning 计划 10 15
· Estimate · 估计这个任务需要多少时间 10 15
Development 开发 360 800
· Analysis · 需求分析 (包括学习新技术) 90 120
· Design Spec · 生成设计文档 20 30
· Design Review · 设计复审 10 10
· Coding Standard · 代码规范 (为目前的开发制定合适的规范) 10 10
· Design · 具体设计 20 30
· Coding · 具体编码 120 240
· Code Review · 代码复审 30 120
· Test · 测试(自我测试,修改代码,提交修改) 60 240
Reporting 报告 70 260
· Test Repor · 测试报告 30 220
· Size Measurement · 计算工作量 10 10
· Postmortem & Process Improvement Plan · 事后总结, 并提出过程改进计划 30 30
|       | 	合计  |440 |1075

3.解题思路描述。即刚开始拿到题目后,如何思考,如何找资料的过程。

程序要求读入一个文本文件,统计该文件的字符数,单词数,有效行数以及输出词频最高的10个单词。我是用正则表达式做的,按行从文件读取内容,每当匹配到一个符合条件的单词时,就将其存进map里面。然后遍历map,将单词及其出现次数放到vector里面,排序输出词频最高的10个单词。

4.设计实现过程。设计包括代码如何组织,比如会有几个类,几个函数,他们之间关系如何,关键函数是否需要画出流程图?单元测试是怎么设计的?

关于代码结构

第一次写的时候,我是分成了以下几个函数,写在同一个源文件里面。

  • CountChar() : 用于统计文件的字符个数
  • CountLines() : 统计文件的有效行数
  • CountWords() : 统计有效单词个数
  • WordsFrequency() : 统计各个单词出现的次数,并将词频最高的10个单词输出到result.txt文件中

后面看到接口封装的要求,参考了赵畅同学的代码组织结构。

工程框架图如下:

设计的单元测试如下:

单元测试名称 测试内容 测试函数 测试结果
AllEmptyChar 统计空白字符(空格,换行符,制表符) CountChar() 通过
OtherASCII 统计非数字字母的ascii码符号 CountChar() 通过
IncludeEmptyLines 统计含有空行的文件的行数 CountLines() 通过
NotValidWords 文本都是无效单词(例如abc,123file) CountWords() 通过
WordSperator 分隔符是其他ascii字符(例如"?","!","$") CountWords() 修改后通过
EmptyFile 测试空文件 CountChar(),CountLines(),CountWords() 通过
CaseInsentitive 测试是否不区分大小写(file和FILE为同一个单词) WordsFreq() 通过
CheckOrder 词频相同时是否按字典序 WordsFreq(),TopTenWords() 通过

以下部分单元测试代码:

namespace EmptyFile//测试空文件
{
	TEST_CLASS(UnitTest1)
	{
	public:

		TEST_METHOD(TestMethod1)
		{
			char filename[105] = "EmptyFile.txt";
			int characters = CountChar(filename);
			int lines = CountLines(filename);
			int words = CountWords(filename);
			Assert::IsTrue(characters == 0 && lines == 0 && words == 0);
		}
	};
}

extern map<string, int> M;
namespace CaseInsentitive //测试是否不区分大小写
{
	TEST_CLASS(UnitTest1)
	{
	public:

		TEST_METHOD(TestMethod1)
		{
			char filename[105] = "CaseInsentitive.txt";
			init();
			WordsFreq(filename);
			map<string, int>::iterator it = M.begin();
			Assert::AreEqual(it->second, 3);
		}

	};
}

5.记录在改进程序性能上所花费的时间,描述你改进的思路,并展示一张性能分析图(由VS 2017的性能分析工具自动生成),并展示你程序中消耗最大的函数。

将main()函数循环执行10000次,得到的结果如下:

程序一共运行了44.21秒,消耗最大的是CountWor()函数。从这张图可以看到,正则表达式有关的函数regex_serch和迭代器matcher消耗的时间也很大,紧跟在main()函数之后。
从下图可以看出,CountWord()函数里面有很多和正则表达式相关的操作,故而消耗的时间大。

代码覆盖率截图


从图中可以看出,除了分析命令行参数的函数AnalyseParameter(int argc, char **argv)代码覆盖率较低外,其他的覆盖率都很高。因为AnalyseParameter(int argc, char **argv)主要用来处理异常情况,而测试时用的样例大部分传参是正确的,故该函数覆盖率较低。

6.代码说明。展示出项目关键代码,并解释思路与注释说明。

在计算单词频率时,用到了正则表达式,参考了这篇文章C++语言,统计一篇英文文章中的单词数(用正则表达式实现)
具体代码如下:

	string str;
	regex rx("\\b[a-zA-Z]{4}[a-zA-Z0-9]*");//至少以四个英文字母开头
	while (getline(file, str)) {
		smatch m;
		while (regex_search(str, m, rx)) {
			string s = m[0];
			for (int i = 0; s[i] != '\0'; i++) {
				if (s[i] >= 'A'&&s[i] <= 'Z') s[i] += 32;//大写转小写
			}
			M[s]++;//将转换成小写的有效单词存进map里
			str = m.suffix().str();
		}
	}

然后将map里的内容复制到vector里面,直接排序输出前10个词。

for (auto x : M) {
		ans.push_back(x);
	}
	sort(ans.begin(), ans.end(), cmp);//排序
	int count = 10;
	//输出词频最高的10个单词
	ofstream fout("result.txt");
	for (auto x : ans) {
		if (!count) break;
		fout << "<" << x.first << ">: " << " " << x.second << endl;//输出到文件
		count--;
	}
	fout.close();

异常处理

int AnalyseParameter(int argc, char **argv)
{
	if (argc != 2) { //传入参数有误时,会给出提示
		cout << "argument error.\n";
		return -1;
	}
	ifstream fin(argv[1]);
	if (!fin) { //若该文件不存在,也会报错
		cout << "can't open the file.\n";
		return -1;
	}
	
	return 1;
}

7.结合在构建之法中学习到的相关内容与个人项目的实践经历,撰写解决项目的心路历程与收获。

一开始拿到题目时,其实是有点慌的,不知道要从哪里开始做。后面冷静下来,一步步分析,先把主要核心功能细分成统计字符数,行数,单词数及计算词频四个部分,一个个写出来。后面再写个函数解析命令行参数,至此基本功能都已经完成了,然后就剩下单元测试和性能改进了。
这次项目最大的收获就是,遇到难题不应该害怕,分成一个个更小的任务,逐个去解决,好像也没想象中那么困难。

8.Refrence

用正则表达式统计单词数: https://www.bbsmax.com/A/A2dmM8qgde/
赵畅同学博客: http://www.cnblogs.com/ZCplayground/p/9607027.html

posted @ 2018-09-12 22:03  __Stella  阅读(312)  评论(0编辑  收藏  举报