结对第2次作业——WordCount进阶需求

作业题目链接
队友链接
Fork的同名仓库的Github项目地址

具体分工

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

PSP表格

预估耗时(分钟)实际耗时(分钟)
Planning计划
Estimate估计这个任务需要多少时间8801170
Development开发
Analysis需求分析 (包括学习新技术)100120
Design Spec生成设计文档2010
Design Review设计复审1010
Coding Standard代码规范2060
Design具体设计60100
Coding具体编码400510
Code Review代码复审400510
Test测试(自我测试,修改代码,提交修改)100180
Reporting报告
Test Repor测试报告3030
Size Measurement计算工作量2015
Postmortem & Process Improvement Plan事后总结, 并提出过程改进计划6045
合计8801170

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

爬虫使用

队友用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结构体用作对词汇进行相关排序的容器。
    1481611-20181012211933926-157457801.png

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

命令行参数实现流程图

1481611-20181012212908755-1472947435.png

词组词频统计流程图

1481611-20181012212943761-266887572.jpg

关键代码解释

命令行参数判断

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个词组。
  • 性能探查器返回结果
    1481611-20181012221909523-21825616.jpg
  • 因为本次测试执行的是词组分割的词频统计,所以占比最高的函数就是词组分割函数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);
        }
    };
}
  • 单元测试通过

    单元测试列表:

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

Github签入记录

1481611-20181012231328774-1470925223.png

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

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

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

评价你的队友

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

学习进度条

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

转载于:https://www.cnblogs.com/czhasd/p/9780836.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Mapreduce实例-WordCount是一个经典的MapReduce程序,用于统计文本中每个单词出现的数。它的工作原理是将输入的文本划分为多个片段,每个片段由多个键值对组成,其中键是单词,值是1。然后通过Map阶段将每个片段中的单词提取出来,并将每个单词映射为键值对,其中键是单词,值是1。接下来,通过Shuffle和Sort阶段将具有相同单词的键值对聚集在一起。最后,通过Reduce阶段将相同单词的计数值进行累加,得到每个单词的总数。 以下是一个示例代码片段,展示了WordCount程序的基本结构和关键组件: ```java import java.io.IOException; import java.util.StringTokenizer; import org.apache.hadoop.fs.Path; import org.apache.hadoop.io.IntWritable; import org.apache.hadoop.io.Text; import org.apache.hadoop.mapreduce.Job; import org.apache.hadoop.mapreduce.Mapper; import org.apache.hadoop.mapreduce.Reducer; import org.apache.hadoop.mapreduce.lib.input.FileInputFormat; import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat; public class WordCount { public static class TokenizerMapper extends Mapper<Object, Text, Text, IntWritable>{ private final static IntWritable one = new IntWritable(1); private Text word = new Text(); public void map(Object key, Text value, Context context) throws IOException, InterruptedException { StringTokenizer itr = new StringTokenizer(value.toString()); while (itr.hasMoreTokens()) { word.set(itr.nextToken()); context.write(word, one); } } } public static class IntSumReducer extends Reducer<Text,IntWritable,Text,IntWritable> { private IntWritable result = new IntWritable(); public void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException { int sum = 0; for (IntWritable val : values) { sum += val.get(); } result.set(sum); context.write(key, result); } } public static void main(String[] args) throws Exception { Job job = Job.getInstance(); job.setJarByClass(WordCount.class); job.setMapperClass(TokenizerMapper.class); job.setCombinerClass(IntSumReducer.class); job.setReducerClass(IntSumReducer.class); job.setOutputKeyClass(Text.class); job.setOutputValueClass(IntWritable.class); FileInputFormat.addInputPath(job, new Path(args[0])); FileOutputFormat.setOutputPath(job, new Path(args[1])); System.exit(job.waitForCompletion(true) ? 0 : 1); } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值