1. 程式人生 > >軟工實踐——結對作業2【wordCount進階需求】

軟工實踐——結對作業2【wordCount進階需求】

nco frame 相關 分隔 檢測 image 交換 錯誤 view

附錄:

隊友的博客鏈接

本次作業的博客鏈接

同名倉庫項目地址

一、具體分工

  • 我負責撰寫爬蟲爬取信息以及代碼整合測試,隊友子恒負責寫詞組詞頻統計功能的代碼。

二、PSP表格

PSP2.1Personal Software Process Stages預估耗時(分鐘)實際耗時(分鐘)
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 • 代碼復審 60 90
• Test • 測試(自我測試,修改代碼,提交修改) 100 180
Reporting 報告
• Test Repor • 測試報告 30 30
• Size Measurement • 計算工作量 20 15
• Postmortem & Process Improvement Plan • 事後總結, 並提出過程改進計劃 60 45
合計 880 1170

三、解題思路與具體實現說明。

1、爬蟲使用

  • 因為之前從來沒有接觸過爬蟲和python代碼,所以自己上網搜索了網課。然後看網課一句一句寫的代碼,用谷歌瀏覽器的“檢查”功能查看要爬取內容在網頁中屬於什麽元素並進行相關爬取。(可能看上去寫得"很醜"並且效率不太高)。大概爬取了10多分鐘的信息,爬下來979篇論文標題和摘要存入result.txt文件中,下面是我的爬蟲代碼:
import requests
from urllib.request import urlopen
from bs4 import BeautifulSoup
txt = open(rC:\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

2、代碼組織與內部實現設計

  • 代碼包含一個主要的類testfile以及實現功能的函數,類圖如下所示。
  • Abstract和Title是分別存放摘要和標題內容的字符串。
  • outputByLine函數是在判斷詞組詞頻時能夠按行返回文本內容並且剔除“Title: "和"Abstract: "。
  • phrasecounts分割詞組並且計算詞組數目與詞頻。
  • sWord結構體用作對詞匯進行相關排序的容器。

技術分享圖片

3、關鍵算法的實現思路以及代碼解釋

(1)命令行參數的判斷

  • 功能流程圖如下:

技術分享圖片

  • 功能設計代碼以及具體思路註釋:
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個
        }
    }

(2)詞組詞頻統計和權重詞頻統計的實現

  • 詞組詞頻統計算法的設計流程圖:

技術分享圖片

  • 具體的代碼實現和思路解析
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簽入記錄

技術分享圖片

  • 共簽入了三次。第一次是自己做的那塊功能做完後簽入。而後隊友做的功能完善後,整理合並到代碼裏再次簽入。第三次簽入時進行完單元測試並加入爬蟲結果。

七、遇到的困難

  • 因為我和隊友都對於爬蟲不熟悉,所以我們一開始決定使用c++來寫爬蟲,但是在寫的過程中遇到較多問題沒法解決,決定改用python寫爬蟲,所以我就去看網課寫了一個爬蟲。
  • 在決定分工寫功能模塊的時候,隊友是在他自己之前個人項目的代碼基礎上添加的詞組詞頻統計功能,而他的代碼和我的代碼在函數傳遞的參數上有很多不同,所以在代碼合並的時候出現了很多bug,為此我們討論了挺久終於把bug解決了。
  • 在對問題做需求分析的時候,我們對詞組詞頻統計的要求理解錯了,隊友在某天晚上肝到了3點(超級拼,點贊,但要註意身體)寫了一個各種判斷的詞組詞頻統計函數,第二天測試時發現和樣例有較大出入。(這時內心是絕望的)所以第二天在重新理解需求之後再次寫了一個詞組詞頻統計函數。

八、對於隊友的評價

  • 我的隊友子恒是我的舍友,我覺得他的腦筋很靈活,並且堅持不懈從不抱怨。其實分工上感覺我做得多一些,但是他做的那部分比較難。說實話如果是我寫了很久的一個函數,發現需求理解錯誤要重寫,我可能會崩潰哈。所以給舍友及隊友子恒贊一個!雖然我們的工作效率都不高,都是代碼小渣渣,不過遇到問題時能交換思維也可以成功解決的。

九、學習進度條

第N周新增代碼行累計代碼行本周學習(時)重要成長
2 255 255 11 熟悉了c++的一些函數使用以及c++輸入輸出流
3 105 350 13 初步接觸了代碼性能分析以及一些調試方法。學習了RP制作原型的方法。
4 0 350 6 本周主要是看鄒欣老師的《構建之法》,第一章到第三章。以及組隊討論項目。
5 60 410 8 看網課學習寫爬蟲。
6 176 586 12 熟悉了命令行參數的傳遞。初步了解代碼單元測試。

軟工實踐——結對作業2【wordCount進階需求】