作业题目链接

队友链接

Fork的同名仓库的Github项目地址

具体分工

玮哥负责命令参数判断、单词权重统计,我只负责词组词频统计(emmmm)。

PSP表格

预估耗时(分钟) 实际耗时(分钟)
Planning 计划
Estimate 估计这个任务需要多少时间 880 1170
Development 开发
Analysis 需求分析 (包括学习新技术) 100 120
Design Spec 生成设计文档 20 10
Design Review 设计复审 10 10
Coding Standard 代码规范 20 60
Design 具体设计 60 100
Coding 具体编码 400 510
Code Review 代码复审 400 510
Test 测试(自我测试,修改代码,提交修改) 100 180
Reporting 报告
Test Repor 测试报告 30 30
Size Measurement 计算工作量 20 15
Postmortem & Process Improvement Plan 事后总结, 并提出过程改进计划 60 45
合计 880 1170

解题思路描述与设计实现说明

爬虫使用

队友用Python编辑的爬虫代码。

import requests
from urllib.request import urlopen
from bs4 import BeautifulSoup
txt = open(r'C:\Users\Administrator\Desktop\result.txt','w',encoding='utf-8')
#打开文件
i = 0
def getPaper(newsUrl): #获取相关信息的函数
res = requests.get(newsUrl) #打开链接
res.encoding = 'utf-8'
soup = BeautifulSoup(res.text,'html.parser') #把网页内容存进容器
Title = soup.select('#papertitle')[0].text.strip() #找到标题元素爬取
print("Title:",Title,file=txt)
Abstract = soup.select('#abstract')[0].text.strip() #找到摘要元素爬取
print("Abstract:",Abstract,"\n\n",file=txt)
return sUrl = 'http://openaccess.thecvf.com/CVPR2018.py'
res1 = requests.get(sUrl)
res1.encoding = 'utf-8'
soup1 = BeautifulSoup(res1.text,'html.parser')
for titles in soup1.select('.ptitle'): #返回每个超链接
t = 'http://openaccess.thecvf.com/'+ titles.select('a')[0]['href']
print(i,file=txt)
getPaper(t) #循环打开每个子页面并进行爬取
i=i+1

代码组织与内部实现设计(类图)

  • 代码包含一个主要的类testfile以及实现功能的函数,类图如下所示。
  • Abstract和Title是分别存放摘要和标题内容的字符串。
  • outputByLine函数是在判断词组词频时能够按行返回文本内容并且剔除“Title: "和"Abstract: "。
  • phrasecounts分割词组并且计算词组数目与词频。
  • sWord结构体用作对词汇进行相关排序的容器。

算法的关键与关键实现部分流程图

命令行参数实现流程图

词组词频统计流程图

关键代码解释

命令行参数判断

while (1) //循环判断命令行参数
{
i++;
if (i == argc)
break;
if (argv[i][0] == '-') //判断当前参数首字符是否为'-'
{
switch (argv[i][1]) //判断当前参数第二个字符
{
case 'i':
{
i++;
input = argv[i];//第二个字符为i时输入下一个参数赋值给input
break;
}
case 'o':
{
i++;
output = argv[i];//第二个字符为o时输入下一个参数赋值给output
break;
}
case 'w':
{
i++;
judgeW = atoi(argv[i]);//第二个字符为w时judgeW为下一个参数的整数值留作输出判断
break;
}
case 'm':
{
i++;
judgeM = atoi(argv[i]);//第二个字符为m时judgeM为下一个参数的整数值留作输出判断
break;
}
case 'n':
{
i++;
judgeN = atoi(argv[i]);//第二个字符为n时judgeN为下一个参数的整数值留作输出判断
break;
}
default:
cout << "输入参数有误" << endl;
break;
}
}
}
配合命令行参数判断的输出代码如下: if (judgeW == 0)
{
if (judgeM == 0) //非权重词频统计
{
f1 = f1.countline(input, f1);
f1.Abstract = changeDx(f1.Abstract);
f1.Title = changeDx(f1.Title);//大小写转换
f1 = f1.countword(f1, f1.Title, 1);
f1 = f1.countword(f1, f1.Abstract, 1);
}
else //非权重词组词频统计
{
f1 = f1.outputByLine(input, f1, 1, judgeM);
}
}
else
{
if (judgeM == 0) //权重词频统计
{
f1 = f1.countline(input, f1);
f1.Abstract = changeDx(f1.Abstract);
f1.Title = changeDx(f1.Title);//大小写转换
f1 = f1.countword(f1, f1.Title, 10);
f1 = f1.countword(f1, f1.Abstract, 1);
}
else //权重词组词频统计
{
f1 = f1.outputByLine(input, f1, 10, judgeM);
}
} if (judgeN == 0)//如果没有定义n那么按默认输出
outCome1(ww, num, output, f1);
else
{
if (judgeN > f1.getwords())
{
cout << "输入的n值超过了文本的所有单词数,将为您按序输出文本的所有单词" << endl;//对于参数过大的错误判断
outCome2(ww, f1.getwords(), output, f1);
}
else
{
outCome2(ww, judgeN, output, f1);//如果定义了n那么输出前n个
}
}

