软工实践第二次作业

github传送门

PSP表格

PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) |实际耗时(分钟)

  • | :-: | -:
    Planning | 计划 |20 |30
    · Estimate | · 估计这个任务需要多少时间 |40 |40
    Development | 开发 |200 |300
    · Analysis | · 需求分析 (包括学习新技术) |60 |40
    · Design Spec | · 生成设计文档 |20 |20
    · Design Review | · 设计复审|10 |10
    · Coding Standard | · 代码规范 (为目前的开发制定合适的规范) |10 |20
    · Design | · 具体设计 |30 |30
    · Coding| · 具体编码 |60 |80
    · Code Review | · 代码复审 |20 |30
    · Test | · 测试(自我测试,修改代码,提交修改) |20 |25
    Reporting| 报告 |15 |15
    · Test Repor | · 测试报告 |10 |10
    · Size Measurement | · 计算工作量 |10 |10
    · Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 |10 |5
    | | 合计 |525 |665

解题思路

看到题目时,就先回想一下以前C++面向对象的知识,之前的面向对象也有在github和博客园写过,所以印象还比较深,但是真正动起手来打代码,真的是发现很多都忘了,于是查了很多资料和询问了一些acm大佬。
统计字符数和行数直接遍历,判断是否是单词也可以模拟一下,重点在于统计单词的次数和输出词频最多的单词。考虑如何统计单词的次数,很明显可以想到利用map,将单词放入map中++,但考虑到map的复杂度非常大,所以用map来统计单词次数不太好,就用了字典树,因为hash不知道开多大,题目并没有说单词数有多少,所以开大开小都有很多缺陷。考虑要记录词频最多的10个单词,可以利用堆来存储,我使用stl的SET加pair来处理,因为SET是一个可以自动排序的容器,插入删除查询时间复杂度都比较低,根据题目要求要排序两个东西,一个是单词词频,还有一个是字符串的字典序,可以将这两个东西放到pair当中,再将pair放入SET中,而SET里我们只需要放10个数据,多的删除即可。

设计实现过程

最初代码

#include "pch.h"
#include <iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<utility>
#include<set>
#include<map>
#pragma warning(disable:4996)
using namespace std;
const int N = 100007;
#define de(x) cout<<#x<<" = "<<x<<endl
#define rep(i,a,b) for(int i=(a);i<(b);++i)
struct node {
node *next[36];
int gs;
}root;
string line[N];
int num_line,eft_num,eft_char,eft_word;//num_line 为文件总行数 eft_num为有效行数 eft_char为字符数 eft_word为有效单词数
int Atoatoint(char c) { //将大写字符转化为小写字母,在hash到0~25,以及将0~9hash到26~35.
if (c >= 'A'&&c <= 'Z')c += 'A' - 'a';
if(c>='a'&&c<='z') return c-'a';
else return c-'0'+26;
}
char FAtoatoint(int c) { //是上个函数的反函数
if (c >= 0 && c <= 25)return 'a' + c;
else return c-26+'0';
}
void qinsert(string s) {  //字典树插入单词
int len = s.size(), tmp;
node *p;
p = &root;
rep(i, 0, len) {
tmp = Atoatoint(s[i]);
if (p->next[tmp]==NULL) {
p->next[tmp] = new node();p->next[tmp]->gs = 0;
}
p = p->next[tmp];
if (i == len - 1) { p->gs++;  }
}
}
void MYscanf(char s[]) {  // 输入总控制台
freopen(s, "r", stdin);
//freopen("0.in", "r", stdin);
string str;
while ( getline(cin, str) ) {
int len = str.size();
line[num_line++] = str;
if (len == 0)continue;
bool f=0;
rep(i, 0, len) {
if (str[i] != ' ')f = 1;
}
if (f) {eft_num++;}
}
}
int CountChar() {
int ret = 0;
rep(i, 0, num_line) {
int lim = line[i].size();
rep(j, 0, lim) {
if (line[i][j] >= 0 && line[i][j] <= 255)ret++;//统计有效字符
}
ret++;//增加换行符,每行都有,除了最后
}
return ret-1;//最后一行没有换行符,减去之。
}
bool is_efct_char(char c) {
if ((c >= '0'&&c <= '9') || (c >= 'A'&&c <= 'Z') || (c >= 'a'&&c <= 'z'))return true;
return false;
}
int CountWord() {
int ret = 0;
rep(i, 0, num_line) {
bool f = 1;int lim = line[i].size();
rep(j, 0, lim) {
if( is_efct_char(line[i][j]) ){
string str = "";str += line[i][j];
rep(k, j+1, lim) {
if (is_efct_char(line[i][k]))str += line[i][k],j=k;
else { j = k;  break; }
}
bool f = 0;;int siz = str.size() - 1;
rep(k, 0, siz) {
if (str[k] >= '0'&&str[k] <= '9' && (str[k + 1]<'0' || str[k + 1]>'9'))f = 1;
}
if (!f) {//判断是否是有效单词
ret++;
qinsert(str);//在字典树中插入这个单词,为计算单词出现次数做准备。字典树节约空间以及为后续统计大幅度缩减时间复杂度
}
}
}
}
return ret;
}
string tmp;
set<pair<int, string> >qur;//利用set排序,单词次数为第一优先级,string字典序为第二优先级
set<pair<int, string> >::iterator it;
void dfs_getword(node u) {
if (u.gs != 0) {
qur.insert(make_pair(-u.gs, tmp));
if (qur.size() > 10) {
it = qur.end();
it--;
qur.erase(it);
}
}
rep(i, 0, 36) {
if (u.next[i] != NULL) {
tmp += FAtoatoint(i);
dfs_getword( *u.next[i] );
tmp.erase(tmp.size() - 1);
}
}
}
void CountMxWord() {
qur.clear();tmp = "";
dfs_getword(root);
}
void MYprint() {
freopen("result.txt", "w", stdout);
cout << "character: " << eft_char << endl;
cout << "words: " << eft_word << endl;
cout << "lines: " << eft_num << endl;
for (it = qur.begin();it != qur.end();it++) {
cout << "<" << it->second << ">: " << -it->first << endl;
}
}
void init() {
root.gs = 0;
}
int main(int argc,char *argv[])
{
init();
MYscanf(argv[1]);
eft_char = CountChar();
eft_word = CountWord();
CountMxWord();
MYprint();
return 0;
}

