• 软工实践第二次作业-词频统计


    作业内容

    第二次作业


    Github项目地址

    由于作业要求C++请用Visual Studio Community 2017进行开发,
    而mac版vs无法写c++项目,所以是先用Xcode写完后再移植到VS上的;又因为作业要求使用Github来管理源代码和测试用例,所以一开始的签入记录可看Xcode版项目地址,最终完成项目在VS版项目地址


    PSP表格

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

    解题思路

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

    分析具体需求

    • 1.基本功能

    (1)统计文件的字符数

      只需要统计Ascii码,汉字不需考虑空格,水平制表符,换行符,均算字符
      统计文件的单词总数,单词:至少以4个英文字母开头,跟上字母数字符号,单词以分隔符分割,不区分大小写。

    (2)统计文件的单词总数

      单词:至少以4个英文字母开头,跟上字母数字符号,单词以分隔符分割,不区分大小写。

    (3)统计文件的有效行数:

      何包含非空白字符的行,都需要统计。
      统计文件中各单词的出现次数,最终只输出频率最高的10个。频率相同的单词,优先输出字典序靠前的单词。
      按照字典序输出到文件result.txt:例如,windows95,windows98和windows2000同时出现时,则先输出windows2000

    (4)统计文件中各单词的出现次数

      最终只输出频率最高的10个。频率相同的单词,优先输出字典序靠前的单词。

    (5)按照字典序输出到文件result.txt

      例如,windows95,windows98和windows2000同时出现时,则先输出windows2000
      输出的单词统一为小写格式

    (6)输出格式

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

    思考:

    四项功能,需要写四个函数,考虑文件读写和输入输出格式问题。

    进一步思考:

    • 文件如何读入读出?

      fstream

    • 如何匹配单词及分隔符?

      使用正则语言,参考教程

    • 单词应该怎么存储?

      存在map里。

    • 2.接口封装

    把基本功能里的:

    统计字符数
    统计单词数
    统计最多的10个单词及其词频

    这三个功能独立出来,成为一个独立的模块

    思考

    • 除了类还有什么方法将功能独立出来?是怎么实现的?

      使用dll封装,参考教程

    • 封装有什么有什么优点?
        
    • 3.单元测试
    • 如何使用单元测试项目?

      参考教程

    • 使用什么插件查看代码覆盖率?

      OpenCppCoverage


    实现过程

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

    组织代码

    有四个函数,它们之间相互独立。

    单元测试

    • 所有测试函数
    • 测试资源文件
    • 具体代码

      代码覆盖率:


    改进思路

    记录在改进程序性能上所花费的时间,描述你改进的思路。

    用时:

    用在改进性能上的时间是62分钟。

    第一次改进思路:

    • 单词匹配优化

       一开始使用的是正则表达式匹配,后来想要改成有限确定自动机(DFA)以减少时间。改后发现正则表达式与自动机相比,虽然时间花费的比较长,但更易于修改,更易于看懂,遂还是选择了正则表达式匹配。

    • 存单词优化

      一开始直接将匹配后的单词丢入TreeMap,建树的时间复杂度为O(nlogn),后改为HashMap,复杂度降至O(n),由于统计总词数和词频记录是分开的,所以没有采用Tire字典树。

    • 排序优化

      简单粗暴地将Map里的数据转存到vector中使用sort排序了。其实因为只需输出词频最高的十个单词,所以只需维护一个数量为10的高频词顶堆就可以了。

    第二次改进思路:

    由于第一次使用NFA匹配输出的单词数与结果不符,所以改成了DFA,但仍不知道为什么正则匹配出现问题,等找出原因后再补充这一part。

      性能分析:

    使用Map:

    使用unordered_map(即hashmap):


    代码说明

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

      异常处理:

    使用 try - catch捕捉无输入或输入错误、输入文件名多于一个、输出文件无法打开的异常。

    try
    {
    	file.open(argv[1], ios::in);
    	if(!file) throw string("输入文件为空或错误
    ");
    	if(argc > 2) throw string("输入参数过多
    ");
    	file.close();
    	
    	......
    	
    	ofstream out(OutName);
    	if (!out) throw string("输出文件无法打开
    ");
    	out.close();
    }
    catch(string R)
    {
    	cout << R << endl;
    }
    

      统计单词数目:

    用正则表达式匹配单词,以统计单词总数。

        regex WordsRegex("^[A-Za-z]{4}[[:w:]]+");//单词的正则表达式
    	long wordsnum = 0;
    	string temp;
    	fstream TextFile;
    	TextFile.open(filename);//打开文件
    	string OneLine;
    	while (TextFile >> OneLine)//读入一行
    	{
    		sregex_token_iterator end;
    		for (sregex_token_iterator wordIter(OneLine.begin(), OneLine.end(), WordsRegex), end; wordIter != end; wordIter++)
    		{//使用正则迭代器在一行文本中逐个找出单词
    			wordsnum++;
    		}
    	}
    

    下推自动机

    while ((charTemp = ifs.get()) != EOF)
    {
    	CharNum++;
    	if (charTemp >= 65 && charTemp <= 90)
    		charTemp += 32;
    	switch (state)
    	{
    	case 0:
    		if (charTemp >= 97 && charTemp <= 122) 
    		{
    			wordtemp += charTemp;
    			state = 1;
    		}
    		break;
    	case 1:
    		if (charTemp >= 97 && charTemp <= 122)
    		{
    			wordtemp += charTemp;
    			state = 2;
    		}
    		else
    		{
    			state = 0;
    			wordtemp = "";
    		}
    		break;
    	case 2:
    		if (charTemp >= 97 && charTemp <= 122) {
    			wordtemp += charTemp;
    			state = 3;
    		}
    		else
    		{
    			state = 0;
    			wordtemp = "";
    		}
    		break;
    	case 3:
    		if (charTemp >= 97 && charTemp <= 122) {
    			wordtemp += charTemp;
    			state = 4;
    		}
    		else
    		{
    			state = 0;
    			wordtemp = "";
    		}
    		break;
    	case 4:
    		if (charTemp >= 97 && charTemp <= 122 || (charTemp >= '0'&&charTemp <= '9')) 
    		{
    			wordtemp = wordtemp + charTemp;
    		}
    		else
    		{
    			WordsMap[wordtemp] ++;
    			state = 0;
    			wordtemp = "";
    		}
    		break;
    	}
    }
    if (state == 4) 
    {
    	WordsMap[wordtemp] ++;
    }
    

      统计字符个数:

    统计所有非中文字符

    int CharNum = 0;
    ifstream ifs(filename);
    char charTemp;
    mci charCountMap;
    while ((charTemp = ifs.get()) != EOF)
    {
    	if (charTemp >= NULL && charTemp <= '~')//统计所有字符
    		CharNum++;
    }
    ifs.clear();
    ifs.seekg(0);
    

      统计行数:

    要求去除空行

    fstream fs(filename, ios::in);
    	string s;
    	while (getline(fs, s))
    	{
    		for (i = 0, IsNull = 1; i < s.length(); i++)
    		{
    			if (s[i] != ' ' && s[i] != '	')//排除掉非空行
    			{
    				IsNull = 0;
    				break;
    			}
    		}
    		if (!IsNull) lines++;
    	}
    	return lines;
    

      输出高频词:

    输出词频最高的十个单词,相同词频的按照字典序排序

    int sortWords(psi p1, psi p2)//自定义的vector排序函数
    {
    	if (p1.second == p2.second)
    	{
    		return p1.first < p2.first;//词频相等按字典序排
    	}
    	else return p1.second > p2.second;
    }
    
    for (unordered_map<string, int>::iterator iter = WordsMap.begin(); iter != WordsMap.end(); iter++)//将map中的数队放入vector中
    {
    	WordsVec.push_back(pair<string, int>(iter->first, iter->second));
    }
    sort(WordsVec.begin(), WordsVec.end(), sortWords);//排序
    
    
    wordsCount(filename1);
    long endp = WordsVec.size();
    endp = (endp < num) ? endp : num;//判断总词数是否大于10
    
    ofstream out(filename2, ios::in | ios::out);//读入输出文件
    out.seekp(0, ios::end);//定位到文件末尾继续写入
    for (vpsi::iterator iter = WordsVec.begin(); iter != (WordsVec.begin() + endp); ++iter)//vpsi为vector(pair<string, int>)
    {
    	out << "<" << iter->first << ">:" << iter->second << endl;
    }
    
    

    心路历程与收获

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

      这次代码作业陆陆续续地写了好几天,虽然代码很快就写完了,但由于设备问题,只能在虚拟机上跑,下载vs花了很长时间,而后运行又跑崩了好几次_(:⁍」∠)_,git又一直上传不上,历经了千辛万苦,最终还是在别人的电脑上跑了程序。
      不过虽说过程艰难,但学到的东西还是蛮多的。在这期间,我不仅了解了单元测试,代码覆盖率和DLL封装,还了解了写代码前准备工作的重要性。而阅读《构建之法》更令我受益匪浅,这本书中所写的,从知识准备到项目规划,都非常清晰有条理,假设的情景也十分生动活泼,阅读感极佳。这些天来感受最深的还是实践的重要性,相信如果能将书中的习题完整地做完,我们的动手实践能力一定会得到极大的提升。


  • 相关阅读:
    python学习笔记之小小购物车
    TestNg学习一
    网监利器镜像——原理配置篇
    改变人生的31个黄金思维
    培养人脉的106个技巧
    CIR,CBS,EBS,PIR,PBS傻傻分不清楚?看这里!—-揭秘令牌桶
    请别浪费你30岁前的时光,职场“35岁现象”
    VRRP主备备份配置示例—实现网关冗余备份
    关于JS的prototype
    使用 Bootstrap Typeahead 组件
  • 原文地址:https://www.cnblogs.com/qvq-qvq/p/9637324.html
Copyright © 2020-2023  润新知