1. 程式人生 > >【原創】python倒排索引之查詢包含某主題或單詞的檔案

【原創】python倒排索引之查詢包含某主題或單詞的檔案

什麼是倒排索引?

倒排索引(英語:Inverted index),也常被稱為反向索引、置入檔案或反向檔案,是一種索引方法,被用來儲存在全文搜尋下某個單詞在一個文件或者一組文件中的儲存位置的對映。它是文件檢索系統中最常用的資料結構。通過倒排索引,可以根據單詞快速獲取包含這個單詞的文件列表。倒排索引主要由兩個部分組成:“單詞詞典”和“倒排檔案”。

假設我們現在有檔案:

test1.txt中存有:我們愛自然語言處理

test2.txt中存有:我們愛計算機視覺

正向索引:{“test1.txt”:["我們",“愛”,"自然語言","處理"],"test2.txt":["我們","愛","計算機","視覺"]}

那麼,我們應該如何通過正向索引找到包含某詞語的檔案呢?我們只能依次遍歷檔案中的內容,從內容中找到是否有該詞語,正向查詢的效率很低。

倒排索引:{"我們":["test1.txt","test2.txt"],"愛":["test1.txt","test2.txt"],"自然語言":["test1.txt"],"處理":["test1.txt"],"計算機":["test2.txt"],"視覺":["test2.txt"]}

建立倒排索引後,我們要想查詢包含某些單詞的檔案,直接從hash表中獲取,是不是就方便多了?接下來,我們用python實現:

現在有基本目錄:

 

python.txt

Python的設計哲學是“優雅”、“明確”、“簡單”。因此,Perl語言中“總是有多種方法來做同一件事”的理念在Python開發者中通常是難以忍受的。Python開發者的哲學是“用一種方法,最好是隻有一種方法來做一件事”。在設計Python語言時,如果面臨多種選擇,Python開發者一般會拒絕花俏的語法,而選擇明確的沒有或者很少有歧義的語法。由於這種設計觀念的差異,Python原始碼通常被認為比Perl具備更好的可讀性,並且能夠支撐大規模的軟體開發。這些準則被稱為Python格言。在Python直譯器內執行import this可以獲得完整的列表。
Python開發人員儘量避開不成熟或者不重要的優化。一些針對非重要部位的加快執行速度的補丁通常不會被合併到Python內。所以很多人認為Python很慢。不過,根據二八定律,大多數程式對速度要求不高。在某些對執行速度要求很高的情況,Python設計師傾向於使用JIT技術,或者用使用C/C++語言改寫這部分程式。可用的JIT技術是PyPy。
Python是完全面向物件的語言。函式、模組、數字、字串都是物件。並且完全支援繼承、過載、派生、多繼承,有益於增強原始碼的複用性。Python支援過載運算子和動態型別。相對於Lisp這種傳統的函數語言程式設計語言,Python對函式式設計只提供了有限的支援。有兩個標準庫(functools, itertools)提供了Haskell和Standard ML中久經考驗的函式式程式設計工具。

java.txt

1.簡單性
Java看起來設計得很像C++,但是為了使語言小和容易熟悉,設計者們把C++語言中許多可用的特徵去掉了,這些特徵是一般程式設計師很少使用的。例如,Java不支援go to語句,代之以提供break和continue語句以及異常處理。Java還剔除了C++的操作符過載(overload)和多繼承特徵,並且不使用主檔案,免去了預處理程式。因為Java沒有結構,陣列和串都是物件,所以不需要指標。Java能夠自動處理物件的引用和間接引用,實現自動的無用單元收集,使使用者不必為儲存管理問題煩惱,能更多的時間和精力花在研發上。
2.面向物件
Java是一個面向物件的語言。對程式設計師來說,這意味著要注意應中的資料和操縱資料的方法(method),而不是嚴格地用過程來思考。在一個面向物件的系統中,類(class)是資料和操作資料的方法的集合。資料和方法一起描述物件(object)的狀態和行為。每一物件是其狀態和行為的封裝。類是按一定體系和層次安排的,使得子類可以從超類繼承行為。在這個類層次體系中有一個根類,它是具有一般行為的類。Java程式是用類來組織的。
Java還包括一個類的擴充套件集合,分別組成各種程式包(Package),使用者可以在自己的程式中使用。例如,Java提供產生圖形使用者介面部件的類(java.awt包),這裡awt是抽象視窗工具集(abstract windowing toolkit)的縮寫,處理輸入輸出的類(java.io包)和支援網路功能的類(java.net包)。
3.分佈性
Java設計成支援在網路上應用,它是分散式語言。Java既支援各種層次的網路連線,又以Socket類支援可靠的流(stream)網路連線,所以使用者可以產生分散式的客戶機和伺服器。
網路變成軟體應用的分佈運載工具。Java程式只要編寫一次,就可到處執行。