改进后 头文件把所有功能封装一个大类

#include<string>
#include<utility>
#include<set>
#include<map>
using namespace std;
const int N = 100007;
#pragma warning(disable:4996)
#define de(x) cout<<#x<<" = "<<x<<endl
#define rep(i,a,b) for(int i=(a);i<(b);++i)
class  QWE_wordCount {
public:
	string line[N];
	string tmp;
	set<pair<int, string> >qur;//利用set排序,单词次数为第一优先级,string字典序为第二优先级
	set<pair<int, string> >::iterator it;
	struct node {
		node *next[36];
		int gs;
	}root;
	int num_line, eft_num, eft_char, eft_word;//num_line 为文件总行数 eft_num为有效行数 eft_char为字符数 eft_word为有效单词数
	int Atoatoint(char c);
	char FAtoatoint(int c);
	void qinsert(string s);
	void MYscanf(char s[]);
	int CountChar();
	bool is_efct_char(char c);
	int CountWord();
	void dfs_getword(node u);
	void CountMxWord();
	void MYprint();
	void init();
};

关键功能代码

int QWE_wordCount::Atoatoint(char c) { //将大写字符转化为小写字母,在hash到0~25,以及将0~9hash到26~35.
	if (c >= 'A'&&c <= 'Z')c += 'A' - 'a';
	if (c >= 'a'&&c <= 'z') return c - 'a';
	else return c - '0' + 26;
}
char QWE_wordCount::FAtoatoint(int c) { //是上个函数的反函数
	if (c >= 0 && c <= 25)return 'a' + c;
	else return c - 26 + '0';
}
void QWE_wordCount::qinsert(string s) {  //字典树插入单词
	int len = s.size(), tmp;
	node *p;
	p = &root;
	rep(i, 0, len) {
		tmp = Atoatoint(s[i]);
		if (p->next[tmp] == NULL) {
			p->next[tmp] = new node(); p->next[tmp]->gs = 0;
		}
		p = p->next[tmp];
		if (i == len - 1) { p->gs++; }
	}
}
void QWE_wordCount::MYscanf(char s[]) {  // 输入总控制台
	freopen(s, "r", stdin);
	//freopen("0.in", "r", stdin);
	string str;
	while (getline(cin, str)) {
		int len = str.size();
		line[num_line++] = str;
		if (len == 0)continue;
		bool f = 0;
		rep(i, 0, len) {
			if (str[i] != ' ')f = 1;
		}
		if (f) { eft_num++; }
	}
}
int QWE_wordCount::CountChar() {
	if (num_line == 0)return 0;
	int ret = 0;
	rep(i, 0, num_line) {
		int lim = line[i].size();
		rep(j, 0, lim) {
			if (line[i][j] >= 0 && line[i][j] <= 255)ret++;//统计有效字符
		}
		ret++;//增加换行符,每行都有,除了最后
	}
	return ret - 1;//最后一行没有换行符,减去之。
}
bool QWE_wordCount::is_efct_char(char c) { //判断是否是属于单词的字符
	if ((c >= '0'&&c <= '9') || (c >= 'A'&&c <= 'Z') || (c >= 'a'&&c <= 'z'))return true;
	return false;
}
int QWE_wordCount::CountWord() {
	int ret = 0;
	rep(i, 0, num_line) {
		bool f = 1; int lim = line[i].size();
		rep(j, 0, lim) {
			if (is_efct_char(line[i][j])) {
				string str = ""; str += line[i][j];
				rep(k, j + 1, lim) {
					if (is_efct_char(line[i][k]))str += line[i][k], j = k;
					else { j = k;  break; }
				}
				bool f = 0;; int siz = str.size();
				if (siz < 4)f = 1;
				else {
					rep(k, 0, 4) {
						if (str[k] >= '0'&&str[k] <= '9')f = 1;
					}
				}

				if (!f&&str.size() >= 4) {//判断是否是有效单词
					ret++;
					qinsert(str);//在字典树中插入这个单词,为计算单词出现次数做准备。字典树节约空间以及为后续统计大幅度缩减时间复杂度
				}
			}
		}
	}
	return ret;

单元测试

TEST_METHOD(TestMethod1)
		{

			A->init();
			A->MYscanf("0.in");
			A->eft_char = A->CountChar();
			Assert::AreEqual(A->eft_char, (int)105);
			// 测试字符是否统计正确
		}
		TEST_METHOD(TestMethod2)
		{
			A->init();
			A->MYscanf("1.in");
			Assert::AreEqual(A->eft_num, (int)13);
		}
		TEST_METHOD(TestMethod3)
		{

			A->init();
			A->MYscanf("2.in");
			A->eft_word = A->CountWord();
			Assert::AreEqual(A->eft_word, (int)3);
			// 测试统计单词数是否正确
		}
		TEST_METHOD(TestMethod4)
		{
			A->init();
			A->MYscanf("3.in");
			A->eft_word = A->CountWord();
			A->CountMxWord();
			A->it = A->qur.begin();
			Assert::AreEqual(-A->it->first, (int)2);
			Assert::AreEqual(A->it->second, (string) "qweee");
			// 测试第一大词频单词
		}
		TEST_METHOD(TestMethod5)
		{
			A->init();
			A->MYscanf("4.in");
			A->eft_word = A->CountWord();
			A->CountMxWord();
			A->it = A->qur.begin(); A->it++;
			Assert::AreEqual(-A->it->first, (int)1);
			Assert::AreEqual(A->it->second, (string) "asdf456");
			// 测试第二大词频单词
		}
		TEST_METHOD(TestMethod6)
		{
			A->init();
			A->MYscanf("5.in");
			A->eft_word = A->CountWord();
			A->CountMxWord();
			A->it = A->qur.begin();
			Assert::AreEqual(-A->it->first, (int)4);
			Assert::AreEqual(A->it->second, (string) "asdf1");
			// 测试词频相同时能否输出字典序最大的那个
		}
		TEST_METHOD(TestMethod7)
		{
			A->init();
			A->MYscanf("6.in");
			A->eft_char = A->CountChar();
			Assert::AreEqual(A->eft_char, (int)0);
			A->eft_word = A->CountWord();
			Assert::AreEqual(A->eft_word, (int)0);
			// 传入空文件
		}
		TEST_METHOD(TestMethod8)
		{
			A->init();
			A->MYscanf("7.in");
			A->eft_char = A->CountChar();
			Assert::AreEqual(A->eft_char, (int)894787);
			A->eft_word = A->CountWord();
			Assert::AreEqual(A->eft_word, (int)99814);
			// 传入大文件
		}
		TEST_METHOD(TestMethod9)
		{
			A->init();
			A->MYscanf("8.in");
			A->eft_word = A->CountWord();
			Assert::AreEqual(A->eft_word, (int)0);
			// 测试是否能辨别错误单词
		}

		TEST_METHOD(TestMethod10)
		{
			A->init();
			A->MYscanf("11.in");
			A->eft_char = A->CountChar();
			Assert::AreEqual(A->eft_char, (int)1000000);
			A->eft_word = A->CountWord();
			Assert::AreEqual(A->eft_word, (int)200000);
			A->CountMxWord();
			A->it = A->qur.begin();
			Assert::AreEqual(-A->it->first, (int)2);
			Assert::AreEqual(A->it->second, (string) "aaaa");
			// 输入一百万个字符,二十万个单词,其中有十万种单词,用于测试性能
		}
	};

测试结果如下
测试结果

CPU使用率和代码覆盖率



异常处理

         //测试单元
        TEST_METHOD(Test4)
        {
            A->init();
            A->MYscanf("6.in");
            A->eft_char = A->CountChar();
            Assert::AreEqual(A->eft_char, (int)0);
            A->eft_word = A->CountWord();
            Assert::AreEqual(A->eft_word, (int)0);
            // 文件不存在。
        }
               处理代码:
        fp=freopen(s, "r", stdin);
        if (fp == NULL) {
            cout << "Error:Cannot open the file";
            error = 1;
            return;
        }

感想和收获

之前没有这么完整的做过一个项目,才发现原来一个项目的每个细节都要仔细斟酌,反复推敲,认识到这个行业真的需要认真和勤奋,还有就是自学能力,每天都有新东西在发展,所以要不断学习才能不被淘汰,才能被人需要。坚持每天积累学习也是很重要,不能来了任务才去想起之前忘记的,就会做很多原来没必要做的工作,费时费力。以后要好好努力学习,争取将来能来这个行业做出一些绵薄之力。

原文地址:https://www.cnblogs.com/nwk1130/p/9630074.html