词组词频统计

testfile testfile::phrasecounts(string temp, int t, int quan, testfile f1)
{
int word = 0;
int i = 0;
int j = 0;
long int n = 0;
int m = 0;
string sumwr = "";//初始化一个存放词组的字符串
n = temp.length();
char x[10];
for (j = 0; j < n; j++)
{
if (temp[j] >= 'A'&&temp[j] <= 'Z')
{
temp[j] += 32;
}
}
string phrase;
int flag = 0, k = 0;
int mark = 0, al = 0;//mark用来记录存储的单词数,al用来记录成词组的第二个单词的首字母序号
for (i = 0; i < n; i++)
{
if (!((temp[i] >= 48 && temp[i] <= 57) || (temp[i] >= 97 && temp[i] <= 122)))
{
if (mark > 0)
{
sumwr = sumwr + temp[i];//如果此时已记录一个及以上的单词,将此分隔符也录入词组字符串
}
continue;
}
else
{
for (j = 0; j < 4 && i < n; j++)
{ if (!((temp[i] >= 48 && temp[i] <= 57) || (temp[i] >= 97 && temp[i] <= 122)))
{
mark = 0;
sumwr = "";//检测到非法单词,重新初始化mark和词组字符串
break;
}
else
{
if (j == 0 && mark == 1)
{
al = i;
}
x[j] = temp[i++];//temp中存入四个非空格字符
}
}
if (j == 4)
{
for (m = 0; m < 4; m++)
{
if (x[m] < 97 || x[m]>122)
{
flag = 1;
mark = 0;
sumwr = "";
break;//判断这四个字符是否都是字母,检测到非法单词,重新初始化mark和词组字符串
}
}
if (flag == 0)//判断为一个单词
{
char *w = new char[100];//存放单词
for (m = 0; m < 4; m++)
{
w[k++] = x[m];//temp中字符存入w
}
while (((temp[i] >= 48 && temp[i] <= 57) || (temp[i] >= 97 && temp[i] <= 122)) && i < n)//继续存入单词剩余字符
{
w[k++] = temp[i++];
}
w[k] = '\0';
sumwr = sumwr + w;//将单词存入词组字符串数组
mark++;
delete[]w;
k = 0;
if (mark == t)
{
loadword(sumwr, quan);//如果此时单词存入达到所需数量,将此词组字符串存入map函数,并初始化mark和字符串
word++;
mark = 0;
i = al;//让i等于存入字符串的第二个单词的第一个字母序号,重新开始查询词组
sumwr = "";
}
i--;
}
else
{
flag = 0;
j = 0;
mark = 0;
sumwr = "";
}
}
}
}
i = 0;
f1.words += word;
return f1;
}

性能分析与改进

  • 输入的命令行参数为:-i C:\Users\Administrator\Desktop\result.txt -m 2 -n 20 -w 1 -o C:\Users\Administrator\Desktop\output.txt 即对爬取的文件进行词组分割,2个单词一组而后执行权重词频统计并且输出频率最高的前20个词组。
  • 性能探查器返回结果

  • 因为本次测试执行的是词组分割的词频统计,所以占比最高的函数就是词组分割函数phrasecount。其次本次程序执行时间在15秒到16秒左右,所以还有优化空间。loadword是把词组存入map,这是c++自带的一个统计词频并且按字典排序的容器,如果有能力的话希望自己能用哈希表写一个类似的排序也许能够提高运行效率。

