2018软件工程第二次作业——个人项目

1.WordCount项目031602510/scr

2.psp表格

PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
Planning 计划 80 60
· Estimate · 估计这个任务需要多少时间 80 60
Development 开发 2000 2700
· Analysis · 需求分析 (包括学习新技术) 400 450
· Design Spec · 生成设计文档 30 50
· Design Review · 设计复审 30 60
· Coding Standard · 代码规范 (为目前的开发制定合适的规范) 60 60
· Design · 具体设计 150 180
· Coding · 具体编码 1000 1300
· Code Review · 代码复审 60 60
· Test · 测试(自我测试,修改代码,提交修改) 280 370
Reporting 报告 90 120
· Test Repor · 测试报告 60 90
· Size Measurement · 计算工作量 30 30
· Postmortem & Process Improvement Plan · 事后总结, 并提出过程改进计划 实际用的时间会比起估计时间长上许多 所以应该安排好时间提前几天完成任务,不然会造成最后几天疯狂赶代码的情况

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

解题思路:仔细阅读作业题目之后,发现程序要完成四件任务:

  • <1>统计文件的字符数
    • 只需要统计Ascii码,汉字不需考虑
    • 空格,水平制表符,换行符,均算字符

估摸着应该是可以用函数fopen()来打开文件,然后用 fgetc()来逐个获取文件中的字符,这个应该不难实现,不过写出来的代码才发现提示fopen()是unsafe的,查了资料才知道现在都建议用函数fopen_s(),二者在接受参数返回值上都有区别。

  • <2>统计文件的单词总数,单词:至少以4个英文字母开头,跟上字母数字符号,单词以分隔符分割,不区分大小写。

    • 英文字母: A-Z,a-z
    • 字母数字符号:A-Z, a-z,0-9
    • 分割符:空格,非字母数字符号

一开始没有看清楚后面的单词实例,一开始感觉要求有歧义,走了很多弯路。对于单词总数的统计,我的思路是先将输入的input.txt文件存入一个字符串变量之中,然后对字符串进行预处理———大写转化为小写,除英文字母,数字以外的字符统一用空格符号代替,然后将字符串按空格隔开放进unordered_map<string, int> strMap;中,最后用容器vector和定义的sort();生成字典排序,然后遍历将符合条件的单词个数累加。

  • <3>统计文件的有效行数:任何包含非空白字符的行,都需要统计。

行数的统计虽然会稍微简单点,不过也有一些坑点,比如空白行可能存在着空格不只有"\n",我写的程序一开始嗨啊忽略了第一行是空白的情况,多次测试之后才发现问题;

  • <4>统计文件中各单词的出现次数,最终只输出频率最高的10个。频率相同的单词,优先输出字典序靠前的单词。

    • 按照字典序输出到文件result.txt:例如,windows95,windows98和windows2000同时出现时,则先输出windows2000
    • 输出的单词统一为小写格式

一开始把result.txt文件忘了,后来经过舍友提醒才知道的,关于单词频率的统计思路和之前写的单词数统计的主要思想差不多,不过统计的是字典序的单词,不过今天思考了一下比起用sort()函数,用堆排序单词会更快,还有听说有的同学遍历了十次容器每次取出最小的字典序单词,只需要O(10N),可以说是很黑科技了。

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

设计的实现过程:
类图

流程图

单元测试的设计,
其实总共有十个测试的,就不一一列出来了

text.cpp

#include "stdafx.h"
#include "CppUnitTest.h"
#include "../WordCount/stdafx.h"

using namespace Microsoft::VisualStudio::CppUnitTestFramework;

namespace UnitTest1
{       
    TEST_CLASS(UnitTest1)
    {
    public:
        
        TEST_METHOD(TestMethod1)
        {
            int Charnum = 198;//期望的测试结果
            int WordNum = 7;
            int lineNum = 3;
            char* filename = "F:\\WordCount\\UnitTest1\\test.txt";//文件存放位置绝对路径
            FILE *fp;
            fopen_s(&fp, filename, "r");
            WordCount A(fp, filename);
            Assert::AreEqual(lineNum, A.LineCount());//用的函数接口作为参数做测试
            Assert::AreEqual(Charnum, A.CharCount());
            Assert::AreEqual(WordNum , A.WordNum());
        }
    };

}   
    

测试结果查看

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

cpu占用图

main函数占用

占用最大的操作原来和vector有关

代码覆盖率

改进思路,对于容器vector和排序sort()是可以找到更好的替代,用堆排序或者遍历10次Map应该会减少算法复杂度。

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

下面就是自己觉得花了比较多时间的核心代码:主要是单词以及频率输出部分,其他就不做赘述。

