福大软工1816 · 第五次作业 - 结对作业2

福大软工1816 · 第五次作业 - 结对作业2

一.链接
github传送门
本次作业传送门
结对同学博客传送门
二.具体分工
  • 王全炯:负责CVPR论文的爬取和格式化,以及wordcount程序命令行代码的编写
  • 郭俊彦:负责wordcount程序主体代码的编写,包括词频和词组频统计等功能模块
三.PSP表格
PSP2.1Personal Software Process Stages预估耗时(分钟)实际耗时(分钟)
Planning计划1030
· Estimate· 估计这个任务需要多少时间1030
Development开发7102460
· Analysis· 需求分析 (包括学习新技术)60400
· Design Spec· 生成设计文档6060
· Design Review· 设计复审300
· Coding Standard· 代码规范 (为目前的开发制定合适的规范)2020
· Design· 具体设计60120
· Coding· 具体编码3601200
· Code Review· 代码复审6060
· Test· 测试(自我测试,修改代码,提交修改)60600
Reporting报告75110
· Test Repor· 测试报告3060
· Size Measurement· 计算工作量1520
· Postmortem & Process Improvement Plan· 事后总结, 并提出过程改进计划3030
合计7952600
四.解题思路与设计描述说明
  • 爬虫使用

    先使用正则表达式匹配出每个论文详情页链接

    然后使用beautifulsoup提取出title和abstract的标签

    最后将其写入txt文件

    import os
    import re
    import urllib
    import requests
    import sys
    import time
    from bs4 import BeautifulSoup
    
    url = "http://openaccess.thecvf.com/CVPR2018.py#"
    request = urllib.request.Request(url)
    resulst = urllib.request.urlopen(request)
    html = resulst.read()
    html = html.decode('utf-8')
    reg = r'<a href="(.+?\.html)">'
    papRe=re.compile(reg)
    paperlist = re.findall(papRe,html)
    le=len(paperlist) 
    
    context = open("result.txt","w",encoding='utf-8')
    url_2="http://openaccess.thecvf.com/"
    i=0
    print (time.strftime('%Y-%m-%d %H:%M:%S',time.localtime(time.time())))
    for i in range(le):
        papget = urllib.request.Request(url_2+paperlist[i])
        papRes = urllib.request.urlopen(papget)
        papRes = papRes.read()
        papRes = papRes.decode('utf-8')
    
        soup = BeautifulSoup(papRes,"html.parser")
        title= soup.select('#papertitle')
        abstr= soup.select('#abstract')
        context.write(str(i)+'\n')
    
        context.write('Title: '+title[0].text)
        context.write('\n')
        #print (title[0].text)
        #print ('\n')
        context.write('Abstract: '+abstr[0].text)
        context.write('\n')
        #print (abstr[0].text)
    print (time.strftime('%Y-%m-%d %H:%M:%S',time.localtime(time.time())))     
    print ("hello world")
  • 代码组织与内部实现设计

    • 主函数进行命令行的解析以及调用相关函数来对字符、单词、词组三类对象进行统计和输出。

    • 字符通过文件流逐行读入后进行ascii码的筛选和统计,同时进行有效行的计算。

    • 单词由结构体链表进行存储,最后通过对单词尾节点的遍历压入优先队列内排序后进行输出。

    • 词组存储在map容器里,以键值对形式存储,之后通过迭代器对map的遍历把词组和频数信息转储到vector容器中借助重载运算符定义比较函数来进行sort排序,最后遍历输出。

程序类图

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

    • 字符、有效行、单词的相关计算均沿用上次个人项目的思路,主要应注意有效行的判断以及换行符的统计以及指针的移动,此处不加累赘
    • 词组统计部分设定了isword(目前单词内字符数)和isgroup(目前词组内单词数)两个标志位,同时借由tmpstr存储目前的临时单词及后缀,preword队列存储目前的临时词组,在游标移动的过程中进行逻辑判断,这是整个算法的核心部分。