单元测试

  • 以下对字符数统计,行数统计,单词数统计以及词组分割和词组词频统计四个函数进行单元测试。
#include "stdafx.h"
#include "CppUnitTest.h"
#include "C:\Users\Administrator\Desktop\软工实践\结对作业2\单元测试\WordCount2\WordCount2\WordCount2\WordCount2.h"
#include <iostream>
using namespace Microsoft::VisualStudio::CppUnitTestFramework; namespace WordCountTest1
{
TEST_CLASS(UnitTest1)
{
public: TEST_METHOD(phraseCount)
{
testfile f1;
f1=f1.outputByLine("C:\\Users\\Administrator\\Desktop\\result.txt", f1, 1, 3);//按行输出后进行词组词频统计
}
TEST_METHOD(wordCount)
{
testfile f1;
f1 = f1.countline("C:\\Users\\Administrator\\Desktop\\result.txt", f1);
f1 = f1.countword(f1, f1.Abstract, 1);
f1 = f1.countword(f1, f1.Title, 1);
}
TEST_METHOD(countCharacters)
{
testfile f1;
string a;
f1 = f1.countcha("C:\\Users\\Administrator\\Desktop\\result.txt", f1);
Assert::AreEqual(f1.getcharacters(), (int)1220332);
}
TEST_METHOD(countLine)
{
testfile f1;
f1 = f1.countline("C:\\Users\\Administrator\\Desktop\\result.txt", f1);
Assert::AreEqual(f1.getlines(), (int)2937);
}
};
}
  • 单元测试通过

单元测试列表:

  • 观察单元测试图可以看到,在对一百万字符数的文本文档进行统计时,总执行时间要20秒,其中词组分割花费11秒最多,再者就是单词分割,需要6秒。还有很大的优化空间啊。

Github签入记录

  • 共签入了三次。第一次是自己做的那块功能做完后签入。而后队友做的功能完善后,整理合并到代码里再次签入。第三次签入时进行完单元测试并加入爬虫结果。

遇到的代码模块异常或结对困难及解决方法

  • emmm我负责的是词组词频统计的代码,一开始做的时候甚至理解错了题目的意思,将m(词组单词数)理解成了分隔符的数量,幸好玮哥很早就发现了这个问题,我才有时间得以重做。
  • 我设计的词组词频统计起初是一篇文本为目标,但是这样的话,会将换行符也当成分隔符存入词组字符串,而且不好区分和消除title和abstract的干扰,于是我便想到一行一行的统计,因为原文中,title及其内容为一行,abstract及其内容也是一行,玮哥写的按行读入代码又有erase函数在读入一行的同时削去了title:和abstract:,把这样读入的一行带入我词组词频统计代码,既能消除换行符和title、abstract的影响,又能减少一次带入函数的数据量,防止数据过多而出现的代码运行错误。

评价你的队友

   玮哥是我大腿,除了词组词频统计的代码,其他的像是什么代码整合,单元测试七七八八的都是由他一个人完成,中途还因为我的失误浪费了一天的时间(有点小愧疚)。下次合作任务时,争取做到细心再细心,像玮哥一样多帮队友分担一些的任务。

学习进度条

第n周 新增代码 本周学习耗时 重要成果
3 400 12h 对于字符串的处理更加精进了

结对第2次作业——WordCount进阶需求的更多相关文章

  1. 结队第二次作业——WordCount进阶需求

    结队第二次作业--WordCount进阶需求 博客地址 051601135 岳冠宇 博客地址 051604103 陈思孝 博客地址 Github地址 具体分工 队友实现了爬虫功能,我实现了wordco ...

  2. 《软件工程实践》第五次作业-WordCount进阶需求 (结对第二次)

    在文章开头给出结对同学的博客链接.本作业博客的链接.你所Fork的同名仓库的Github项目地址 本作业博客链接 github pair c 031602136魏璐炜博客 031602139徐明盛博客 ...

  3. 第二次结对作业-WordCount进阶需求

    原博客 队友博客 github项目地址 目录 具体分工 需求分析 PSP表格 解题思路描述与设计实现说明 爬虫使用 代码组织与内部实现设计(类图) 算法的关键与关键实现部分流程图 附加题设计与展示 设 ...

  4. 软工实践第五次作业-WordCount进阶需求

    软工实践作业(五) GitHub 作业链接 结对博客 031602240 具体分工 PSP表格 代码规范 解题思路与设计说明 爬虫使用 代码组织与内部实现设计(类图) 算法关键 实现方法 流程图 附加 ...

  5. 软工实践——结对作业2【wordCount进阶需求】

    附录: 队友的博客链接 本次作业的博客链接 同名仓库项目地址 一.具体分工 我负责撰写爬虫爬取信息以及代码整合测试,队友子恒负责写词组词频统计功能的代码. 二.PSP表格 PSP2.1 Persona ...

  6. 结对作业-WordCount进阶版

    1.在文章开头给出博客作业要求地址. 博客园地址:https://www.cnblogs.com/happyzm/p/9559372.html 2.给出结对小伙伴的学号.博客地址,结对项目的码云地址. ...

  7. 结对作业——WordCount进阶版

    Deadline: 2018-10-7 22:00PM,以博客提交至班级博客时间为准 要求参考来自:https://www.cnblogs.com/xinz/archive/2011/11/27/22 ...

  8. 福州大学软件工程1816 | W班 第6次作业WordCount成绩排名

    作业链接 WordCount进阶需求 评分细则 本次个人项目分数由两部分组成(博客分满分40分+程序得分满分60分) 博客评分细则(满分60,最终折算为40分) 在文章开头给出结对同学的博客链接.本作 ...

  9. 《ABCD组》第四次作业:项目需求调研与分析

    <ABCD组>第四次作业:项目需求调研与分析 项目 内容 这个作业属于哪个课程 http://www.cnblogs.com/nwnu-daizh/ 这个作业的要求在哪里 https:// ...

随机推荐

  1. Ubuntu16.10下使用VSCode开发.netcore

    按照通常的套路,首先创建一个空白的解决方案,需要用到.netcore sdk命令: dotnet new sln -o dotnetcore_tutrorial 这个时候可以看到在目标目录下生成了一个 ...

  2. JS 浮点型计算的精度问题 推荐的js 库 推荐的类库 Numeral.js 和 accounting.js

    推荐的类库 Numeral.js 和 accounting.js 文章来自 http://www.css88.com/archives/7324#more-7324

  3. POJ 3080 Blue Jeans 后缀数组, 高度数组 难度:1

    题目 http://poj.org/problem?id=3080 题意 有m个(2<=m<=10)不包含空格的字符串,长度为60个字符,求所有字符串中都出现过的最长公共子序列,若该子序列 ...

  4. Oracle 12c的自增列Identity Columns

    在Oracle的12c版本中,Oracle实现了类似MySQL中的auto_increment的自增列,下面我们看一起Oracle是怎么实现的. Oracle Database 12c Enterpr ...

  5. RabbitMQ direct类型的Exchange

    就目前来说,Exchange是与消息发送端有关的,因为它可以指定将消息发送到哪个或哪些队列中. 本篇文章介绍的direct类型就是指定将消息定向发送到哪个队列中. direct,顾名思义,就是直接的意 ...

  6. Top k问题的讨论(三种方法的java实现及适用范围)

    在很多的笔试和面试中,喜欢考察Top K.下面从自身的经验给出三种实现方式及实用范围. 合并法 这种方法适用于几个数组有序的情况,来求Top k.时间复杂度为O(k*m).(m:为数组的个数).具体实 ...

  7. 内存管理和GC算法以及回收策略

    JVM内存组成结构 JVM栈由堆.栈.本地方法栈.方法区等部分组成,结构图如下所示: JVM内存回收 Sun的JVMGenerationalCollecting(垃圾回收)原理是这样的:把对象分为年青 ...

  8. C++基础知识:构造与析构

    1.构造函数的定义: C++中的类可以定义与类名相同的特殊成员函数这种与类名相同的成员函数叫做构造函数构造函数在定义时可以有参数,但是没有任何返回类型的声明 2.构造函数的调用: 一般情况下C++编译 ...

  9. 获取表单内元素组装成对象类型,方便datagrid的load取参数

    /** * 获取表单数据,并将其转换为对象 */ function getFormObj(formId) { var formObj = {}; var inputs = $('#'+formId). ...

  10. poj3279(dfs+二进制枚举思路)

    题意转载自https://www.cnblogs.com/blumia/p/poj3279.html 题目属性:DFS 相关题目:poj3276 题目原文:[desc]Farmer John know ...