c.txt

C語言是一種結構化語言,它有著清晰的層次,可按照模組的方式對程式進行編寫,十分有利於程式的除錯,且c語言的處理和表現能力都非常的強大,依靠非常全面的運算子和多樣的資料型別,可以輕易完成各種資料結構的構建,通過指標型別更可對記憶體直接定址以及對硬體進行直接操作,因此既能夠用於開發系統程式,也可用於開發應用軟體。通過對C語言進行研究分析,總結出其主要特點如下:
(1)簡潔的語言
C語言包含有各種控制語句僅有9種,關鍵字也只有32 個,程式的編寫要求不嚴格且多以小寫字母為主,對許多不必要的部分進行了精簡。實際上,語句構成與硬體有關聯的較少,且C語言本身不提供與硬體相關的輸入輸出、檔案管理等功能,如需此類功能,需要通過配合編譯系統所支援的各類庫進行程式設計,故c語言擁有非常簡潔的編譯系統。 [5]
(2)具有結構化的控制語句
C語言是一種結構化的語言,提供的控制語句具有結構化特徵,如for語句、if⋯else語句和switch語句等。可以用於實現函式的邏輯控制,方便麵向過程的程式設計。 [5]
(3)豐富的資料型別
C語言包含的資料型別廣泛,不僅包含有傳統的字元型、整型、浮點型、陣列型別等資料型別,還具有其他程式語言所不具備的資料型別,其中以指標型別資料使用最為靈活,可以通過程式設計對各種資料結構進行計算。 [5]
(4)豐富的運算子
C語言包含34個運算子,它將賦值、括號等均視作運算子來操作,使C程式的表示式型別和運算子型別均非常豐富。 [5]
(5)可對實體地址進行直接操作
C語言允許對硬體記憶體地址進行直接讀寫,以此可以實現組合語言的主要功能,並可直接操作硬體。C語言不但具備高階語言所具有的良好特性,又包含了許多低階語言的優勢,故在系統軟體程式設計領域有著廣泛的應用。 [5]
(6)程式碼具有較好的可移植性
C語言是面向過程的程式語言,使用者只需要關注所被解決問題的本身,而不需要花費過多的精力去了解相關硬體,且針對不同的硬體環境,在用C語言實現相同功能時的程式碼基本一致,不需或僅需進行少量改動便可完成移植,這就意味著,對於一臺計算機編寫的C程式可以在另一臺計算機上輕鬆地執行,從而極大的減少了程式移植的工作強度。 [5]
(7)可生成的高質量目的碼,高執行效率的程式

首先,我們匯入相應的包:

#用於獲取該目錄下得所有txt檔案,忽略掉資料夾及裡面的
import glob
#主要是一些路徑的操作
import os
#對句子進行分詞或關鍵詞提取
from jieba import analyse

接下來,我們要獲取所有txt檔案的絕對路徑:

#獲取當前pyhtho檔案所在的目錄:當前是:C:\gongoubo\python-work\direc\files
dir_path = os.path.dirname(os.path.abspath(__file__))
print(dir_path)
#儲存txt檔案的絕對路徑為列表,同時為每個檔案建立索引
def file_store():
    files_name =[]
    files_dict = {}
    #獲取file資料夾下所有為txt的檔案
    for i,name in enumerate(glob.glob("file/*.txt")):
        files_dict[i] = name.split('\\')[-1]
        file_name = dir_path + "\\" + name
        files_name.append(file_name)
    return files_name,files_dict