unordered_map<string, int> strMap;


bool mysort(pair < int, string > a, pair < int, string > b)//定义sort规则
{
    if (a.first != b.first)
        return a.first > b.first;
    else
        return a.second < b.second;
}


void GetMap(stringstream &ss)//生成string中单词的键值对
{
    string strTmp;
    while (ss >> strTmp)
    {
        unordered_map<string, int>::iterator it = strMap.find(strTmp);
        if (it == strMap.end())
        {
            strMap.insert(unordered_map<string, int>::value_type(strTmp, 1));
        }
        else
            strMap[strTmp]++;
    }
}


void WordCount::LetterCount() //字符统计函数
{
    string strFile, tmp;
    int i = 0;
    ifstream file(target_file);      //读取当前文件夹下input.txt文件
    while (getline(file, tmp))//直到文件结尾,依次逐行读入文本
    {
        strFile.append(tmp); //每次读入一行附加到strFile结尾
        strFile.append(" ");//行尾补充空格
        tmp.clear();            //记得清除,否则上一次读入比这次文本长,不会完全覆盖而出错
    }
    for (unsigned int i = 1; i <= strFile.size(); i++)//将字符串中英文字母大写转小写
    {

        if (strFile[i] >= 'A'&&strFile[i] <= 'Z')
            strFile[i] += 32;
    }
    for (unsigned int i = 0; i < strFile.length(); i++)//分隔符号替换成为空格
    {
        if (ispunct(strFile[i]))
            strFile[i] = ' '; 
    }
    stringstream ss(strFile);
    if (strMap.empty()) GetMap(ss);
    
    vector < pair < int, string > > m;//容器
    for (unordered_map<string, int>::iterator it = strMap.begin(); it != strMap.end(); ++it)
        m.push_back(make_pair(it->second, it->first));
    sort(m.begin(), m.end(), mysort);//排序
    for (unsigned int k = 0; k < m.size(); ++k)//输出单词以及频率
    {
        string a = m[k].second.c_str();
        if (
            ((a[0] >= 'a'&&a[0] <= 'z') || (a[0] >= 'A'&&a[0] <= 'Z')) &&
            ((a[1] >= 'a'&&a[1] <= 'z') || (a[1] >= 'A'&&a[1] <= 'Z')) &&
            ((a[2] >= 'a'&&a[2] <= 'z') || (a[2] >= 'A'&&a[2] <= 'Z')) &&
            ((a[3] >= 'a'&&a[3] <= 'z') || (a[3] >= 'A'&&a[3] <= 'Z')))
        {
            cout << '<' << a << '>' << ":" << m[k].first << endl;//打印结果
            i++;
            if (i >= 10)//输出频率前十
                break;
        }
    }
}

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

一开始并没有感觉第二次作业会很难,git的操作之前有过,都蛮顺利的,写代码也没有花费很多时间。看到题目之后捋了一下思路,其实题目就是在之前c/c++课程的基础上,把一个个简单的功能糅合成一个程序,往里面掺杂一些文件的读写,排序,字符串处理等一些“简单”操作,应该不难。

但是当调试的时候,和同学一对比运行结果,就发现不少问题,代码有很多bug导致结果出错,主要还是没有考虑周全,有的地方代码不得不重新写过,用到Map和容器的时候,由于不熟悉,甚至结果完全出错,在电脑面前找了一个多小时,最后发现是自己写的Get Map()(往Map里面传数据的函数)多次传入,导致单词频率的统计结果翻了几倍。

接下来就是单元测试?什么是单元测试??怎么测试???这是第一次听的时候的心理活动,不过万能的百度和善良的同学给了我不少帮助,最后也学会了编写测试代码,看测试结果是否正确。期间还有一个小插曲,写好测试程序之后不管怎么测试,都会有部分通过不了,但是运行结果是没错的?!自己折腾了一两个小时,查Assert::AreEqual()的很多资料,感觉测试结果不应该不对啊,然后舍友说,你再运行一下未通过的测试,然后居然能通过了。

最后对于封装,也是一知半解,感觉封装的也不是很完善,第一次认真正式地封装。

现在对于这次的作业,感觉就是各种触及我的知识盲区,不过有所得。说实话,和周围同学比起来,作业对于我来说是挺吃力的,甚至于过程是艰难的,不过能听听同学聊聊程序的改进,向他们学习,上网络上查资料,自己也把查资料过程中的好博客和内容摘抄下来,积累起来,也有欣慰。

猜你喜欢

转载自www.cnblogs.com/dalegac/p/9631924.html
今日推荐