-
作业链接 结对第二次—文献摘要热词统计及进阶需求
-
队员
- 221500201_孙文慈 代码测试 上传程序到github仓库 文档编写 查阅资料 进度规划
- 226100125_刘杰 WordCount基础需求和进阶需求主要程序的编写 描述解题思路 设计实现过程 进度规划
-Github
-基础需求(https://github.com/swc221500201/PairProject1-C/)
-进阶需求(https://github.com/swc221500201/PairProject2-C/)
PSP表格
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | ||
Estimate | 估计这个任务需要多少时间 | 1420 | |
Development | 开发 | ||
Analysis | 需求分析 (包括学习新技术) | 60 | 50 |
Design Spec | 生成设计文档 | 100 | 200 |
Design Review | 设计复审 | 60 | 80 |
Coding Standard | 代码规范 (为目前的开发制定合适的规范) | 120 | 180 |
Design | 具体设计 | 120 | 170 |
Coding | 具体编码 | 660 | 720 |
Code Review | 代码复审 | 60 | 120 |
Test | 测试(自我测试,修改代码,提交修改) | 90 | 100 |
Reporting | 报告 | ||
Test Repor | 测试报告 | 60 | 90 |
Size Measurement | 计算工作量 | 60 | 70 |
Postmortem & Process Improvement Plan | 事后总结, 并提出过程改进计划 | 30 | 60 |
合计 | 1420 | 1840 |
解题思路
刚刚接触题目之后第一反应就是用c++的文件流操作来挨个读取和分隔单词,并且很快的写出了可以运行的源码,后续单词统计排序功能,自己手动写了一些程序,虽然实现了排序功能,不过在文本大小超过1M以上,速度就明显更不上了,在CSDN和博客园上学习了几篇相关的博客,发现用哈希表是个不错的选择,而C++的map容器又正好适合这个功能,内部的红黑树实现使得插入和排序速度快了不少,经过改进之后速度上有了很大提升。
实现过程
在考虑好实现基础功能和进阶需求之后:
- 针对基础需求:考虑到要尽量做到功能独立即"Core"模块化,于是按要求实现了三个主要函数以及一些辅助的bool判断函数来满足题目需求。因为功能比较简单所以可以在一个源文件中实现。
int countCharacter(string f);//f为要统计的文件路径 返回int类型的字符个数
int countLine(string f);//f为要统计的文件路径 返回int类型的行数
int countWords(string f);//f为要统计的文件路径 返回int类型的单词个数
void sortwordCount(string f, string resultTxt);//排序文件f单词并输出到文件 resultTxt
- 针对进阶需求:相比于基础功能,进阶功能要求多了命令行运行,以及词频统计,权重统计等进一步要求,所以总体上是在基础功能的函数之上重写了一些功能来满足。
void countWordsWithWeight(string f, string resultTxt, int w);//权重统计,f为要统计的文件路径
//w为权重选择 并输出到文件 resultTxt
void countGroupWordsWithLength(string f, string resultTxt, int n);//f为要统计的文件路径 并输出到文件 resultTxt ,n为用户自定义词组长度
.....
以下是使用命令行运行时的主要写法,即把argv[i]挨个判断并执行相关操作
for (int i = 0; i < argc; ++i) {
if (string(argv[i]) == "-i"){
}
else if(){
}
......
}
- 针对爬虫:爬虫原计划使用c++的类库实现,不过代码量实在巨大,后续改用java的jsoup爬取整个html文档,并生成可以修改的DOM文档,在DOM文档中使用正则匹配找到了每一篇论文的href超链接,之后对这个href链接挨个发送请求,从返回的文本中解析出.ptitle等我们需要的数据内容。总的来说不是很难,不过自己分析DOM文档结构的时候比较难找到规律,好在这个网站没有设置反爬虫的陷阱。而且不需要翻页等复杂操作。
改进思路
-
爬虫java
爬虫的爬取速度太慢,爬取全部900多条论文消息需要花费近一分钟,如果能够启用线程应该能大幅度提高速度,不过限制于时间没有实现。
-
基础功能
其实我觉得统计行数,统计单词,统计字符的三个功能可以集中于一个函数,这样可以在分析统计的时候节约两遍的读取时间。不过助教说了这是为了使功能独立。
-
进阶功能
进阶功能基于基础功能,所以还是觉得要改进的是基础功能。
代码说明
基础需求
//f:要进行字符统计的文件路径
//返回值 字符数
//c >= 0 && c <= 127 因为爬取的文件包含法语字符所以用c++的 isascii(c)判断会报错
int countCharacter(string f) {
int ascii = 0;
ifstream read;
read.open(f, ios::in);
char c;
while (!read.eof()) {
read >> c;
if (c >= 0 && c <= 127)
ascii++;
}
read.close();
return ascii;
}
//f:要进行统计的文件路径
//返回值 行数
//eachline.empty()划去空行
int countLine(string f) {
ifstream input(f, ios::in);
string eachline;
int line = 0;
while (getline(input, eachline))
{
if (!eachline.empty())
line++;
}
input.close();
return line;
}
//f:要进行统计的文件路径
//返回值 单词数
//里面的isword()为自定义的单词判断函数 自动过滤不符合题意单词
int countWords(string f) {
int wordNum = 0;
ifstream input;
input.open(f, ios::in);
string aline;
string content;
string::size_type start = 0;
string::size_type end = aline.find_first_of(" ");//空格作为单词分隔符
while (getline(input, aline))
{
//为了避免溢出,保存一个string对象size的最安全的方法就是使用标准库类型string::size_type
string::size_type start = 0;
string::size_type end = aline.find_first_of(" ");//空格作为单词分隔符
while (end != string::npos) //npos就是这一行到头啦;
{
string content = aline.substr(start, end - start);
if (isword(content))//这个单词从未出现
wordNum++;
start = end + 1;
end = aline.find_first_of(" ", start);//空格作为单词分隔符
}
}
input.close();
return wordNum;
}
//f:要进行统计的文件路径
//返回值 无
//利用map容器来存储统计单词词频 multimap来实现单词字典顺序输
void sortwordCount(string f) {
ofstream out(resultTxt,ios::app);
ifstream input;
input.open(f, ios::in);
string eachline;
map<string, int> mapA; //第一个存单词,第二个存单词出现的次数;
while (getline(input, eachline))
{
//为了避免溢出,保存一个string对象size的最安全的方法就是使用标准库类型string::size_type
string::size_type start = 0;
string::size_type end = eachline.find_first_of(" ");//空格作为单词分隔符
while (end != string::npos) //npos就是这一行到头啦;
{
string content = eachline.substr(start, end - start);
if (isword(content)) {
tolowerString(content);//把content内容转换为小写 便于输出和统计
//if (!isLetter(content[end])&&!isdigit(content[end]))
// content.erase(content.end());
map<string, int>::iterator it = mapA.find(content);
if (it == mapA.end())//这个单词从未出现
mapA.insert(pair<string, int>(content, 1));//赋值的时候只接受pair类型;
else
++it->second;//单词存在
}
start = end + 1;
end = eachline.find_first_of(" ", start);//空格作为单词分隔符
}
}
multimap<int, string, greater<int> > mapB;//按int排序的multimap
//转移mapA
for (map<string, int>::iterator it1 = mapA.begin(); it1 != mapA.end(); ++it1)
{
mapB.insert(pair<int, string>(it1->second, it1->first));
}
//界面输出前十
int i = 0;
for (map<int, string>::iterator it2 = mapB.begin(); i < 10&&it2!=mapB.end(); ++it2, ++i)
cout <<"<"<<it2->second <<">:"<< it2->first << endl;
//输出排序好的map
for (map<int, string>::iterator it2 = mapB.begin(); it2 != mapB.end(); ++it2)
{
// if ((it2->first) > 1)
out << "<" << it2->second << ">:" << it2->first << endl;
}
out.close();
input.close();
}
进阶需求
词频统计
//f:要进行统计的文件路径
//返回值 无
//实现方法与基础功能里差不多,区别在于过滤了title: 和abstract:
//为了简洁以下只显示关键段代码
void sortwordCount(string f) {
if (isAbstract(eachline.substr(start, end - start)) || isTitle(eachline.substr(start, end - start))) {
start = end + 1;
end = eachline.find_first_of(" ", start);//空格作为单词分隔符
}
}
//f:要进行统计的文件路径 w 是否开启权重统计 0关闭 1开启
//返回值 无
//实现方法与基础功能里差不多,区别在于增加了权重统计功能
void countWordsWithWeight(string f, int w) {
flag = isTitle(eachline.substr(start, end - start));
///不统计title和abstract
if (isAbstract(eachline.substr(start, end - start))||isTitle(eachline.substr(start, end - start))) {
start = end + 1;
end = eachline.find_first_of(" ", start);//空格作为单词分隔符
}
while (end != string::npos) //npos就是这一行到头啦;
{
string content = eachline.substr(start, end - start);
if (isword(content)) {
tolowerString(content);//把content内容转换为小写 便于输出和统计
map<string, int>::iterator it = mapA.find(content);
if (it == mapA.end())//这个单词从未出现
mapA.insert(pair<string, int>(content, 1));//赋值的时候只接受pair类型
else {
if (w == 0 || flag == false)
++it->second;//单词存在
else if (w == 1 && flag == true)
{
it->second+=10;//单词存在+= 10;
}
}
}
start = end + 1;
end = eachline.find_first_of(" ", start);//空格作为单词分隔符
}
}
}
//f:要进行统计的文件路径 n 用户自定义的词组长度
//返回值 无
//实现方法:从流里读取到第n个分隔符后截断
void countGroupWordsWithLength(string f,int n) {
while (getline(input, eachline))
{
//为了避免溢出,保存一个string对象size的最安全的方法就是使用标准库类型string::size_type
string::size_type start = 0;
string::size_type end = eachline.find_first_of(" ");//空格作为单词分隔符
///不统计title和abstract
if (isAbstract(eachline.substr(start, end - start)) || isTitle(eachline.substr(start, end - start))) {
start = end + 1;
end = eachline.find_first_of(" ", start);//空格作为单词分隔符
}
content = eachline.substr(start, end-start);
for (i = 0; i < content.size() && cntNum < n; ++i) {
if (content[i]==' ')
cntNum++;
}
end = end + i;
}
爬取论文信息
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
public class Spiter {
// TODO Auto-generated method stub
public static void main(String[] args) throws IOException {
Document doc=Jsoup.connect("http://openaccess.thecvf.com/CVPR2018.py").maxBodySize(0).get();
Elements listClass = doc.getElementsByAttributeValue("class", "ptitle");
Document paper;
int num=0;
File file=new File("spider.txt");
Writer out=new FileWriter(file);
try {
System.out.print("爬取开始\n");
for(Element element:listClass) {
String link = element.getElementsByTag("a").attr("href");
link="http://openaccess.thecvf.com/"+link;
paper=Jsoup.connect(link).get();
Element Etitle=paper.getElementById("papertitle");
Element Eabstr=paper.getElementById("abstract");
String abstr=Eabstr.text();
String title=Etitle.text();
out.write(num+"\r\n");
out.write("Title: "+title+"\r\n");
out.write("Abstract: "+abstr+"\r\n"); // \r\n即为换行
out.write("\r\n");
out.write("\r\n");
num++;
out.flush(); // 把缓存区内容压入文件
}
System.out.print("爬取结束");
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
out.close();
}
}
测试代码
File *input = fopen("input.txt","r");
File *ans = fopen("ans.txt","r");
String getAns,getInput;
while(!getline(getInput,input)){
getline(getAns,input);
if(!getAns.equal(getInput))
showMessage();
}
cout<<"success"<<endl;
困难
很久没有接触到文件的操作,对于c++的api比较生疏,重新熟悉的过程花了不少时间,另外在爬虫上也花了不少时间。例如Jsoup.connect()函数会限定一个默认的1M大小,使得我爬取的数据只有500多条,然而其他队伍用python做出来的却有900多条,正当准备重写的时候,队友发现了这个问题,为我们节省了不少时间。以及后期需求不断变更,每次都要重新考虑。不过好在这次的配合默契了不少。
总结
通过这次作业,我们可以说重新学习了一遍c++,对相关知识有了更深入的理解和掌握。完成基础需求后发现时间还比较充裕,便去尝试写了一下进阶需求,在这个过程中接触了爬虫,开始时遇到了一些问题,好在队友间相互配合探讨,成功发现并解决了问题,使我们进一步体会到了合作的优势。