例图

  • 在字符串连续读入四个字母之前isword以读到字母为条件自增,之后则开始允许接纳数字。清空的条件显然是读入非法单词或者单词结束即读到分割符的时候。

  • 当游标在分隔符串之后首次读到字母且分隔符串之前已经读入合法单词的时候isgroup++,即isgroup是以一个单词完全结束(包括他的后缀完结)为条件自增。

  • 在一个单词恰完全读取完毕的时候即单词后第一次读取到分隔符,会对isgroup进行判断,如果isgroup==词组长度-1,即算上目前恰读取完毕的单词正好符合词组长度要求时进行一次读入。

  • 可以参考下面的例子结合上面图片进行理解

  • 假设词组长度为3,此处设大写字母代表一个单词,设下划线(______)为分隔符串,当游标在单词C之后首次读到分隔符的时候,isgroup为2加上C这个单词恰好符合词组长度,可以将其存储下来。

    在这个过程中tmpstr会将目前的临时单词(包含他的后缀)存储下来,而队列preword会保存之前的isgroup个单词(包含他的后缀)便于之后的操作。每进行一次词组存储isgroup--同时preword会pop一次。

五.附加题设计与展示
  • 设计的创意独到之处

    由于时间和能力的关系,实现的功能只是在原有的基础上进行了改进,不算是特别有意思或者独到的。

  • 实现思路

    在爬取的过程中,顺带记录下作者信息,并进行自动下载;接下来想做的是能不能扒一扒作者的信息(感觉比较好玩

  • 实现成果展示

六.关键代码解释
  • 字符由getline读入之后根据ascii可打印字符进行筛选,需注意补算上换行符
  • 有效行通过置flag判断当前行是否有非空字符
  • 单词统计由isword标志位根据连续出现四个字母为标准进行判断
  • 词频统计将合法单词放入结构体链表当中最后压入优先队列排序,过程中要注意指针的移动以及旧字符串的保存

上述部分的整体代码都和上一次个人项目较为类似,此处就不加累赘,可以直接移步上次的博客

  • 下面为groupinsert的部分函数,主要思路可以参考上面算法关键写的应该不难理解
for (int i = ibegin; i < len; i++)
    {
        if (str[i] >= 'A' && str[i] <= 'Z')
            str[i] += 32;

        if (str[i] >= 'a'&&str[i] <= 'z')
        {
            if (tmpstr != ""&&isword == 0)//如果前面是一个单词完全结束 
            {
                isgroup++;
                preword.push(tmpstr);
                tmpstr = "";
            }
            tmpstr += str[i];
            isword++;
        }
        else if (isword < 4)//非法单词或分隔符 
        {
            if (isgroup)//之前有合法单词 
            {
                if (tmpstr != ""&&isword == 0)//在合法单词中可以吞字符 
                {
                    if (str[i] >= '0'&&str[i] <= '9')
                    {
                        tmpstr = "";
                        isgroup = 0;
                        preword = queue<string>();
                    }
                    else
                        tmpstr += str[i];
                }
                else//在非法单词中不能吞 
                {
                    tmpstr = "";
                    isgroup = 0;
                    preword = queue<string>();
                }
            }
            else
                tmpstr = "";
            isword = 0;
        }
        else if (str[i] >= '0'&&str[i] <= '9')//合法单词继续接纳数字 
        {
            tmpstr += str[i];
            isword++;
        }
        else//分隔符 合法单词恰好结束tmpstr接受此分隔符同时开启一次读入
        {
            if (isgroup == (GL - 1))//之前的单词有GL-1个再加上目前这个即可压入树
            {
                temp = preword;
                insertstr = "";
                for (int j = 1; j < GL; j++)//插入长度为GL-1词组 
                {
                    insertstr += temp.front();
                    temp.pop();
                }
                insertstr += tmpstr;//插入目前为止的最后一个单词
                iter = wordgroup.find(insertstr);
                if (iter != wordgroup.end())
                    //iter->second++;
                    wordgroup[insertstr]++;
                else
                    wordgroup.insert(map<string, int>::value_type(insertstr, 1));
                isgroup--;
                preword.pop();
            }
            tmpstr += str[i];
            isword = 0;
        }
    }
    if (isword >= 4)//特殊处理最后一个单词 
    {
        if (isgroup == (GL - 1))//之前的单词有GL-1个再加上目前这个即可压入树
        {
            temp = preword;
            temp.push(tmpstr);
            insertstr = "";
            for (int j = 1; j <= GL; j++)//插入长度为词组 
            {
                insertstr += temp.front();
                temp.pop();
            }
            iter = wordgroup.find(insertstr);
            if (iter != wordgroup.end())
                //iter->second++;  
                wordgroup[insertstr]++;
            else
                wordgroup.insert(map<string, int>::value_type(insertstr, 1));
        }
    }
}

