软工第二次作业

软工个人项目之词频统计

 

GitHub仓库地址:https://github.com/waaaafool/081600410

PSP 表格

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

需求分析

  • 统计文件的字符数:
    • 只需要统计Ascii码,汉字不需考虑
    • 空格,水平制表符,换行符,均算字符
  • 统计文件的单词总数,单词:至少以4个英文字母开头,跟上字母数字符号,单词以分隔符分割,不区分大小写。
    • 英文字母: A-Z,a-z
    • 字母数字符号:A-Z, a-z,0-9
    • 分割符:空格,非字母数字符号
    • :file123是一个单词, 123file不是一个单词。file,File和FILE是同一个单词
  • 统计文件的有效行数:任何包含非空白字符的行,都需要统计。
  • 统计文件中各单词的出现次数,最终只输出频率最高的10个。频率相同的单词,优先输出字典序靠前的单词。
  • 按照字典序输出到文件result.txt:例如,windows95,windows98和windows2000同时出现时,则先输出windows2000

    • 输出的单词统一为小写格式
  • 输出的格式为

characters: number
words: number
lines: number
<word1>: number
<word2>: number

思路

拿到题目后,进行分析,发现统计行数,输出文件,以及字母数,都只需要从文件流中读出数据,就可以进行统计。而统计单词则需要进行单词的判定,同时统计最高频率的十个单词不仅需要判定单词,还需要对单词出现的次数进行hash处理。所以就想到了,从文档流中读出string类型,然后进行分割出单词,接着则是对单词放入hash_map中,最后用遍历hash_map,放入容量为十的优先队列。最后对这剩下的十个进行按字符串的比较大小排列,输出。

测试文档

input

a
a
a
a
file123

file123
file123
123file
123file
what should i do?i donot like you.
what should i do?i donot like you.
what should i do?i donot like you.

what should i do?i donot like you.

output

characters:190
words: 19
line:13
<donot>:4
<file123>:3
<like>:4
<should>:4
<what>:4

分析:123file不是单词,以及未满四个字符的单词均未被统计入内。空行也没被统计入行数。

代码规范

对于.h和.cpp文件,均使用每个单词的第一个字母大写来命名,而函数和重要变量使用驼峰法命名。

实现

首先确定文件的组织结构

其中Frequency是用来统计单词的数量,而WordCount是用来统计频率最高的十个单词。

主要算法

统计频率最高的十个单词

  •  

算法关键

  引入hash_map,来使查询的时间复杂度变成o(1),以及引入优先队列(堆模型),来使遍历hash_map后,储存以及更新频率最大的单词的时间变为o(lgn);

 

编码 过程

struct node {
	int num;
	string s;
};
node result[10];

bool cmp(node a, node b) {
	return a.s < b.s;
}


using namespace std;
int number = 0;

void  wordCount(const char* file){
	ifstream fin;
	ofstream fout;
	fout.open("..//result.txt", ios::out | ios::app);
	fin.open(file);
	if (!fout) {
		cout << "The File cannot open.1111" << endl;
	}
	if (!fin) {
		cout << "The File cannot open." << endl;
	}
	string s;
	hash_map<string, int> wordList;
	while (true) {
		fin >> s;
		string tem="";
		int len = s.length();
		int pre = -1;//记录本单词上一个字母的位置,初始为-1

		//进行文档流读出的string类型的单词切割
		for (int i = 0; i < len; i++) {
			if ('A' <= s[i]&&s[i] <= 'Z') s[i] += 32;
			if (s[i] > 'z' || (s[i]<'a'&&s[i]>'9') || s[i] < '0') {
				if (i - pre > 4&&(s[pre+1]<='z'&&s[pre + 1]>='a')) {
					for (int j = pre + 1; j < i; j++)
						tem += s[j];
					wordList[tem]++;
					tem = "";
				}
				pre = i;
			}
			else if (i == len - 1 && i - pre >=4 && (s[pre + 1] <= 'z'&&s[pre + 1] >= 'a')) {
				for (int j = pre + 1; j <= i; j++)
					tem += s[j];
				wordList[tem]++;
				tem = "";
			}
		}
		if (fin.eof()) {
			break;
		}

	}
	priority_queue<pair<int, string>, vector<pair<int, string>>,greater<pair<int, string>>> maxList;
	
	//遍历hash_map,加入优先队列
	for (hash_map<string, int>::const_iterator i = wordList.begin(); i != wordList.end(); i++) {
		maxList.push(make_pair(i->second, i->first));
		if (maxList.size() > 10) {
			maxList.pop();
		}
	}
	int i = 0;
	//取出队列里面的值,放入result结构数组中
	while (!maxList.empty()) {
		result[i].num = maxList.top().first;
		result[i].s = maxList.top().second;
		maxList.pop();
		i++;
	}

	//排序输出
	sort(result, result + i, cmp);
	for (int j = 0; j < i; j++) {
		cout << "<" << result[j].s << ">" << ":" << result[j].num << endl;
		fout << "<" << result[j].s << ">" << ":" << result[j].num << endl;
	}
	
	fin.close();
	fout.close();

}

  分析:代码使用循环来分割单词,使得性能比较慢,可以改用正则表达式来改进。

性能分析报告 

  分析:对main函数进行大量循环,发现除了那几个自己定义的函数之外,输入输出流占用的cpu比例很大,以前写一些算法题的时候也发现使用C++的输入输出流会超时。所以改用c的输入输出会好很多,因为c++的输入输出流封装了太多c的东西。

单元测试

设计:

1、传入一个空文件;                              

2、传入只有123file等的文件;

3、传入只有空格, , 等字符;

4、传入一个文件,其中包含空行的;

5、传入一个文件,里面有类似adc.fef类似的非空白符为终结符的;

6、传入一个文件,里面不超过十个有效单词的。

7、输入一个文件,里面单词有类似window2002,window1001fa类似的,看输出排列。

通过该变文件类型,证明成功,但由于VS未能找到对应的test,所以没有工程化测试,有点遗憾。

代码覆盖率

下载插件OpenCppCoverage

代码覆盖率的截图如下:

分析:总体而言覆盖率还是很不错的。

异常处理

1、在一开始进行main函数整合的时候,如果文件为空,发现会直接返回错误信息

于是加了一个判断

if (argv == NULL) {
			cout << "请输入文件名" << endl;
			return 0;
		}

  

总结和感想

经过这次软工实践,让我感受到了为什么说退课率这么高了。很多人不一定是因为坚持不下来,而是在衡量坚持下来和换个其他东西坚持,哪个性价比更高。因为软工确实太耗时间了,真的非常耗时间。

通过这次,最直观的就是感受到自己对各种工具的使用能力的匮乏,这次题目,编码不难,难的是在后面插件的下载与使用。

现在想着,如果最后坚持下来,那一定能学到很多东西的。

原文地址:https://www.cnblogs.com/waaaafool/p/9636865.html