對英文文檔中的單詞與詞組進行頻率統計


一、程序分析

 

1、以只讀模式讀取文件到字符串

def process_file(path):
    try:
        with open(path, 'r') as file:
            text = file.read()
    except IOError:
        print("Read File Error!")
        return None
    return text

 

2、對字符串進行數據清洗,返回一個字典

import re
word_list = re.sub('[^a-zA-Z0-9n]', ' ', textString).lower().split()

使用正則表達式過濾掉文檔中的特殊字符,把它們全部替換為空格,方便后續的分隔操作。(忽略大小寫,所以全部使用小寫字母)

  

  2.1、只考慮單詞頻率統計

for word in word_list:
     if word in word_freq:
          word_freq[word] += 1
      else:
           word_freq[word] = 1

  判斷單詞列表中的單詞是否在單詞頻率字典中。

  如果這個單詞在字典中,則該單詞的個數加1;

  如果這個單詞不在字典中,則以這個單詞為鍵,賦值為1,表示這個單詞第一次出現。

 

  2.2、考慮單詞和詞組的頻率

    2.2.1、數據結構

    詞組是由單詞連接構成的,一個單詞既可以與前面的單詞構成詞組,也可以與后面的單詞構成詞組。

    同時,一個單詞可能在文章中多次出現,並且和前后的單詞構成多種不同的詞組。

    這也就表示幾乎每一個單詞可以一前一后發散出去,這與圖狀結構頗為類似。(適用於兩個單詞構成的詞組)

    選擇圖狀結構與當前的問題頗為契合。但是,目前python沒有一種已知的圖類來供我們操作。(自定義類暫不作考慮) 

    於是我想到了樹狀結構(兩個以上單詞構成的詞組也適用),砍去了單詞向前的發散,只保留向后的。至此,問題就變成了構建森林。

    而在python中,森林是比較容易表示的。用一個字典放置每一棵樹的根節點,字典套字典,就形成了森林。

    可能你們要問單詞和詞組的統計個數放在哪。我使用“Value”作鍵,鍵值number這樣一種結構放在該單詞的字典中。

    具體的字典示例樣本參見sample.json

 

    2.2.2、具體流程

  

    以索引停在50,詞組限制單詞個數為3為例:

    (1)在樹的根節點中尋找mother節點,找不到就創建mother節點。

                "mother":{
                    "Value":1
                }            

    (2)在樹的根節點中尋找your節點,找到了繼續尋找mother節點,找不到就創建mother節點。(不可能找不到,因為索引經過49之后肯定存在your根節點)

    (3)以此類推,在樹的根節點中尋找did節點,找到了繼續尋找your節點,,找到了繼續尋找mother節點,找不到就創建mother節點......

 

    具體實現:

count = len(word_list)
i = 0
while i < count: # 因為需要用到索引遍歷列表,只能使用while來遍歷列表
finish = i
start = i - num + 1 # num表示詞組的單詞個數限制,start表示以該單詞作為詞組結尾的第一個單詞的索引
if start < 0:
start = 0 # 處理開始時索引前面沒有單詞的特殊情況
index = i
while index >= start: # 做num次建立節點
if word_list[i] in get_dict_value(word_freq, word_list[index: finish]).keys():
get_dict_value(word_freq, word_list[index: finish])[word_list[i]]['Value'] += 1
else:
get_dict_value(word_freq, word_list[index: finish]).update({word_list[i]: {'Value': 1}})
index -= 1
i += 1
    get_dict_value函數
def get_dict_value(word_freq={}, keys=[]):
    """如果keys為字符串,返回word_freq字典中以keys為鍵的值。
    如果keys為列表,則使用eval()函數進行字符串拼接,深度查找word_freq字典中以keys為鍵的值。"""
    if type(keys).__name__ == 'str':
        return word_freq[keys]
    else:
        count = len(keys)
        if count == 0:
            return word_freq
        elif count == 1:
            return word_freq[keys[0]]
        elif count == 2:
            return word_freq[keys[0]][keys[1]]
        elif count == 3:
            return word_freq[keys[0]][keys[1]][keys[2]] #對尋找三個單詞以下的詞組進行特化
        else:
            string = "word_freq['"
            string += "']['".join(keys)
            string += "']"
            return eval(string) #動態尋找字典的值的一般版本


    2.2.3、字典格式化

    數據已經存儲到了森林中,接下來就是如何把森林格式化成普通的字典。

    相比深度優先遍歷,我選擇的是更易理解與實現的廣度優先遍歷。

    廣度優先遍歷的原理我不再贅述。