然後,我們讀取每個txt檔案,再對其進行關鍵詞提取,將結果儲存到新的txt中,並用原txt檔案的索引命名:

#讀取每個txt檔案
def transform(files_name):
    #注意開啟的時候需要申明為utf-8編碼
    for i,j in enumerate(files_name):
        #開啟檔案
        tmp = open(j,'r',encoding='utf-8').read()
        #提取關鍵詞
        content = analyse.extract_tags(tmp)
     #也可以進行分詞content=jieba.cut_for_search(tmp),關於jieba分詞,可以看我的自然語言處理之基礎技能 #新建process資料夾 path=dir_path+'\\file\\'+'process' if not os.path.exists(path): os.makedirs(path) #為儲存關鍵詞的txt取名,對應這每個檔案的索引 fp=open(path+'\\'+str(i)+'.txt','w',encoding='utf-8') #將關鍵詞寫入到txt中 fp.write(" ".join(content)) fp.close()

執行後,我們會有如下目錄:其中process資料夾下的是提取關鍵詞後的結果,檔名對應索引,即{0:"c.txt",1:"java.txt",2:"python.txt"}

 接下來,進行倒排索引的構建:

#建立倒排索引
def invert_index():
    path=dir_path+'\\file\\'+'process'
    word_dict = {}
    # 取包含關鍵詞的txt
    for file in glob.glob(path+'/*.txt'):
        #取出txt檔名,也就是檔案的索引
        index = file.split('\\')[-1][0]
        #開啟檔案,並將關鍵詞儲存為列表
        with open(file,'r',encoding='utf-8') as fp:
            word_list=fp.read().split(" ")
        #建立倒排索引,如果單詞不在單詞字典中,就儲存檔案的索引,否則就新增索引到索引列表後
        for word in word_list:
            if word not in word_dict:
                word_dict[word]=[index]
            else:
                word_dict[word].append(index)
    return word_dict

基本的內容我們有了,再考慮我們的輸入,我們希望實現在控制檯輸入幾個單詞,找到最符合的幾個檔案。我們將輸入儲存為單詞列表,以此判斷該單詞是否出現在檔案中,如果出現了,我們將該單詞對應的檔案的索引+1,否則繼續判斷下一個單詞。之後我們得到了關於檔案索引次數的字典,我們按次數從大到小排列,然後取前幾個作為我們最後的結果。當然,我們需要的是原始的檔名,因此,我們還要將索引映射回檔名,相關程式碼如下:

def get_topk(count,topk=None):
    print(count)
    file_index = []
    #如果topk超出了返回的數目,則有多少顯示多少
    if topk > len(count):
        for i in range(0,len(count)):
            file_index.append(int(count[i][0]))
        return file_index
    if len(count)<0:
        print("沒有找到相關的檔案")
        return False
    else:
        for i in range(0,topk):
            file_index.append(int(count[i][0]))
    return file_index
#得到檔名
def get_files(file_index,files_dict):
    res=[]
    for i in file_index:
        res.append(files_dict[i])
    return res

主函式:

def main():
    print("請輸入要查詢的內容,不同單詞間','隔開:")
    words = input().split(',')
    #獲得檔名和檔名索引字典
    files_name, files_dict = file_store()
    #提取關鍵詞或分詞
    transform(files_name)
    #倒排索引建立
    word_dict = invert_index()
    count={}
    #統計檔案索引的次數
    for word in words:
        if word in word_dict:
            for file in word_dict[word]:
                if file not in count:
                    count[file]=1
                else:
                    count[file]+=1
        else:
            continue
    #按次數從大到小排列
    count=sorted(count.items(),key=lambda i:i[1],reverse=True)
    #返回前k個檔案索引
    file_index=get_topk(count,topk=3)
    if file_index != False:
        print("與之描述最可能的檔案是:")
        #返回檔名,並輸出結果
        res=get_files(file_index,files_dict)
        print(res)

最後,我們執行主函式:

if __name__ == '__main__':
    main()

最終結果:

 我們將topk改為3: