第一次个人作业
试验要求
1. 对源文件(*.txt,*.cpp,*.h,*.cs,*.html,*.js,*.java,*.py,*.php等,文件夹内的所有文件)统计字符数、单词数、行数、词频,统计结果以指定格式输出到默认文件中,以及其他扩展功能,并能够快速地处理多个文件。
2. 使用性能测试工具进行分析,找到性能的瓶颈并改进
3. 对代码进行质量分析,消除所有警告
4. 设计10个测试样例用于测试,确保程序正常运行(例如:空文件,只包含一个词的文件,只有一行的文件,典型文件等等)
5. 使用Github进行代码管理
6. 撰写博客
前期准备
需求分析
本次作业要求对任意文件或者特定目录下所有文件中的字符、单词、词组做相应的统计,并将统计结果以文件的形式保存。其中主要有以下需要特别注意的:
- 要求较为复杂,细节较多,要考虑许多特殊情况。其中尤其要注意实际输出的单词或词组必须是出现过的字典顺序最小的单词,而不能是全大写或者全小写,这也是我觉得甲方最不人道的一件事,加上这个功能会导致性能慢了将近一倍,而实际输出结果大部分都是大写,甚至不存在大小写混杂的情况,真的是多余。
- 代码质量要求高,除了性能上要越快越好以外,要做到没有“警告”。
- 有跨平台的需求,对代码的可移植性提出了要求。
- 需要学习
- 博客的撰写、作业进程的记录与分析等。
代码规范
Visual Studio已经有对代码风格进行自动规范的功能,在此基础上根据我个人的习惯,结合Menci大神的总结,暂定了以下规范:
- 所有的
#include
指令必须放置于整个程序开头。 main
函数应该放置于整个程序末尾。#include
中,C 标准库头文件应该放置于 C++ 标准库头文件前,其它头文件(如果有)应放置于最后。- 对于每个代码块,使用 4 空格或等长的 Tab 缩进。
-
花括号必须遵循「花括号不换行」,且左花括号的左边必须有且仅有一个空格。所有右花括号必须与上一级代码块的缩进相同。
-
多个意义独立的代码块之间应该用空行隔开。
-
右花括号前不应该有多余的空行。
-
不应该有两个连续的空行。
-
非空行尾不应该有多余的空格。
-
所有的
#include
指令之后必须有一个空行。 -
如果有
using namespace std;
,则必须紧跟在#include
后的空行后,之后必须一个空行。 -
main
函数的返回值类型必须是int
,可以省略return 0;
。 -
空函数体可以使用
{}
。 - 传参时,应该根据实际需要使用「引用」、「
const
引用」和「值传递」。 -
应该尽量少使用全局变量。
-
局部变量必须在用时定义,变量名不应该与上一个块中的变量重名,可以与全局变量重名。
- 逗号
,
与for
中的分号;
后面必须有一个空格,前面不能有空格。 -
双目运算符、三目运算符的两侧必须有一个空格。单目运算符的两侧不能有空格。冒号的两侧必须有一个空格。
PSP表格----随着进度会随时更新
PSP2.1 |
任务内容 |
计划完成需要的时间(min) |
实际完成需要的时间(min) |
Planning |
计划 |
30 |
20 |
Estimate |
估计这个任务需要多少时间,并规划大致工作步骤 |
30 |
20 |
Development |
开发 |
510 |
1780 |
Analysis |
需求分析 (包括学习新技术) |
30 |
30 |
Design Spec |
生成设计文档 |
30 |
30 |
Design Review |
设计复审 (和同事审核设计文档) |
10 |
15 |
Coding Standard |
代码规范 (为目前的开发制定合适的规范) |
20 |
35 |
Design |
具体设计 |
40 |
50 |
Coding |
具体编码 |
300 |
900 |
Code Review |
代码复审 |
40 |
320 |
est |
测试(自我测试,修改代码,提交修改) |
40 |
400 |
Reporting |
报告 |
240 |
330 |
Test Report |
测试报告 |
60 |
270 |
Size Measurement |
计算工作量 |
30 |
20 |
Postmortem & Process Improvement Plan |
事后总结 ,并提出过程改进计划 |
150 |
40 |
Summary |
合计 |
780 |
代码设计
1.解决文件遍历问题
遍历得到文件夹下的所有文件名,并存在<vector>string files中,代码如下
/* the funtion to get the name of all the files and subfiles under the homepath */ #include"functions.h" #include<io.h> #include<iostream> using namespace std; void getallfiles(string homepath, vector<string>& files) { //file handle long hFile = 0; //file info struct _finddata_t fileinfo; string p; if ((hFile = (long)_findfirst(p.assign(homepath).append("\*").c_str(), &fileinfo)) != -1) { //find all the file under the homepath, and assert this is not empty do { //if it is a dirctory, iterate //if it is a file, push to the files(list) if ((fileinfo.attrib & _A_SUBDIR)) { if (strcmp(fileinfo.name, ".") != 0 && strcmp(fileinfo.name, "..") != 0) getallfiles(p.assign(homepath).append("\").append(fileinfo.name), files); } else { files.push_back(p.assign(homepath).append("\").append(fileinfo.name)); } } while(_findnext(hFile, &fileinfo) == 0); _findclose(hFile); } }
linux下的版本
/* the funtion to get the name of all the files and subfiles under the homepath */ #include"functions.h" //#include<io.h> #include<iostream> #include<dirent.h> #include<iomanip> #include<string> #include<string.h> using namespace std; void getallfiles(string homepath, vector<string>& files) { DIR* dir = opendir(homepath.c_str()); struct dirent* ptr; string absolutePath; string subDirect; while((ptr = readdir(dir)) != NULL) { if(strcmp(ptr->d_name,".") != 0 && strcmp(ptr->d_name,"..") != 0) { subDirect.assign(homepath); if(ptr->d_type == 4) { subDirect += "/"; subDirect += ptr->d_name; getallfiles(subDirect, files); //files.push_back(subDirect); } else if(ptr->d_type == 8) { string absolutePath = subDirect + "/"; absolutePath += ptr->d_name; //cout << absolutePath << endl; files.push_back(absolutePath); } } } }
2.i/o 读取文件内容,采用的方法是,通过上面函数得到的文件名对文件读取,并且一次性将文件以二进制读入到内存,用char* buffer指向,然后之后对buffer进行操作,并在结束读取下一个文件时,释放buffer内存,这样做可以减少i/o次数,节省代码运行的时间,代码如下
/* this function transfer the content of a file to RAM at one time, and generate a head pointer buffer */ #include"functions.h" #include<stdio.h> using namespace std; void getcontent(string path, char *& buffer, long &size) { FILE* fp; size_t result; buffer = NULL; //to open the file in the path fp = fopen(path.c_str(), "rb"); fseek(fp, 0, SEEK_END); size = ftell(fp); rewind(fp); //to get the same size space in the RAM buffer = new char[size]; //to copy the file result = fread(buffer, 1, size, fp); fclose(fp); }
3.数据结构是一个HashTable的对象
变量
char WordTable[L1][200]用来存储检测到的字符,存在里面的形式是当前出现同类词里面,按ASCII最小的词
int WordFrequency[L1]用来存储WordTable里面对应index单词出现的频率
int Formar[L2] 用来存储词组Phrase的第一个单词在WordTable里面的地址
int Latter[L2]用来存储Phrase的第二个单词在WordTable里面的地址
int PhraseFrequency[L2]用来存储Formar Latter[L2]中对应地址词组的频率
方法
HashTable(int, int)//构造函数,可以初始化L1,L2长度
int append(char* sample, int formar);//用来将一个已检测为单词的词加入到表中,其中还要输入前一个单词的在WordTable的index:formar, 同时方法返回该单词在WordTable中的index以便下一个单词加入时初入参数.
get10words();;//输出10个频率最高的单词,里面采用普通的排序方法
get10Phrase();//输出10个频率最高的单词,
内部方法:
int hash(char*);//输入单词,初步得到在WordTable里面的index,冲突在append方法中解决,在append方法中被调用
int hash2(int formar, int latter);//输入词组两词在WordTable中的index,初步得到词组在Formar[] Latter[] PhraseFrequency[]里面的index, 冲突在append方法中解决, 在append方法中被调用.
代码如下:
#ifndef _DATASTRUCTURE_H #define _DATASTRUCTURE_H #define MAXINITLENGTH 3010349 #define PHRASELENGTH 16785407 //#define EMERGENCYLENGTH 10 //TODO: to think about whether add the safe operation #include<iostream> #include<string> #include<math.h> #include<string.h> using namespace std; #define LETTERH 64 #define LETTERL 96 #define NUMBER 48 #define DELIMITER 0 int WhatKindChar(char c) { if( ((int)c>=65) && ((int)c<=90) ) return LETTERH; if( ((int)c>=97) && ((int)c<=122) ) return LETTERL; if( ((int)c>=48) && ((int)c<=57) ) return NUMBER; else return DELIMITER; } class HashTable { public: int* WordFrequency; char(*WordTable)[200]; int* Formar; int* Latter; int* PhraseFrequency; HashTable() { WordFrequency = new int[MAXINITLENGTH]; WordTable = new char[MAXINITLENGTH][200]; Formar = new int[PHRASELENGTH]; Latter = new int[PHRASELENGTH]; PhraseFrequency = new int[PHRASELENGTH]; for (int i = 0; i < MAXINITLENGTH; i++) { WordFrequency[i] = 0; } for (int i = 0; i < PHRASELENGTH; i++) { PhraseFrequency[i] = 0; } } int getwordnumber() { int result = 0; for (int i = 0; (i < MAXINITLENGTH); i++) { if (WordFrequency[i] != 0) result++; } return result; } long getphrasenumber() { long result = 0; for (int i = 0; i < PHRASELENGTH; i++) { if (PhraseFrequency[i] != 0) result++; } return result; } void get10words() { int index[10] = { 0 }; for (int m = 0; m < 10; m++) { int flag = 0; for (int i = 0; i < MAXINITLENGTH; i++) { for (int c = 0; c < m; c++) { if (i == index[c]) i++; } if (WordFrequency[i] > WordFrequency[flag]) { flag = i; } } index[m] = flag; cout << WordTable[flag] << ": " << WordFrequency[flag] << endl; } } void get10phrases() { int index[10] = { 0 }; for (int m = 0; m < 10; m++) { int flag = 0; for (int i = 0; i < PHRASELENGTH; i++) { for (int c = 0; c < m; c++) { if (i == index[c]) i++; } if (PhraseFrequency[i] > PhraseFrequency[flag]) { flag = i; } } index[m] = flag; cout << WordTable[Formar[flag]] << " " << WordTable[Latter[flag]] << ": " << PhraseFrequency[flag] << endl; } } int append(char* sample, int formarindex) { char tochangestring[200];//wordcompare doesn't changed, use string like before unsigned int index = hash(sample);//TODO int c;//speciallly for the char* for (int i = 1; WordFrequency[index] != 0 && (wordcompare(WordTable[index], sample, tochangestring)) == false; i++) { index = (index + i * i) % MAXINITLENGTH; } if (WordFrequency[index] == 0) { WordFrequency[index]++; //wordcompare(sample, sample, tochangestring); for (c = 0; sample[c] != '