PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 30 | 40 |
· Estimate | · 估计这个任务须要多少时间 | 30 | 40 |
Development | 开发 | 1750 | 2365 |
· Analysis | · 需求分析 (包括学习新技术) | 240 | 300 |
· Design Spec | · 生成设计文档 | 60 | 150 |
· Design Review | · 设计复审 | 10 | 10 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 20 | 15 |
· Design | · 具体设计 | 180 | 90 |
· Coding | · 具体编码 | 840 | 1320 |
· Code Review | · 代码复审 | 280 | 120 |
· Test | · 测试(自我测试,修改代码,提交修改) | 120 | 360 |
Reporting | 报告 | 135 | 140 |
· Test Repor | · 测试报告 | 60 | 30 |
· Size Measurement | · 计算工做量 | 15 | 20 |
· Postmortem & Process Improvement Plan | · 过后总结, 并提出过程改进计划 | 60 | 90 |
合计 | 1915 | 2545 |
解题思路大体将这4项小问题归为3类来解决。html
统计字符数:只须要统计Ascii码,汉字不需考虑,
空格,水平制表符,换行符,均算字符。node
统计文件的有效行数:任何包含非空白字符的行,都须要统计。python
统计文件的单词总数,单词:至少以4个英文字母开头,跟上字母数字符号,单词以分隔符分割,不区分大小写。ios
统计文件中各单词的出现次数,最终只输出频率最高的10个。频率相同的单词,优先输出字典序靠前的单词。git
为了独立需求中的三项功能,因此我在代码文件上的组织也将这三项功能封装到不一样的cpp文件中,而且在头文件中声明各自的函数。 github
work_2.h
——包含头文件、数据结构以及用到函数的声明。Count_chrs.cpp
——统计字符数模块(也包含行数的统计)Count_words.cpp
——统计单词数模块(结果计入hashmap)Rank_words.cpp
——词频字典序导出模块
各个模块能够分开进行单元测试,也能够合并一块儿做为最终的输出结果。正则表达式
Rank_words.cpp
包含3个函数用于实现hashmap
031602509 |- src |- WordCount.sln |- UnitTest1 |- WordCount |- Count_chrs.cpp |- Count_words.cpp |- Rank_words.cpp |- WordCount.cpp |- WordCount.vcxproj |- pch.cpp |- pch.h |- work_2.h
具体文件组织以下所示编程
整个需求完成的流程图以下所示。
ubuntu
详细正则表达式判断过程在下文中以流程图形式展现windows
敲重点!!!查阅了一些文档,发现VS2017不支持零宽断言判断,因此使用正则表达式须要额外增长分隔符的判断
具体代码以下所示:(性能改进后)
int C_words(istream &fl, Words &wn, Wordnode **l) { int count = 0; int flag = 0; regex pattern(".[a-zA-Z]{3}[a-zA-Z0-9]*"); //设定正则表达式模板 smatch result; //smatch类存放string结果 //cout << regex_search(wn.all_string, result, pattern)<<endl; string::const_iterator start = wn.all_string.begin(); //字符串起始迭代器 string::const_iterator end = wn.all_string.end(); //字符串末尾迭代器 string temp_str; while (regex_search(start, end, result, pattern)) //循环搜索匹配模板的单词 { flag = 0; //cout<<"successfully match"; temp_str = result[0]; if (!((temp_str[0] <= 90 && temp_str[0] >= 65) || (temp_str[0] <= 122 && temp_str[0] >= 97)))//首字符判断 { if (temp_str[0] >= 48 && temp_str[0] <= 57) //数字首字符判断 flag = 1; temp_str.erase(0, 1); if (!(temp_str.size()>=4&&((temp_str[3] <= 90 && temp_str[3] >= 65) || (temp_str[3] <= 122 && temp_str[3] >= 97)))) { flag = 1; } } if (flag == 0) { transform(temp_str.begin(), temp_str.end(), temp_str.begin(), ::tolower);//转换为小写单词 hash_insert(l, temp_str); //哈希节点插入 count++; } start = result[0].second; //检测下一单词 } //cout << endl; return count; } }
修改后结果以下图所示
设定了12个单元测试用于测试代码,具体以下所示。
单元测试内容 | 测试模块 | 输出结果 | 测试效果 |
---|---|---|---|
给定一个字符串 | 字符统计 | 字符数 | 经过 |
给定论文部份内容A | 单词统计 | 单词数 | 经过 |
给定论文部份内容B | 词频统计 | 词频前10排名 | 经过 |
非法参数 | 容错检测 | 错误提示 | 经过 |
输入文件异常 | 容错检测 | 错误提示 | 经过 |
输出文件异常 | 容错检测 | 错误提示 | 经过 |
给定部分文本内容与部分无效行 | 有效行判断 | 有效行数 | 经过 |
给定相近字符串 | 单词统计、词频统计 | 词频前10排名、单词数 | 修改代码后经过 |
给定存在大小写区别的字符串 | 单词统计、词频统计 | 词频前10排名、单词数 | 经过 |
给出“File123”与“123File” | 单词统计、词频统计 | 词频前10排名、单词数 | 修改代码后经过 |
给出多个不合规范字符串 | 单词统计 | 单词数 | 经过 |
给出相似乱码文档 | 单词统计、词频统计、有效行统计、字符统计 | 所有需求 | 经过 |
namespace WordCount_Test { TEST_CLASS(UnitTest1) { public: TEST_METHOD(TestMethod6) { // TODO: 在此输入测试代码 File fnew; //控制文件模块 Words wnew; //控制单词模块 Wordnode *log[HASH_LENGTH] = { NULL }; //哈希散列指针数组 strcpy_s(fnew.file_name, "F:/VS_project/WordCount/WordCount_Test/test/test6.txt"); //获取文件名 //cout << fnew.file_name << endl; ifstream f; f.open(fnew.file_name, ios::in); //打开文件 if (!f.is_open()) //检测文件是否存在 { cout << "can't open this file!" << endl; } fnew.count_chars = C_chars(f, fnew, wnew); fnew.count_words = C_words(f, wnew, log); //计算单词数(插入哈希节点) rank_word(log, wnew); //词频排名 //单词需按字典序排列才可,依次检测排序。 Assert::AreEqual(wnew.word_rank[1], string("ubuntu14")); Assert::AreEqual(wnew.count_rank[1], 1); Assert::AreEqual(wnew.word_rank[2], string("ubuntu16")); Assert::AreEqual(wnew.count_rank[2], 1); Assert::AreEqual(wnew.word_rank[3], string("windows")); Assert::AreEqual(wnew.count_rank[3], 1); Assert::AreEqual(wnew.word_rank[4], string("windows2000")); Assert::AreEqual(wnew.count_rank[4], 1); Assert::AreEqual(wnew.word_rank[5], string("windows97")); Assert::AreEqual(wnew.count_rank[5], 1); Assert::AreEqual(wnew.word_rank[6], string("windows98")); Assert::AreEqual(wnew.count_rank[6], 1); } TEST_METHOD(TestMethod7) { // TODO: 在此输入测试代码 File fnew; //控制文件模块 Words wnew; //控制单词模块 Wordnode *log[HASH_LENGTH] = { NULL }; //哈希散列指针数组 strcpy_s(fnew.file_name, "F:/VS_project/WordCount/WordCount_Test/test/test7.txt"); //获取文件名 //cout << fnew.file_name << endl; ifstream f; f.open(fnew.file_name, ios::in); //打开文件 if (!f.is_open()) //检测文件是否存在 { cout << "can't open this file!" << endl; } fnew.count_chars = C_chars(f, fnew, wnew); fnew.count_words = C_words(f, wnew, log); //计算单词数(插入哈希节点) rank_word(log, wnew); //词频排名 //大写“ABCD”和小写“abcd”应被当作同一词汇统计 Assert::AreEqual(wnew.word_rank[1], string("abcd")); Assert::AreEqual(wnew.count_rank[1], 2); } }; }
之前就有了解VS有自带的代码覆盖率检测,此次做业实现时发现代码覆盖率结果须要VS企业版 才有提供,最后查阅了这篇博客。给VS2017装了一个小插件OpenCppCoverage才能够运行。
这里简单给出一个小教程 (查阅不少资料都没有很好的使用方法)
代码覆盖率结果以下图所示
除了图示的WordCount.cpp覆盖率不高之外,其他的代码覆盖率都十分高,总覆盖率为 91% ,仔细看函数内部结构发现,该cpp中存在多处异常检测与提示,再加上本次测试给定的参数正确,因此这也是代码覆盖率不高的缘由。(测试正常输出)
对应单元测试以下
TEST_METHOD(Exception_input) { File fnew; int flag_input_exception = 0; strcpy_s(fnew.file_name, "../UnitTest1/test/test11.txt");//输入文件名异常 ifstream f; if (!f.is_open()) { flag_input_exception = 1; //输入异常标志 } Assert::AreEqual(flag_input_exception, 1); } TEST_METHOD(Exception_output) { File fnew; int flag_output_exception = 0; strcpy_s(fnew.file_name, "../UnitTest1/test/ ");//输出文件异常 ofstream fo; fo.open(fnew.file_name , ios::out); //输出文件 if (!fo.is_open()) //输出文件合法性检查 { flag_output_exception = 1; //输出异常标志 } Assert::AreEqual(flag_output_exception, 1); }
最后在跑了几回由cbattle同窗提供的测试数据,发现运行时间相差不大,这里还要感谢个人一个没有参加软工实践课程的舍友,发现了输出界面的差别—— system("pause") 致使了命令行窗口没有正确中止。我认为这也就是本次TLE的缘由。
因为原先的实现方式是正则表达式匹配,因此也把整个文本读入来进行全文匹配单词,可是发现这样的方式在实现底层匹配时候不是特别方便。因而改进成vector_string 的形式来解决,具体实现方式以下流程图所示: .
system("pause")
之类的致使等待时间过长。[2] http://www.ruanyifeng.com/blog/2016/01/commit_message_change_log.html