简单来说就是借助isword和isgroup两个标志位,通过tmpstr存储目前的临时单词及后缀,preword队列存储目前的临时词组,在游标移动的过程中进行逻辑判断。

  • 下面为map的定义和迭代器iter
map<string, int>wordgroup;
map<string, int>::iterator iter;
  • 下面为之后存入vector的WGnode以及后面sort要用到的cmp函数
struct WGnode
{
    string group;
    int count;
    WGnode() {}
    WGnode(string _group, int _count) :group(_group), count(_count) {}
};

bool cmp(WGnode a, WGnode b)
{
    if (a.count == b.count)
        return (a.group.compare(b.group) <= 0);
    return a.count > b.count;
}
  • 下面为词组频率排序和输出的函数,通过min来控制输出数量
void  groupFreqout(ofstream &fout)
{
    map<string, int>::iterator it;
    vector<WGnode> vec;
    for (it = wordgroup.begin(); it != wordgroup.end(); it++)
    {
        vec.push_back(WGnode(it->first, it->second));
    }
    sort(vec.begin(), vec.end(), cmp);

    int length = vec.size();
    length = min(outctrmode, length);

    for (int i = 0; i < length; i++)
    {
        fout << "<" << vec[i].group << ">: " << vec[i].count << endl;
    }
}
七.性能分析与改进

性能分析

  • 描述你改进的思路
    • 最早的想法是词组统计上也完全沿用单词统计的那一套,但是因为不明的指针问题最后放弃了。在整体的效率上map应该是高于结构体链表的。之后词频的计算部分也可以把单词的容器由结构体迁移到map来。词组方面目前的做法是遍历map再把里面的键值对抓出来放到vector里面从而方便排序,以后的优化可以放在这个地方,试着不用更换容器直接进行排序,应该会剩下不少空间和时间。
  • 展示性能分析图和程序中消耗最大的函数
八.单元测试

展示出项目部分单元测试代码,并说明测试的函数,构造测试数据的思路

九.Github的代码签入记录

1472161-20181011015810004-971721009.png

十.代码模块异常或结对困难及解决方法
  • 问题描述

    刚开始在词组的存储以及排序上面沿用了上次单词统计的思路,采取结构体链表进行处理。完成初版之后程序在devC++上快乐地奔跑,而放在vs2017中时则会随机性地出现错误词组的输出,偶尔会报指针域冲突的错误。

  • 做过哪些尝试

    对于代码的总体逻辑以及词组插入链表的过程都进行了分析,同时也进行调试监控节点存储字符串内容是否正确,但是都没有找出问题是什么。后来干脆重写了词组插入函数groupinsert,然而呈现出一样的问题,再次对代码进行重审以及请教各路大佬之后依旧是很难看出问题出在哪里。插入中间变量的临时输出语句也看不出任何问题,实在是很折磨。最后只得改变存储容器,由结构体链表转战map。

  • 是否解决

    用STLmap来存储词组-频数对以后输出之类都很顺利,在devC++和vs2017上都能愉快翱翔

  • 有何收获

    极大地磨练了小组的受虐能力,在这个过程中自己对于vs的调试能力也有了一定的提高,对于链表的组织也更加的熟练了,同时学习了map的使用。以及学习到不要一直死磕一个问题,该转换思路的时候要学会变通。

十一.评价你的队友
  • 值得学习的地方

    全炯是一位学习效率很高且沟通能力很好的同学,和他合作的过程非常愉快。他对于新技术的学习速度很快,在我们决定开始开发之后就用python写出了爬去cvpr论文的程序,同时思维比较灵活,在我们反复遇到指针域冲突问题之后,不像我会受限于旧的思路和结构,而是提出应该尝试一下新的容器。

  • 需要改进的地方

    和我一样都有一点点咸鱼,在合作进行开发的过程当中,常会碰到两个人都无法解决的问题,比较尴尬。

十二.学习进度条
第N周新增代码(行)累计代码(行)本周学习耗时(小时)累计学习耗时(小时)重要成长
1001212学习构建之法的前面部分内容
22002002133学习需求开发模型,巩固c++编程基础
32004002255学习原型工具墨刀,巩固c++
45709704398巩固结构体链表知识,学习STL map的使用,学习vs的调试功能使用方法

转载于:https://www.cnblogs.com/-JAM/p/9769752.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值