def format_dict(word_freq={}):
    """對統計短語的情況生成的復雜字典進行格式化,格式化后的形式為<str,int>"""
    formated_word_freq = {}
    phrases = []
    for word in word_freq.keys(): #將所有根節點放入隊列中
        phrases.append(word)
    while len(phrases) > 0: #只要隊列還有元素,就表明還沒有遍歷結束
        phrase = phrases[0]
        if len(get_dict_value(word_freq, phrase)) == 1 and type(phrase).__name__ == 'list':
            formated_word_freq[' '.join(phrase)] = get_dict_value(word_freq, phrase)['Value'] #搜索到葉子節點了,這個節點存入格式化好的字典中
        else:
            for next_word in get_dict_value(word_freq, phrase): #除"Value"鍵值對以外的鍵都入隊
                temp = []
                if type(phrase).__name__ == 'str':
                    temp.append(phrase)
                else:
                    temp.extend(phrase)
                if next_word != 'Value':
                    temp.append(next_word)
                    phrases.append(temp)
        phrases.pop(0) #搜索完隊列的第一個節點的子節點,這個節點出隊
    # print(formated_word_freq)
    return formated_word_freq

 

3、輸出字典(沒有改動)

def output_result(word_freq):
    if word_freq:
        sorted_word_freq = sorted(word_freq.items(), key=lambda v: v[1], reverse=True)
        for item in sorted_word_freq[:10]:
            print(item)

 

4、主函數

if __name__ == "__main__":
    import argparse

    parser = argparse.ArgumentParser()
    parser.add_argument('path') #路徑
    parser.add_argument('num') #詞組單詞個數
    args = parser.parse_args()
    path = args.path
    num = int(args.num)
    buffer = process_file(path)
    if buffer:
        word_freq = process_buffer(buffer, num)
        if num != 1:
            word_freq = format_dict(word_freq)
        output_result(word_freq)

命令行接受兩個參數,一個是英文文檔的路徑,還有一個是構成一個詞組的最大單詞個數(為1時即單詞統計)。沒有這個限制,森林里就會出現一棵從第一個單詞到最后一個單詞的樹。

 

二、代碼風格說明

  基本遵從PEP8,PEP8 涵蓋了諸如空格、函數/類/方法之間的換行、import、對已棄用功能的警告之類的尋常東西,是一個不錯的准則。

def get_dict_value(word_freq={}, keys=[]):
    """如果keys為字符串,返回word_freq字典中以keys為鍵的值。
    如果keys為列表,則使用eval()函數進行字符串拼接,深度查找word_freq字典中以keys為鍵的值。"""
    if type(keys).__name__ == 'str':
        return word_freq[keys]
    else:
        count = len(keys)
        if count == 0:
            return word_freq
        elif count == 1:
            return word_freq[keys[0]]
        elif count == 2:
            return word_freq[keys[0]][keys[1]]
        elif count == 3:
            return word_freq[keys[0]][keys[1]][keys[2]] #對尋找三個單詞以下的詞組進行特化
        else:
            string = "word_freq['"
            string += "']['".join(keys)
            string += "']"
            return eval(string) #動態尋找字典的值的一般版本

    注釋基本也只在關鍵地方才有,不必面面俱到。

 

三、程序運行命令、運行結果截圖 

    運行命令:

python statistic.py Gone_with_the_wind.txt 1
python statistic.py Gone_with_the_wind.txt 2

 

四、簡單性能分析 

  運行命令:

python -m cProfile -o result.out -s cumulative statistic.py Gone_with_the_wind.txt 2

python gprof2dot.py -f pstats result.out | dot -Tpng -o result.png

 

  單詞統計性能分析:

  詞組統計性能分析:

  在進行單詞統計時,時間有五成花費在過濾特殊字符上,有三成花費在創建字典上。

  而在詞組統計中,過濾特殊字符只占了一成,兩層時間在創建森林,六成時間在格式化森林。其中,尤其以list的pop()函數占用時間最多,將近一半的時間用來出隊。

  

  程序優化:嘗試在不pop()得情況下修改廣度優先遍歷。嘗試另一種數據結構,森林比圖占的內存多。

  修改中...


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM