日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

海量数据处理之倒排索引

發(fā)布時間:2023/12/10 编程问答 24 豆豆
生活随笔 收集整理的這篇文章主要介紹了 海量数据处理之倒排索引 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

前言:本文是對博文http://blog.csdn.net/v_july_v/article/details/7085669的總結和引用

一,什么是倒排索引

問題描述:文檔檢索系統(tǒng),查詢那些文件包含了某單詞,比如常見的學術論文的關鍵字搜索。

基本原理及要點:為何叫倒排索引?一種索引方法,被用來存儲在全文搜索下某個單詞在一個文檔或者一組文檔中的存儲位置的映射。
 以英文為例,下面是要被索引的文本:
? ? T0 = "it is what it is"
? ? T1 = "what is it"
? ? T2 = "it is a banana"
??? 我們就能得到下面的反向文件索引:
?? "a":????? {2}
? ? "banana": {2}
? ? "is":???? {0, 1, 2}
? "it":???? {0, 1, 2}
? "what":?? {0, 1}
 檢索的條件"what","is"和"it"將對應集合的交集。

正向索引開發(fā)出來用來存儲每個文檔的單詞的列表。正向索引的查詢往往滿足每個文檔有序頻繁的全文查詢和每個單詞在校驗文檔中的驗證這樣的查詢。在正向索引中,文檔占據(jù)了中心的位置,每個文檔指向了一個它所包含的索引項的序列。也就是說文檔指向了它包含的那些單詞,而反向索引則是單詞指向了包含它的文檔,很容易看到這個反向的關系。
本章要介紹這樣一個問題,對倒排索引中的關鍵詞進行編碼。那么,這個問題將分為兩個個步驟:

  • 首先,要提取倒排索引內(nèi)詞典文件中的關鍵詞;
  • 對提取出來的關鍵詞進行編碼。本章采取hash編碼的方式。既然要用hash編碼,那么最重要的就是要解決hash沖突的問題,下文會詳細介紹。
  • ??? 有一點必須提醒讀者的是,倒排索引包含詞典和倒排記錄表兩個部分,詞典一般有詞項(或稱為關鍵詞)和詞項頻率(即這個詞項或關鍵詞出現(xiàn)的次數(shù)),倒排記錄表則記錄著上述詞項(或關鍵詞)所出現(xiàn)的位置,或出現(xiàn)的文檔及網(wǎng)頁ID等相關信息。

    24.1、正排索引與倒排索引?

    ??? 咱們先來看什么是倒排索引,以及倒排索引與正排索引之間的區(qū)別:

    ??? 我們知道,搜索引擎的關鍵步驟就是建立倒排索引,所謂倒排索引一般表示為一個關鍵詞,然后是它的頻度(出現(xiàn)的次數(shù)),位置(出現(xiàn)在哪一篇文章或網(wǎng)頁中,及有關的日期,作者等信息),它相當于為互聯(lián)網(wǎng)上幾千億頁網(wǎng)頁做了一個索引,好比一本書的目錄、標簽一般。讀者想看哪一個主題相關的章節(jié),直接根據(jù)目錄即可找到相關的頁面。不必再從書的第一頁到最后一頁,一頁一頁的查找。

    ??? 接下來,闡述下正排索引與倒排索引的區(qū)別:

    一般索引(正排索引)????

    ??? 正排表是以文檔的ID為關鍵字,表中記錄文檔中每個字的位置信息,查找時掃描表中每個文檔中字的信息直到找出所有包含查詢關鍵字的文檔。正排表結構如圖1所示,這種組織方法在建立索引的時候結構比較簡單,建立比較方便且易于維護;因為索引是基于文檔建立的,若是有新的文檔假如,直接為該文檔建立一個新的索引塊,掛接在原來索引文件的后面。若是有文檔刪除,則直接找到該文檔號文檔對因的索引信息,將其直接刪除。但是在查詢的時候需對所有的文檔進行掃描以確保沒有遺漏,這樣就使得檢索時間大大延長,檢索效率低下。 ????

    ??? 盡管正排表的工作原理非常的簡單,但是由于其檢索效率太低,除非在特定情況下,否則實用性價值不大。


    倒排索引

    ??? 倒排表以字或詞為關鍵字進行索引,表中關鍵字所對應的記錄表項記錄了出現(xiàn)這個字或詞的所有文檔,一個表項就是一個字表段,它記錄該文檔的ID和字符在該文檔中出現(xiàn)的位置情況。由于每個字或詞對應的文檔數(shù)量在動態(tài)變化,所以倒排表的建立和維護都較為復雜,但是在查詢的時候由于可以一次得到查詢關鍵字所對應的所有文檔,所以效率高于正排表。在全文檢索中,檢索的快速響應是一個最為關鍵的性能,而索引建立由于在后臺進行,盡管效率相對低一些,但不會影響整個搜索引擎的效率。

    ??? 倒排表的結構圖如圖2


    ??? 倒排表的索引信息保存的是字或詞后繼數(shù)組模型、互關聯(lián)后繼數(shù)組模型條在文檔內(nèi)的位置,在同一篇文檔內(nèi)相鄰的字或詞條的前后關系沒有被保存到索引文件內(nèi)。

    24.2、倒排索引中提取關鍵詞

    ??? 倒排索引是搜索引擎之基石。建成了倒排索引后,用戶要查找某個query,如在搜索框輸入某個關鍵詞:“結構之法”后,搜索引擎不會再次使用爬蟲又一個一個去抓取每一個網(wǎng)頁,從上到下掃描網(wǎng)頁,看這個網(wǎng)頁有沒有出現(xiàn)這個關鍵詞,而是會在它預先生成的倒排索引文件中查找和匹配包含這個關鍵詞“結構之法”的所有網(wǎng)頁。找到了之后,再按相關性度排序,最終把排序后的結果顯示給用戶。

    ??? 如下,即是一個倒排索引文件(不全),我們把它取名為big_index,文件中每一較短的,不包含有“#####”符號的便是某個關鍵詞,及這個關鍵詞的出現(xiàn)次數(shù)。現(xiàn)在要從這個大索引文件中提取出這些關鍵詞,--Firelf--,-11,-Winter-,.,007,007:天降殺機,02Chan..如何做到呢?一行一行的掃描整個索引文件么?

    ??? 何意?之前已經(jīng)說過:倒排索引包含詞典和倒排記錄表兩個部分,詞典一般有詞項(或稱為關鍵詞)和詞項頻率(即這個詞項或關鍵詞出現(xiàn)的次數(shù)),倒排記錄表則記錄著上述詞項(或關鍵詞)所出現(xiàn)的位置,或出現(xiàn)的文檔及網(wǎng)頁ID等相關信息。

    ??? 最簡單的講,就是要提取詞典中的詞項(關鍵詞):--Firelf--,-11,-Winter-,.,007,007:天降殺機,02Chan...

    --Firelf--(關鍵詞) 8(出現(xiàn)次數(shù))


    ??? 我們可以試著這么解決:通過查找#####便可判斷某一行出現(xiàn)的詞是不是關鍵詞,但如果這樣做的話,便要掃描整個索引文件的每一行,代價實在巨大。如何提高速度呢?對了,關鍵詞后面的那個出現(xiàn)次數(shù)為我們問題的解決起到了很好的作用,如下注釋所示:

    // 本身沒有##### 的行判定為關鍵詞行,后跟這個關鍵詞的行數(shù)N(即詞項頻率)
    // 接下來,截取關鍵詞--Firelf--,然后讀取后面關鍵詞的行數(shù)N
    // 再跳過N行(濾過和避免掃描中間的倒排記錄表信息)
    // 讀取下一個關鍵詞..

    ??? 有朋友指出,上述方法雖然減少了掃描的行數(shù),但并沒有減少I0開銷。讀者是否有更好地辦法?歡迎隨時交流

    24.2、為提取出來的關鍵詞編碼

    ??? 愛思考的朋友可能會問,上述從倒排索引文件中提取出那些關鍵詞(詞項)的操作是為了什么呢?其實如我個人微博上12月12日所述的Hash詞典編碼:

    ??? 詞典文件的編碼:1、詞典怎么生成(存儲和構造詞典);2、如何運用hash對輸入的漢字進行編碼;3、如何更好的解決沖突,即不重復以及追加功能。具體例子為:事先構造好詞典文件后,輸入一個詞,要求找到這個詞的編碼,然后將其編碼輸出。且要有不斷能添加詞的功能,不得重復。
    ??? 步驟應該是如下:1、讀索引文件;2、提取索引中的詞出來;3、詞典怎么生成,存儲和構造詞典;4、詞典文件的編碼:不重復與追加功能。編碼比如,輸入中國,他的編碼可以為10001,然后輸入銀行,他的編碼可以為10002。只要實現(xiàn)不斷添加詞功能,以及不重復即可,詞典類的大文件,hash最重要的是怎樣避免沖突。

    ??? 也就是說,現(xiàn)在我要對上述提取出來后的關鍵詞進行編碼,采取何種方式編碼呢?暫時用hash函數(shù)編碼。編碼之后的效果將是每一個關鍵詞都有一個特定的編碼,如下圖所示(與上文big_index文件比較一下便知):

    ??? --Firelf--? 對應編碼為:135942

    ??? -11???????? 對應編碼為:106101

    ??? ....

    ??? 但細心的朋友一看上圖便知,其中第34~39行顯示,有重復的編碼,那么如何解決這個不重復編碼的問題呢?

    用hash表編碼?但其極易產(chǎn)生沖突碰撞,為什么?請看:

    哈希表是一種查找效率極高的數(shù)據(jù)結構,很多語言都在內(nèi)部實現(xiàn)了哈希表。PHP中的哈希表是一種極為重要的數(shù)據(jù)結構,不但用于表示Array數(shù)據(jù)類型,還在Zend虛擬機內(nèi)部用于存儲上下文環(huán)境信息(執(zhí)行上下文的變量及函數(shù)均使用哈希表結構存儲)。

    理想情況下哈希表插入和查找操作的時間復雜度均為O(1),任何一個數(shù)據(jù)項可以在一個與哈希表長度無關的時間內(nèi)計算出一個哈希值(key),然后在常量時間內(nèi)定位到一個桶(術語bucket,表示哈希表中的一個位置)。當然這是理想情況下,因為任何哈希表的長度都是有限的,所以一定存在不同的數(shù)據(jù)項具有相同哈希值的情況,此時不同數(shù)據(jù)項被定為到同一個桶,稱為碰撞(collision)。

    ??? 哈希表的實現(xiàn)需要解決碰撞問題,碰撞解決大體有兩種思路,

  • 第一種是根據(jù)某種原則將被碰撞數(shù)據(jù)定為到其它桶,例如線性探測——如果數(shù)據(jù)在插入時發(fā)生了碰撞,則順序查找這個桶后面的桶,將其放入第一個沒有被使用的桶;
  • 第二種策略是每個桶不是一個只能容納單個數(shù)據(jù)項的位置,而是一個可容納多個數(shù)據(jù)的數(shù)據(jù)結構(例如鏈表或紅黑樹),所有碰撞的數(shù)據(jù)以某種數(shù)據(jù)結構的形式組織起來。
  • 不論使用了哪種碰撞解決策略,都導致插入和查找操作的時間復雜度不再是O(1)。以查找為例,不能通過key定位到桶就結束,必須還要比較原始key(即未做哈希之前的key)是否相等,如果不相等,則要使用與插入相同的算法繼續(xù)查找,直到找到匹配的值或確認數(shù)據(jù)不在哈希表中。

    PHP是使用單鏈表存儲碰撞的數(shù)據(jù),因此實際上PHP哈希表的平均查找復雜度為O(L),其中L為桶鏈表的平均長度;而最壞復雜度為O(N),此時所有數(shù)據(jù)全部碰撞,哈希表退化成單鏈表。下圖PHP中正常哈希表和退化哈希表的示意圖。

    哈希表碰撞攻擊就是通過精心構造數(shù)據(jù),使得所有數(shù)據(jù)全部碰撞,人為將哈希表變成一個退化的單鏈表,此時哈希表各種操作的時間均提升了一個數(shù)量級,因此會消耗大量CPU資源,導致系統(tǒng)無法快速響應請求,從而達到拒絕服務攻擊(DoS)的目的。

    可以看到,進行哈希碰撞攻擊的前提是哈希算法特別容易找出碰撞,如果是MD5或者SHA1那基本就沒戲了,幸運的是(也可以說不幸的是)大多數(shù)編程語言使用的哈希算法都十分簡單(這是為了效率考慮),因此可以不費吹灰之力之力構造出攻擊數(shù)據(jù).(上述五段文字引自:http://www.codinglabs.org/html/hash-collisions-attack-on-php.html)。

    24.4、暴雪的Hash算法

    ??? 值得一提的是,在解決Hash沖突的時候,搞的焦頭爛額,結果今天上午在自己的博客內(nèi)的一篇文章(十一、從頭到尾徹底解析Hash表算法)內(nèi)找到了解決辦法:網(wǎng)上流傳甚廣的暴雪的Hash算法。 OK,接下來,咱們回顧下暴雪的hash表算法:

    接下來,咱們來具體分析一下一個最快的Hash表算法。
    我們由一個簡單的問題逐步入手:有一個龐大的字符串數(shù)組,然后給你一個單獨的字符串,讓你從這個數(shù)組中查找是否有這個字符串并找到它,你會怎么做?
    有一個方法最簡單,老老實實從頭查到尾,一個一個比較,直到找到為止,我想只要學過程序設計的人都能把這樣一個程序作出來,但要是有程序員把這樣的程序交給用戶,我只能用無語來評價,或許它真的能工作,但...也只能如此了。
    最合適的算法自然是使用HashTable(哈希表),先介紹介紹其中的基本知識,所謂Hash,一般是一個整數(shù),通過某種算法,可以把一個字符串"壓縮" 成一個整數(shù)。當然,無論如何,一個32位整數(shù)是無法對應回一個字符串的,但在程序中,兩個字符串計算出的Hash值相等的可能非常小,下面看看在MPQ中的Hash算法:
    ??? 函數(shù)prepareCryptTable以下的函數(shù)生成一個長度為0x500(合10進制數(shù):1280)的cryptTable[0x500]

    01.//函數(shù)prepareCryptTable以下的函數(shù)生成一個長度為0x500(合10進制數(shù):1280)的cryptTable[0x500] 02.void prepareCryptTable() 03.{ 04. unsigned long seed = 0x00100001, index1 = 0, index2 = 0, i; 05. 06. for( index1 = 0; index1 < 0x100; index1++ ) 07. { 08. for( index2 = index1, i = 0; i < 5; i++, index2 += 0x100 ) 09. { 10. unsigned long temp1, temp2; 11. 12. seed = (seed * 125 + 3) % 0x2AAAAB; 13. temp1 = (seed & 0xFFFF) << 0x10; 14. 15. seed = (seed * 125 + 3) % 0x2AAAAB; 16. temp2 = (seed & 0xFFFF); 17. 18. cryptTable[index2] = ( temp1 | temp2 ); 19. } 20. } 21.}


    函數(shù)HashString以下函數(shù)計算lpszFileName 字符串的hash值,其中dwHashType 為hash的類型,

    01.//函數(shù)HashString以下函數(shù)計算lpszFileName 字符串的hash值,其中dwHashType 為hash的類型, 02.unsigned long HashString(const char *lpszkeyName, unsigned long dwHashType ) 03.{ 04. unsigned char *key = (unsigned char *)lpszkeyName; 05. unsigned long seed1 = 0x7FED7FED; 06. unsigned long seed2 = 0xEEEEEEEE; 07. int ch; 08. 09. while( *key != 0 ) 10. { 11. ch = *key++; 12. seed1 = cryptTable[(dwHashType<<8) + ch] ^ (seed1 + seed2); 13. seed2 = ch + seed1 + seed2 + (seed2<<5) + 3; 14. } 15. return seed1; 16.}


    Blizzard的這個算法是非常高效的,被稱為"One-Way Hash"( A one-way hash is a an algorithm that is constructed in such a way that deriving the original string (set of strings, actually) is virtually impossible)。舉個例子,字符串"unitneutralacritter.grp"通過這個算法得到的結果是0xA26067F3。
     是不是把第一個算法改進一下,改成逐個比較字符串的Hash值就可以了呢,答案是,遠遠不夠,要想得到最快的算法,就不能進行逐個的比較,通常是構造一個哈希表(Hash Table)來解決問題,哈希表是一個大數(shù)組,這個數(shù)組的容量根據(jù)程序的要求來定義,
    ???? 例如1024,每一個Hash值通過取模運算 (mod) 對應到數(shù)組中的一個位置,這樣,只要比較這個字符串的哈希值對應的位置有沒有被占用,就可以得到最后的結果了,想想這是什么速度?是的,是最快的O(1),現(xiàn)在仔細看看這個算法吧:

    01.typedef struct 02.{ 03. int nHashA; 04. int nHashB; 05. char bExists; 06. ...... 07.} SOMESTRUCTRUE; 08.//一種可能的結構體定義?


    函數(shù)GetHashTablePos下述函數(shù)為在Hash表中查找是否存在目標字符串,有則返回要查找字符串的Hash值,無則,return -1.

    01.//函數(shù)GetHashTablePos下述函數(shù)為在Hash表中查找是否存在目標字符串,有則返回要查找字符串的Hash值,無則,return -1. 02.int GetHashTablePos( har *lpszString, SOMESTRUCTURE *lpTable ) 03.//lpszString要在Hash表中查找的字符串,lpTable為存儲字符串Hash值的Hash表。 04.{ 05. int nHash = HashString(lpszString); //調(diào)用上述函數(shù)HashString,返回要查找字符串lpszString的Hash值。 06. int nHashPos = nHash % nTableSize; 07. 08. if ( lpTable[nHashPos].bExists && !strcmp( lpTable[nHashPos].pString, lpszString ) ) 09. { //如果找到的Hash值在表中存在,且要查找的字符串與表中對應位置的字符串相同, 10. return nHashPos; //返回找到的Hash值 11. } 12. else 13. { 14. return -1; 15. } 16.}


    看到此,我想大家都在想一個很嚴重的問題:“如果兩個字符串在哈希表中對應的位置相同怎么辦?”,畢竟一個數(shù)組容量是有限的,這種可能性很大。解決該問題的方法很多,我首先想到的就是用“鏈表”,感謝大學里學的數(shù)據(jù)結構教會了這個百試百靈的法寶,我遇到的很多算法都可以轉(zhuǎn)化成鏈表來解決,只要在哈希表的每個入口掛一個鏈表,保存所有對應的字符串就OK了。事情到此似乎有了完美的結局,如果是把問題獨自交給我解決,此時我可能就要開始定義數(shù)據(jù)結構然后寫代碼了。
    ??? 然而Blizzard的程序員使用的方法則是更精妙的方法。基本原理就是:他們在哈希表中不是用一個哈希值而是用三個哈希值來校驗字符串。

    ??? MPQ使用文件名哈希表來跟蹤內(nèi)部的所有文件。但是這個表的格式與正常的哈希表有一些不同。首先,它沒有使用哈希作為下標,把實際的文件名存儲在表中用于驗證,實際上它根本就沒有存儲文件名。而是使用了3種不同的哈希:一個用于哈希表的下標,兩個用于驗證。這兩個驗證哈希替代了實際文件名。
    ??? 當然了,這樣仍然會出現(xiàn)2個不同的文件名哈希到3個同樣的哈希。但是這種情況發(fā)生的概率平均是:1:18889465931478580854784,這個概率對于任何人來說應該都是足夠小的。現(xiàn)在再回到數(shù)據(jù)結構上,Blizzard使用的哈希表沒有使用鏈表,而采用"順延"的方式來解決問題。下面,咱們來看看這個網(wǎng)上流傳甚廣的暴雪hash算法:
    ??? 函數(shù)GetHashTablePos中,lpszString 為要在hash表中查找的字符串;lpTable 為存儲字符串hash值的hash表;nTableSize 為hash表的長度:

    表;nTableSize 為hash表的長度: 01.//函數(shù)GetHashTablePos中,lpszString 為要在hash表中查找的字符串;lpTable 為存儲字符串hash值的hash表;nTableSize 為hash表的長度: 02.int GetHashTablePos( char *lpszString, MPQHASHTABLE *lpTable, int nTableSize ) 03.{ 04. const int HASH_OFFSET = 0, HASH_A = 1, HASH_B = 2; 05. 06. int nHash = HashString( lpszString, HASH_OFFSET ); 07. int nHashA = HashString( lpszString, HASH_A ); 08. int nHashB = HashString( lpszString, HASH_B ); 09. int nHashStart = nHash % nTableSize; 10. int nHashPos = nHashStart; 11. 12. while ( lpTable[nHashPos].bExists ) 13. { 14.// 如果僅僅是判斷在該表中時候存在這個字符串,就比較這兩個hash值就可以了,不用對結構體中的字符串進行比較。 15.// 這樣會加快運行的速度?減少hash表占用的空間?這種方法一般應用在什么場合? 16. if (   lpTable[nHashPos].nHashA == nHashA 17. && lpTable[nHashPos].nHashB == nHashB ) 18. { 19. return nHashPos; 20. } 21. else 22. { 23. nHashPos = (nHashPos + 1) % nTableSize; 24. } 25. 26. if (nHashPos == nHashStart) 27. break; 28. } 29. return -1; 30.}


    上述程序解釋:

  • 計算出字符串的三個哈希值(一個用來確定位置,另外兩個用來校驗)
  • 察看哈希表中的這個位置
  • 哈希表中這個位置為空嗎?如果為空,則肯定該字符串不存在,返回-1。
  • 如果存在,則檢查其他兩個哈希值是否也匹配,如果匹配,則表示找到了該字符串,返回其Hash值。
  • 移到下一個位置,如果已經(jīng)移到了表的末尾,則反繞到表的開始位置起繼續(xù)查詢 
  • 看看是不是又回到了原來的位置,如果是,則返回沒找到
  • 回到3。
  • 24.4、不重復Hash編碼

    ??? 有了上面的暴雪Hash算法。咱們的問題便可解決了。不過,有兩點必須先提醒讀者:1、Hash表起初要初始化;2、暴雪的Hash算法對于查詢那樣處理可以,但對插入就不能那么解決。

    ??? 關鍵主體代碼如下:

    01.//函數(shù)prepareCryptTable以下的函數(shù)生成一個長度為0x500(合10進制數(shù):1280)的cryptTable[0x500] 02.void prepareCryptTable() 03.{ 04. unsigned long seed = 0x00100001, index1 = 0, index2 = 0, i; 05. 06. for( index1 = 0; index1 <0x100; index1++ ) 07. { 08. for( index2 = index1, i = 0; i < 5; i++, index2 += 0x100) 09. { 10. unsigned long temp1, temp2; 11. seed = (seed * 125 + 3) % 0x2AAAAB; 12. temp1 = (seed & 0xFFFF)<<0x10; 13. seed = (seed * 125 + 3) % 0x2AAAAB; 14. temp2 = (seed & 0xFFFF); 15. cryptTable[index2] = ( temp1 | temp2 ); 16. } 17. } 18.} 19. 20.//函數(shù)HashString以下函數(shù)計算lpszFileName 字符串的hash值,其中dwHashType 為hash的類型, 21.unsigned long HashString(const char *lpszkeyName, unsigned long dwHashType ) 22.{ 23. unsigned char *key = (unsigned char *)lpszkeyName; 24. unsigned long seed1 = 0x7FED7FED; 25. unsigned long seed2 = 0xEEEEEEEE; 26. int ch; 27. 28. while( *key != 0 ) 29. { 30. ch = *key++; 31. seed1 = cryptTable[(dwHashType<<8) + ch] ^ (seed1 + seed2); 32. seed2 = ch + seed1 + seed2 + (seed2<<5) + 3; 33. } 34. return seed1; 35.} 36. 37./ 38.//function: 哈希詞典 編碼 39.//parameter: 40.//author: lei.zhou 41.//time: 2011-12-14 42./ 43.MPQHASHTABLE TestHashTable[nTableSize]; 44.int TestHashCTable[nTableSize]; 45.int TestHashDTable[nTableSize]; 46.key_list test_data[nTableSize]; 47. 48.//直接調(diào)用上面的hashstring,nHashPos就是對應的HASH值。 49.int insert_string(const char *string_in) 50.{ 51. const int HASH_OFFSET = 0, HASH_C = 1, HASH_D = 2; 52. unsigned int nHash = HashString(string_in, HASH_OFFSET); 53. unsigned int nHashC = HashString(string_in, HASH_C); 54. unsigned int nHashD = HashString(string_in, HASH_D); 55. unsigned int nHashStart = nHash % nTableSize; 56. unsigned int nHashPos = nHashStart; 57. int ln, ires = 0; 58. 59. while (TestHashTable[nHashPos].bExists) 60. { 61.// if (TestHashCTable[nHashPos] == (int) nHashC && TestHashDTable[nHashPos] == (int) nHashD) 62.// break; 63.// //... 64.// else 65. //如之前所提示讀者的那般,暴雪的Hash算法對于查詢那樣處理可以,但對插入就不能那么解決 66. nHashPos = (nHashPos + 1) % nTableSize; 67. 68. if (nHashPos == nHashStart) 69. break; 70. } 71. 72. ln = strlen(string_in); 73. if (!TestHashTable[nHashPos].bExists && (ln < nMaxStrLen)) 74. { 75. TestHashCTable[nHashPos] = nHashC; 76. TestHashDTable[nHashPos] = nHashD; 77. 78. test_data[nHashPos] = (KEYNODE *) malloc (sizeof(KEYNODE) * 1); 79. if(test_data[nHashPos] == NULL) 80. { 81. printf("10000 EMS ERROR !!!!\n"); 82. return 0; 83. } 84. 85. test_data[nHashPos]->pkey = (char *)malloc(ln+1); 86. if(test_data[nHashPos]->pkey == NULL) 87. { 88. printf("10000 EMS ERROR !!!!\n"); 89. return 0; 90. } 91. 92. memset(test_data[nHashPos]->pkey, 0, ln+1); 93. strncpy(test_data[nHashPos]->pkey, string_in, ln); 94. *((test_data[nHashPos]->pkey)+ln) = 0; 95. test_data[nHashPos]->weight = nHashPos; 96. 97. TestHashTable[nHashPos].bExists = 1; 98. } 99. else 100. { 101. if(TestHashTable[nHashPos].bExists) 102. printf("30000 in the hash table %s !!!\n", string_in); 103. else 104. printf("90000 strkey error !!!\n"); 105. } 106. return nHashPos; 107.}


    ??? 接下來要讀取索引文件big_index對其中的關鍵詞進行編碼(為了簡單起見,直接一行一行掃描讀寫,沒有跳過行數(shù)了):

    01.void bigIndex_hash(const char *docpath, const char *hashpath) 02.{ 03. FILE *fr, *fw; 04. int len; 05. char *pbuf, *p; 06. char dockey[TERM_MAX_LENG]; 07. 08. if(docpath == NULL || *docpath == '\0') 09. return; 10. 11. if(hashpath == NULL || *hashpath == '\0') 12. return; 13. 14. fr = fopen(docpath, "rb"); //讀取文件docpath 15. fw = fopen(hashpath, "wb"); 16. if(fr == NULL || fw == NULL) 17. { 18. printf("open read or write file error!\n"); 19. return; 20. } 21. 22. pbuf = (char*)malloc(BUFF_MAX_LENG); 23. if(pbuf == NULL) 24. { 25. fclose(fr); 26. return ; 27. } 28. 29. memset(pbuf, 0, BUFF_MAX_LENG); 30. 31. while(fgets(pbuf, BUFF_MAX_LENG, fr)) 32. { 33. len = GetRealString(pbuf); 34. if(len <= 1) 35. continue; 36. p = strstr(pbuf, "#####"); 37. if(p != NULL) 38. continue; 39. 40. p = strstr(pbuf, " "); 41. if (p == NULL) 42. { 43. printf("file contents error!"); 44. } 45. 46. len = p - pbuf; 47. dockey[0] = 0; 48. strncpy(dockey, pbuf, len); 49. 50. dockey[len] = 0; 51. 52. int num = insert_string(dockey); 53. 54. dockey[len] = ' '; 55. dockey[len+1] = '\0'; 56. char str[20]; 57. itoa(num, str, 10); 58. 59. strcat(dockey, str); 60. dockey[len+strlen(str)+1] = '\0'; 61. fprintf (fw, "%s\n", dockey); 62. 63. } 64. free(pbuf); 65. fclose(fr); 66. fclose(fw); 67.}


    主函數(shù)已經(jīng)很簡單了,如下:

    01.int main() 02.{ 03. prepareCryptTable(); //Hash表起初要初始化 04. 05. //現(xiàn)在要把整個big_index文件插入hash表,以取得編碼結果 06. bigIndex_hash("big_index.txt", "hashpath.txt"); 07. system("pause"); 08. 09. return 0; 10.}

    ??? 程序運行后生成的hashpath.txt文件如下:


    ??? 如上所示,采取暴雪的Hash算法并在插入的時候做適當處理,當再次對上文中的索引文件big_index進行Hash編碼后,沖突問題已經(jīng)得到初步解決。當然,還有待更進一步更深入的測試。

    后續(xù)添上數(shù)目索引1~10000...

    ??? 后來又為上述文件中的關鍵詞編了碼一個計數(shù)的內(nèi)碼,不過,奇怪的是,同樣的代碼,在Dev C++ 與VS2010上運行結果卻不同(左邊dev上計數(shù)從"1"開始,VS上計數(shù)從“1994014002”開始),如下圖所示:


    ??? 在上面的bigIndex_hashcode函數(shù)的基礎上,修改如下,即可得到上面的效果:

    ?

    01.void bigIndex_hashcode(const char *in_file_path, const char *out_file_path) 02.{ 03. FILE *fr, *fw; 04. int len, value; 05. char *pbuf, *pleft, *p; 06. char keyvalue[TERM_MAX_LENG], str[WORD_MAX_LENG]; 07. 08. if(in_file_path == NULL || *in_file_path == '\0') { 09. printf("input file path error!\n"); 10. return; 11. } 12. 13. if(out_file_path == NULL || *out_file_path == '\0') { 14. printf("output file path error!\n"); 15. return; 16. } 17. 18. fr = fopen(in_file_path, "r"); //讀取in_file_path路徑文件 19. fw = fopen(out_file_path, "w"); 20. 21. if(fr == NULL || fw == NULL) 22. { 23. printf("open read or write file error!\n"); 24. return; 25. } 26. 27. pbuf = (char*)malloc(BUFF_MAX_LENG); 28. pleft = (char*)malloc(BUFF_MAX_LENG); 29. if(pbuf == NULL || pleft == NULL) 30. { 31. printf("allocate memory error!"); 32. fclose(fr); 33. return ; 34. } 35. 36. memset(pbuf, 0, BUFF_MAX_LENG); 37. 38. int offset = 1; 39. while(fgets(pbuf, BUFF_MAX_LENG, fr)) 40. { 41. if (--offset > 0) 42. continue; 43. 44. if(GetRealString(pbuf) <= 1) 45. continue; 46. 47. p = strstr(pbuf, "#####"); 48. if(p != NULL) 49. continue; 50. 51. p = strstr(pbuf, " "); 52. if (p == NULL) 53. { 54. printf("file contents error!"); 55. } 56. 57. len = p - pbuf; 58. 59. // 確定跳過行數(shù) 60. strcpy(pleft, p+1); 61. offset = atoi(pleft) + 1; 62. 63. strncpy(keyvalue, pbuf, len); 64. keyvalue[len] = '\0'; 65. value = insert_string(keyvalue); 66. 67. if (value != -1) { 68. 69. // key value中插入空格 70. keyvalue[len] = ' '; 71. keyvalue[len+1] = '\0'; 72. 73. itoa(value, str, 10); 74. strcat(keyvalue, str); 75. 76. keyvalue[len+strlen(str)+1] = ' '; 77. keyvalue[len+strlen(str)+2] = '\0'; 78. 79. keysize++; 80. itoa(keysize, str, 10); 81. strcat(keyvalue, str); 82. 83. // 將key value寫入文件 84. fprintf (fw, "%s\n", keyvalue); 85. 86. } 87. } 88. free(pbuf); 89. fclose(fr); 90. fclose(fw); 91.}


    第二部分:于給定的文檔生成倒排索引

    第一節(jié)、索引的構建方法

    ??? 根據(jù)信息檢索導論(Christtopher D.Manning等著,王斌譯)一書給的提示,我們可以選擇兩種構建索引的算法:BSBI算法,與SPIMI算法。

    BSBI算法,基于磁盤的外部排序算法,此算法首先將詞項映射成其ID的數(shù)據(jù)結構,如Hash映射。而后將文檔解析成詞項ID-文檔ID對,并在內(nèi)存中一直處理,直到累積至放滿一個固定大小的塊空間為止,我們選擇合適的塊大小,使之能方便加載到內(nèi)存中并允許在內(nèi)存中快速排序,快速排序后的塊轉(zhuǎn)換成倒排索引格式后寫入磁盤。

    ??? 建立倒排索引的步驟如下:

  • 將文檔分割成幾個大小相等的部分;
  • 對詞項ID-文檔ID進行排序;
  • 將具有同一詞項ID的所有文檔ID放到倒排記錄表中,其中每條倒排記錄僅僅是一個文檔ID;
  • 將基于塊的倒排索引寫到磁盤上。
  • 此算法假如說最后可能會產(chǎn)生10個塊。其偽碼如下:

    [cpp] view plaincopyprint?
  • BSBI NDEXConSTRUCTION()?
  • n <- 0?
  • while(all documents have not been processed)?
  • ??? do n<-n+1?
  • ??????? block <- PARSENEXTBLOCK()??? //文檔分析?
  • ??????? BSBI-INVERT(block)?
  • ??????? WRITEBLOCKTODISK(block,fn)?
  • ??????? MERGEBLOCKS(f1,...,fn;fmerged)?
  • BSBI NDEXConSTRUCTION() n <- 0 while(all documents have not been processed)do n<-n+1block <- PARSENEXTBLOCK() //文檔分析BSBI-INVERT(block)WRITEBLOCKTODISK(block,fn)MERGEBLOCKS(f1,...,fn;fmerged)

    (基于塊的排序索引算法,該算法將每個塊的倒排索引文件存入文件f1,...,fn中,最后合并成fmerged
    如果該算法應用最后一步產(chǎn)生了10個塊,那么接下來便會將10個塊索引同時合并成一個索引文件。)

    ??? 合并時,同時打開所有塊對應的文件,內(nèi)存中維護了為10個塊準備的讀緩沖區(qū)和一個為最終合并索引準備的寫緩沖區(qū)。每次迭代中,利用優(yōu)先級隊列(如堆結構或類似的數(shù)據(jù)結構)選擇最小的未處理的詞項ID進行處理。如下圖所示(圖片引自深入搜索引擎--海里信息的壓縮、索引和查詢,梁斌譯),分塊索引,分塊排序,最終全部合并(說實話,跟MapReduce還是有些類似的):

    ??? 讀入該詞項的倒排記錄表并合并,合并結果寫回磁盤中。需要時,再次從文件中讀入數(shù)據(jù)到每個讀緩沖區(qū)(基于磁盤的外部排序算法的更多可以參考:程序員編程藝術第十章、如何給10^7個數(shù)據(jù)量的磁盤文件排序)。

    ??? BSBI算法主要的時間消耗在排序上,選擇什么排序方法呢,簡單的快速排序足矣,其時間復雜度為O(N*logN),其中N是所需要排序的項(詞項ID-文檔ID對)的數(shù)目的上界。

    SPIMI算法,內(nèi)存式單遍掃描索引算法
    ??? 與上述BSBI算法不同的是:SPIMI使用詞項而不是其ID,它將每個塊的詞典寫入磁盤,對于寫一塊則重新采用新的詞典,只要硬盤空間足夠大,它能索引任何大小的文檔集。
    ??? 倒排索引 = 詞典(關鍵詞或詞項+詞項頻率)+倒排記錄表。建倒排索引的步驟如下:

  • 從頭開始掃描每一個詞項-文檔ID(信息)對,遇一詞,構建索引;
  • 繼續(xù)掃描,若遇一新詞,則再建一新索引塊(加入詞典,通過Hash表實現(xiàn),同時,建一新的倒排記錄表);若遇一舊詞,則找到其倒排記錄表的位置,添加其后
  • 在內(nèi)存內(nèi)基于分塊完成排序,后合并分塊;
  • 寫入磁盤。
  • 其偽碼如下:

    [cpp] view plaincopyprint?
  • SPIMI-Invert(Token_stream)?
  • output.file=NEWFILE()?
  • dictionary = NEWHASH()?
  • while (free memory available)?
  • ??? do token <-next(token_stream)??? //逐一處理每個詞項-文檔ID對?
  • ??????? if term(token) !(- dictionary?
  • ??????????? then postings_list = AddToDictionary(dictionary,term(token))??? //如果詞項是第一次出現(xiàn),那么加入hash詞典,同時,建立一個新的倒排索引表?
  • ??????? else postings_list = GetPostingList(dictionary,term(token))???????? //如果不是第一次出現(xiàn),那么直接返回其倒排記錄表,在下面添加其后?
  • ??? if full(postings_list)?
  • ??????? then postings_list =DoublePostingList(dictionary,term(token))?
  • ??? AddToPosTingsList (postings_list,docID(token))????????? //SPIMI與BSBI的區(qū)別就在于此,前者直接在倒排記錄表中增加此項新紀錄?
  • sorted_terms <- SortTerms(dictionary)?
  • WriteBlockToDisk(sorted_terms,dictionary,output_file)?
  • return output_file?
  • SPIMI-Invert(Token_stream) output.file=NEWFILE() dictionary = NEWHASH() while (free memory available)do token <-next(token_stream) //逐一處理每個詞項-文檔ID對if term(token) !(- dictionarythen postings_list = AddToDictionary(dictionary,term(token)) //如果詞項是第一次出現(xiàn),那么加入hash詞典,同時,建立一個新的倒排索引表else postings_list = GetPostingList(dictionary,term(token)) //如果不是第一次出現(xiàn),那么直接返回其倒排記錄表,在下面添加其后if full(postings_list)then postings_list =DoublePostingList(dictionary,term(token))AddToPosTingsList (postings_list,docID(token)) //SPIMI與BSBI的區(qū)別就在于此,前者直接在倒排記錄表中增加此項新紀錄 sorted_terms <- SortTerms(dictionary) WriteBlockToDisk(sorted_terms,dictionary,output_file) return output_file

    SPIMI與BSBI的主要區(qū)別
    ??? SPIMI當發(fā)現(xiàn)關鍵詞是第一次出現(xiàn)時,會直接在倒排記錄表中增加一項(與BSBI算法不同)。同時,與BSBI算法一開始就整理出所有的詞項ID-文檔ID,并對它們進行排序的做法不同(而這恰恰是BSBI的做法),這里的每個倒排記錄表都是動態(tài)增長的(也就是說,倒排記錄表的大小會不斷調(diào)整),同時,掃描一遍就可以實現(xiàn)全體倒排記錄表的收集。
    ??? SPIMI這樣做有兩點好處:

  • 由于不需要排序操作,因此處理的速度更快,
  • 由于保留了倒排記錄表對詞項的歸屬關系,因此能節(jié)省內(nèi)存,詞項的ID也不需要保存。這樣,每次單獨的SPIMI-Invert調(diào)用能夠處理的塊大小可以非常大,整個倒排索引的構建過程也可以非常高效。
  • ??? 但不得不提的是,由于事先并不知道每個詞項的倒排記錄表大小,算法一開始只能分配一個較小的倒排記錄表空間,每次當該空間放滿的時候,就會申請加倍的空間,
    ??? 與此同時,自然而然便會浪費一部分空間(當然,此前因為不保存詞項ID,倒也省下一點空間,總體而言,算作是抵銷了)。
    ??? 不過,至少SPIMI所用的空間會比BSBI所用空間少。當內(nèi)存耗盡后,包括詞典和倒排記錄表的塊索引將被寫到磁盤上,但在此之前,為使倒排記錄表按照詞典順序來加快最后的合并操作,所以要對詞項進行排序操作。

    小數(shù)據(jù)量與大數(shù)據(jù)量的區(qū)別

    ??? 在小數(shù)據(jù)量時,有足夠的內(nèi)存保證該創(chuàng)建過程可以一次完成;
    ??? 數(shù)據(jù)規(guī)模增大后,可以采用分組索引,然后再歸并索 引的策略。該策略是,

  • 建立索引的模塊根據(jù)當時運行系統(tǒng)所在的計算機的內(nèi)存大小,將索引分為 k 組,使得每組運算所需內(nèi)存都小于系統(tǒng)能夠提供的最大使用內(nèi)存的大小。
  • 按照倒排索引的生成算法,生成 k 組倒排索引。
  • 然后將這 k 組索引歸并,即將相同索引詞對應的數(shù)據(jù)合并到一起,就得到了以索引詞為主鍵的最終的倒排文件索引,即反向索引。
  • 為了測試的方便,本文針對小數(shù)據(jù)量進行從正排文檔到倒排索引文件的實現(xiàn)。而且針對大數(shù)量的K路歸并算法或基于磁盤的外部排序算法本編程藝術系列第十章中已有詳細闡述。

    第二節(jié)、Hash表的構建與實現(xiàn)

    ??? 如下,給定如下圖所示的正排文檔,每一行的信息分別為(中間用##########隔開):文檔ID、訂閱源(子頻道)、 頻道分類、 網(wǎng)站類ID(大頻道)、時間、 md5、文檔權值、關鍵詞、作者等等。

    ??? 要求基于給定的上述正排文檔。生成如第二十四章所示的倒排索引文件(注,關鍵詞所在的文章如果是同一個日期的話,是挨在同一行的,用“#”符號隔開):

    ??? 我們知道:為網(wǎng)頁建立全文索引是網(wǎng)頁預處理的核心部分,包括分析網(wǎng)頁和建立倒排文件。二者是順序進行,先分析網(wǎng)頁,后建立倒排文件(也稱為反向索引),如圖所示:

    ?? 正如上圖粗略所示,我們知道倒排索引創(chuàng)建的過程如下:

  • 寫爬蟲抓取相關的網(wǎng)頁,而后提取相關網(wǎng)頁或文章中所有的關鍵詞;
  • 分詞,找出所有單詞;
  • 過濾不相干的信息(如廣告等信息);
  • 構建倒排索引,關鍵詞=>(文章ID 出現(xiàn)次數(shù) 出現(xiàn)的位置)
  • 生成詞典文件 頻率文件 位置文件
  • 壓縮。
  • 建相關的數(shù)據(jù)結構 ?

    ??? 根據(jù)給定的正排文檔,我們可以建立如下的兩個結構體表示這些信息:文檔ID、訂閱源(子頻道)、 頻道分類、 網(wǎng)站類ID(大頻道)、時間、 md5、文檔權值、關鍵詞、作者等等。如下所示:

    01.typedef struct key_node 02.{ 03. char *pkey; // 關鍵詞實體 04. int count; // 關鍵詞出現(xiàn)次數(shù) 05. int pos; // 關鍵詞在hash表中位置 06. struct doc_node *next; // 指向文檔結點 07.}KEYNODE, *key_list; 08. 09.key_list key_array[TABLE_SIZE]; 10. 11.typedef struct doc_node 12.{ 13. char id[WORD_MAX_LEN]; //文檔ID 14. int classOne; //訂閱源(子頻道) 15. char classTwo[WORD_MAX_LEN]; //頻道分類 16. int classThree; //網(wǎng)站類ID(大頻道) 17. char time[WORD_MAX_LEN]; //時間 18. char md5[WORD_MAX_LEN]; //md5 19. int weight; //文檔權值 20. struct doc_node *next; 21.}DOCNODE, *doc_list;


    我們知道,通過第二十四章的暴雪的Hash表算法,可以比較好的避免相關沖突的問題。下面,我們再次引用其代碼:

    基于暴雪的Hash之上的改造算

    01.//函數(shù)prepareCryptTable以下的函數(shù)生成一個長度為0x100的cryptTable[0x100] 02.void PrepareCryptTable() 03.{ 04. unsigned long seed = 0x00100001, index1 = 0, index2 = 0, i; 05. 06. for( index1 = 0; index1 <0x100; index1++ ) 07. { 08. for( index2 = index1, i = 0; i < 5; i++, index2 += 0x100) 09. { 10. unsigned long temp1, temp2; 11. seed = (seed * 125 + 3) % 0x2AAAAB; 12. temp1 = (seed & 0xFFFF)<<0x10; 13. seed = (seed * 125 + 3) % 0x2AAAAB; 14. temp2 = (seed & 0xFFFF); 15. cryptTable[index2] = ( temp1 | temp2 ); 16. } 17. } 18.} 19. 20.//函數(shù)HashString以下函數(shù)計算lpszFileName 字符串的hash值,其中dwHashType 為hash的類型, 21.unsigned long HashString(const char *lpszkeyName, unsigned long dwHashType ) 22.{ 23. unsigned char *key = (unsigned char *)lpszkeyName; 24. unsigned long seed1 = 0x7FED7FED; 25. unsigned long seed2 = 0xEEEEEEEE; 26. int ch; 27. 28. while( *key != 0 ) 29. { 30. ch = *key++; 31. seed1 = cryptTable[(dwHashType<<8) + ch] ^ (seed1 + seed2); 32. seed2 = ch + seed1 + seed2 + (seed2<<5) + 3; 33. } 34. return seed1; 35.} 36. 37.//按關鍵字查詢,如果成功返回hash表中索引位置 38.key_list SearchByString(const char *string_in) 39.{ 40. const int HASH_OFFSET = 0, HASH_C = 1, HASH_D = 2; 41. unsigned int nHash = HashString(string_in, HASH_OFFSET); 42. unsigned int nHashC = HashString(string_in, HASH_C); 43. unsigned int nHashD = HashString(string_in, HASH_D); 44. unsigned int nHashStart = nHash % TABLE_SIZE; 45. unsigned int nHashPos = nHashStart; 46. 47. while (HashTable[nHashPos].bExists) 48. { 49. if (HashATable[nHashPos] == (int) nHashC && HashBTable[nHashPos] == (int) nHashD) 50. { 51. break; 52. //查詢與插入不同,此處不需修改 53. } 54. else 55. { 56. nHashPos = (nHashPos + 1) % TABLE_SIZE; 57. } 58. 59. if (nHashPos == nHashStart) 60. { 61. break; 62. } 63. } 64. 65. if( key_array[nHashPos] && strlen(key_array[nHashPos]->pkey)) 66. { 67. return key_array[nHashPos]; 68. } 69. 70. return NULL; 71.} 72. 73.//按索引查詢,如果成功返回關鍵字(此函數(shù)在本章中沒有被用到,可以忽略) 74.key_list SearchByIndex(unsigned int nIndex) 75.{ 76. unsigned int nHashPos = nIndex; 77. if (nIndex < TABLE_SIZE) 78. { 79. if(key_array[nHashPos] && strlen(key_array[nHashPos]->pkey)) 80. { 81. return key_array[nHashPos]; 82. } 83. } 84. 85. return NULL; 86.} 87. 88.//插入關鍵字,如果成功返回hash值 89.int InsertString(const char *str) 90.{ 91. const int HASH_OFFSET = 0, HASH_A = 1, HASH_B = 2; 92. unsigned int nHash = HashString(str, HASH_OFFSET); 93. unsigned int nHashA = HashString(str, HASH_A); 94. unsigned int nHashB = HashString(str, HASH_B); 95. unsigned int nHashStart = nHash % TABLE_SIZE; 96. unsigned int nHashPos = nHashStart; 97. int len; 98. 99. while (HashTable[nHashPos].bExists) 100. { 101. nHashPos = (nHashPos + 1) % TABLE_SIZE; 102. 103. if (nHashPos == nHashStart) 104. break; 105. } 106. 107. len = strlen(str); 108. if (!HashTable[nHashPos].bExists && (len < WORD_MAX_LEN)) 109. { 110. HashATable[nHashPos] = nHashA; 111. HashBTable[nHashPos] = nHashB; 112. 113. key_array[nHashPos] = (KEYNODE *) malloc (sizeof(KEYNODE) * 1); 114. if(key_array[nHashPos] == NULL) 115. { 116. printf("10000 EMS ERROR !!!!\n"); 117. return 0; 118. } 119. 120. key_array[nHashPos]->pkey = (char *)malloc(len+1); 121. if(key_array[nHashPos]->pkey == NULL) 122. { 123. printf("10000 EMS ERROR !!!!\n"); 124. return 0; 125. } 126. 127. memset(key_array[nHashPos]->pkey, 0, len+1); 128. strncpy(key_array[nHashPos]->pkey, str, len); 129. *((key_array[nHashPos]->pkey)+len) = 0; 130. key_array[nHashPos]->pos = nHashPos; 131. key_array[nHashPos]->count = 1; 132. key_array[nHashPos]->next = NULL; 133. HashTable[nHashPos].bExists = 1; 134. return nHashPos; 135. } 136. 137. if(HashTable[nHashPos].bExists) 138. printf("30000 in the hash table %s !!!\n", str); 139. else 140. printf("90000 strkey error !!!\n"); 141. return -1; 142.}


    有了這個Hash表,接下來,我們就可以把詞插入Hash表進行存儲了。

    第三節(jié)、倒排索引文件的生成與實現(xiàn)

    ??? Hash表實現(xiàn)了(存于HashSearch.h中),還得編寫一系列的函數(shù),如下所示(所有代碼還只是初步實現(xiàn)了功能,稍后在第四部分中將予以改進與優(yōu)化):

    [cpp] view plaincopyprint?
  • //處理空白字符和空白行?
  • int GetRealString(char *pbuf)?
  • {?
  • ??? int len = strlen(pbuf) - 1;?
  • ??? while (len > 0 && (pbuf[len] == (char)0x0d || pbuf[len] == (char)0x0a || pbuf[len] == ' ' || pbuf[len] == '\t'))??
  • ??? {?
  • ??????? len--;?
  • ??? }?
  • ?
  • ??? if (len < 0)??
  • ??? {?
  • ??????? *pbuf = '\0';?
  • ??????? return len;?
  • ??? }?
  • ??? pbuf[len+1] = '\0';?
  • ??? return len + 1;?
  • }?
  • ?
  • //重新strcoll字符串比較函數(shù)?
  • int strcoll(const void *s1, const void *s2)??
  • {?
  • ??? char *c_s1 = (char *)s1;?
  • ??? char *c_s2 = (char *)s2;?
  • ??? while (*c_s1 == *c_s2++)?
  • ??? {?
  • ??????? if (*c_s1++ == '\0')??
  • ??????? {?
  • ??????????? return 0;?
  • ??????? }?
  • ??? }?
  • ?
  • ??? return *c_s1 - *--c_s2;?
  • }?
  • ?
  • //從行緩沖中得到各項信息,將其寫入items數(shù)組?
  • void GetItems(char *&move, int &count, int &wordnum)??
  • {?
  • ??? char *front = move;?
  • ??? bool flag = false;?
  • ??? int len;?
  • ??? move = strstr(move, "#####");?
  • ??? if (*(move + 5) == '#')??
  • ??? {?
  • ??????? flag = true;?
  • ??? }?
  • ?
  • ??? if (move)??
  • ??? {?
  • ??????? len = move - front;?
  • ??????? strncpy(items[count], front, len);?
  • ??? }?
  • ??? items[count][len] = '\0';?
  • ??? count++;?
  • ?
  • ??? if (flag)??
  • ??? {?
  • ??????? move = move + 10;?
  • ??? } else??
  • ??? {?
  • ??????? move = move + 5;?
  • ??? }?
  • }?
  • ?
  • //保存關鍵字相應的文檔內(nèi)容?
  • doc_list SaveItems()??
  • {?
  • ??? doc_list infolist = (doc_list) malloc(sizeof(DOCNODE));?
  • ??? strcpy_s(infolist->id, items[0]);?
  • ??? infolist->classOne = atoi(items[1]);?
  • ??? strcpy_s(infolist->classTwo, items[2]);?
  • ??? infolist->classThree = atoi(items[3]);?
  • ??? strcpy_s(infolist->time, items[4]);?
  • ??? strcpy_s(infolist->md5, items[5]);????
  • ??? infolist->weight = atoi(items[6]);?
  • ??? return infolist;?
  • }?
  • ?
  • //得到目錄下所有文件名?
  • int GetFileName(char filename[][FILENAME_MAX_LEN])?
  • {?
  • ??? _finddata_t file;?
  • ??? long handle;?
  • ??? int filenum = 0;?
  • ??? //C:\Users\zhangxu\Desktop\CreateInvertedIndex\data?
  • ??? if ((handle = _findfirst("C:\\Users\\zhangxu\\Desktop\\CreateInvertedIndex\\data\\*.txt", &file)) == -1)??
  • ??? {?
  • ??????? printf("Not Found\n");?
  • ??? }??
  • ??? else??
  • ??? {?
  • ??????? do??
  • ??????? {?
  • ??????????? strcpy_s(filename[filenum++], file.name);?
  • ??????? } while (!_findnext(handle, &file));?
  • ??? }????
  • ??? _findclose(handle);?
  • ??? return filenum;?
  • }?
  • ?
  • //以讀方式打開文件,如果成功返回文件指針?
  • FILE* OpenReadFile(int index, char filename[][FILENAME_MAX_LEN])??
  • {?
  • ??? char *abspath;?
  • ??? char dirpath[] = {"data\\"};?
  • ??? abspath = (char *)malloc(ABSPATH_MAX_LEN);?
  • ??? strcpy_s(abspath, ABSPATH_MAX_LEN, dirpath);?
  • ??? strcat_s(abspath, FILENAME_MAX_LEN, filename[index]);?
  • ?
  • ??? FILE *fp = fopen (abspath, "r");?
  • ??? if (fp == NULL)??
  • ??? {?
  • ??????? printf("open read file error!\n");?
  • ??????? return NULL;?
  • ??? }??
  • ??? else??
  • ??? {?
  • ??????? return fp;?
  • ??? }?
  • }?
  • ?
  • //以寫方式打開文件,如果成功返回文件指針?
  • FILE* OpenWriteFile(const char *in_file_path)??
  • {?
  • ??? if (in_file_path == NULL)??
  • ??? {?
  • ??????? printf("output file path error!\n");?
  • ??????? return NULL;?
  • ??? }?
  • ?
  • ??? FILE *fp = fopen(in_file_path, "w+");?
  • ??? if (fp == NULL)??
  • ??? {?
  • ??????? printf("open write file error!\n");?
  • ??? }?
  • ??? return fp;?
  • }?
  • //處理空白字符和空白行 int GetRealString(char *pbuf) {int len = strlen(pbuf) - 1;while (len > 0 && (pbuf[len] == (char)0x0d || pbuf[len] == (char)0x0a || pbuf[len] == ' ' || pbuf[len] == '\t')) {len--;}if (len < 0) {*pbuf = '\0';return len;}pbuf[len+1] = '\0';return len + 1; }//重新strcoll字符串比較函數(shù) int strcoll(const void *s1, const void *s2) {char *c_s1 = (char *)s1;char *c_s2 = (char *)s2;while (*c_s1 == *c_s2++){if (*c_s1++ == '\0') {return 0;}}return *c_s1 - *--c_s2; }//從行緩沖中得到各項信息,將其寫入items數(shù)組 void GetItems(char *&move, int &count, int &wordnum) {char *front = move;bool flag = false;int len;move = strstr(move, "#####");if (*(move + 5) == '#') {flag = true;}if (move) {len = move - front;strncpy(items[count], front, len);}items[count][len] = '\0';count++;if (flag) {move = move + 10;} else {move = move + 5;} }//保存關鍵字相應的文檔內(nèi)容 doc_list SaveItems() {doc_list infolist = (doc_list) malloc(sizeof(DOCNODE));strcpy_s(infolist->id, items[0]);infolist->classOne = atoi(items[1]);strcpy_s(infolist->classTwo, items[2]);infolist->classThree = atoi(items[3]);strcpy_s(infolist->time, items[4]);strcpy_s(infolist->md5, items[5]); infolist->weight = atoi(items[6]);return infolist; }//得到目錄下所有文件名 int GetFileName(char filename[][FILENAME_MAX_LEN]) {_finddata_t file;long handle;int filenum = 0;//C:\Users\zhangxu\Desktop\CreateInvertedIndex\dataif ((handle = _findfirst("C:\\Users\\zhangxu\\Desktop\\CreateInvertedIndex\\data\\*.txt", &file)) == -1) {printf("Not Found\n");} else {do {strcpy_s(filename[filenum++], file.name);} while (!_findnext(handle, &file));} _findclose(handle);return filenum; }//以讀方式打開文件,如果成功返回文件指針 FILE* OpenReadFile(int index, char filename[][FILENAME_MAX_LEN]) {char *abspath;char dirpath[] = {"data\\"};abspath = (char *)malloc(ABSPATH_MAX_LEN);strcpy_s(abspath, ABSPATH_MAX_LEN, dirpath);strcat_s(abspath, FILENAME_MAX_LEN, filename[index]);FILE *fp = fopen (abspath, "r");if (fp == NULL) {printf("open read file error!\n");return NULL;} else {return fp;} }//以寫方式打開文件,如果成功返回文件指針 FILE* OpenWriteFile(const char *in_file_path) {if (in_file_path == NULL) {printf("output file path error!\n");return NULL;}FILE *fp = fopen(in_file_path, "w+");if (fp == NULL) {printf("open write file error!\n");}return fp; }

    ??? 最后,主函數(shù)編寫如下:

    [cpp] view plaincopyprint?
  • int main()?
  • {???
  • ??? key_list keylist;???
  • ??? char *pbuf, *move;???
  • ??? int filenum = GetFileName(filename);???
  • ??? FILE *fr;???
  • ??? pbuf = (char *)malloc(BUF_MAX_LEN);???
  • ??? memset(pbuf, 0, BUF_MAX_LEN);???
  • ?
  • ??? FILE *fw = OpenWriteFile("index.txt");???
  • ??? if (fw == NULL)????
  • ??? {???
  • ??????? return 0;???
  • ??? }???
  • ?
  • ??? PrepareCryptTable();??? //初始化Hash表? ?
  • ?
  • ??? int wordnum = 0;???
  • ??? for (int i = 0; i < filenum; i++)???
  • ??? {???
  • ??????? fr = OpenReadFile(i, filename);???
  • ??????? if (fr == NULL)????
  • ??????? {???
  • ??????????? break;???
  • ??????? }???
  • ?
  • ??????? // 每次讀取一行處理? ?
  • ??????? while (fgets(pbuf, BUF_MAX_LEN, fr))???
  • ??????? {???
  • ??????????? int count = 0;???
  • ??????????? move = pbuf;???
  • ??????????? if (GetRealString(pbuf) <= 1)???
  • ??????????????? continue;???
  • ?
  • ??????????? while (move != NULL)???
  • ??????????? {???
  • ??????????????? // 找到第一個非'#'的字符? ?
  • ??????????????? while (*move == '#')???
  • ??????????????????? move++;???
  • ?
  • ??????????????? if (!strcmp(move, ""))???
  • ??????????????????? break;???
  • ?
  • ??????????????? GetItems(move, count, wordnum);???
  • ??????????? }???
  • ?
  • ??????????? for (int i = 7; i < count; i++)????
  • ??????????? {???
  • ??????????????? // 將關鍵字對應的文檔內(nèi)容加入文檔結點鏈表中 ?
  • ??????????????? if (keylist = SearchByString(items[i]))???? //到hash表內(nèi)查詢? ?
  • ??????????????? {???
  • ??????????????????? doc_list infolist = SaveItems();???
  • ??????????????????? infolist->next = keylist->next;???
  • ??????????????????? keylist->count++;???
  • ??????????????????? keylist->next = infolist;???
  • ??????????????? }????
  • ??????????????? else???
  • ??????????????? {???
  • ??????????????????? // 如果關鍵字第一次出現(xiàn),則將其加入hash表? ?
  • ??????????????????? int pos = InsertString(items[i]);?????? //插入hash表? ?
  • ??????????????????? keylist = key_array[pos];???
  • ??????????????????? doc_list infolist = SaveItems();???
  • ??????????????????? infolist->next = NULL;???
  • ??????????????????? keylist->next = infolist;???
  • ??????????????????? if (pos != -1)????
  • ??????????????????? {???
  • ??????????????????????? strcpy_s(words[wordnum++], items[i]);???
  • ??????????????????? }???
  • ??????????????? }???
  • ??????????? }???
  • ??????? }???
  • ??? }???
  • ?
  • ??? // 通過快排對關鍵字進行排序? ?
  • ??? qsort(words, WORD_MAX_NUM, WORD_MAX_LEN, strcoll);???
  • ?
  • ??? // 遍歷關鍵字數(shù)組,將關鍵字及其對應的文檔內(nèi)容寫入文件中? ?
  • ??? for (int i = 0; i < WORD_MAX_NUM; i++)????
  • ??? {???
  • ??????? keylist = SearchByString(words[i]);???
  • ??????? if (keylist != NULL)????
  • ??????? {???
  • ??????????? fprintf(fw, "%s %d\n", words[i], keylist->count);???
  • ??????????? doc_list infolist = keylist->next;???
  • ??????????? for (int j = 0; j < keylist->count; j++)???
  • ??????????? {???
  • ??????????????? //文檔ID,訂閱源(子頻道) 頻道分類 網(wǎng)站類ID(大頻道) 時間? md5,文檔權值? ?
  • ??????????????? fprintf(fw, "%s %d %s %d %s %s %d\n", infolist->id, infolist->classOne,????
  • ??????????????????? infolist->classTwo, infolist->classThree, infolist->time, infolist->md5, infolist->weight);???
  • ??????????????? infolist = infolist->next;???
  • ??????????? }???
  • ??????? }???
  • ??? }???
  • ?
  • ??? free(pbuf);???
  • ??? fclose(fr);???
  • ??? fclose(fw);???
  • ??? system("pause");???
  • ??? return 0;???
  • }??
  • int main() { key_list keylist; char *pbuf, *move; int filenum = GetFileName(filename); FILE *fr; pbuf = (char *)malloc(BUF_MAX_LEN); memset(pbuf, 0, BUF_MAX_LEN); FILE *fw = OpenWriteFile("index.txt"); if (fw == NULL) { return 0; } PrepareCryptTable(); //初始化Hash表 int wordnum = 0; for (int i = 0; i < filenum; i++) { fr = OpenReadFile(i, filename); if (fr == NULL) { break; } // 每次讀取一行處理 while (fgets(pbuf, BUF_MAX_LEN, fr)) { int count = 0; move = pbuf; if (GetRealString(pbuf) <= 1) continue; while (move != NULL) { // 找到第一個非'#'的字符 while (*move == '#') move++; if (!strcmp(move, "")) break; GetItems(move, count, wordnum); } for (int i = 7; i < count; i++) { // 將關鍵字對應的文檔內(nèi)容加入文檔結點鏈表中 if (keylist = SearchByString(items[i])) //到hash表內(nèi)查詢 { doc_list infolist = SaveItems(); infolist->next = keylist->next; keylist->count++; keylist->next = infolist; } else { // 如果關鍵字第一次出現(xiàn),則將其加入hash表 int pos = InsertString(items[i]); //插入hash表 keylist = key_array[pos]; doc_list infolist = SaveItems(); infolist->next = NULL; keylist->next = infolist; if (pos != -1) { strcpy_s(words[wordnum++], items[i]); } } } } } // 通過快排對關鍵字進行排序 qsort(words, WORD_MAX_NUM, WORD_MAX_LEN, strcoll); // 遍歷關鍵字數(shù)組,將關鍵字及其對應的文檔內(nèi)容寫入文件中 for (int i = 0; i < WORD_MAX_NUM; i++) { keylist = SearchByString(words[i]); if (keylist != NULL) { fprintf(fw, "%s %d\n", words[i], keylist->count); doc_list infolist = keylist->next; for (int j = 0; j < keylist->count; j++) { //文檔ID,訂閱源(子頻道) 頻道分類 網(wǎng)站類ID(大頻道) 時間 md5,文檔權值 fprintf(fw, "%s %d %s %d %s %s %d\n", infolist->id, infolist->classOne, infolist->classTwo, infolist->classThree, infolist->time, infolist->md5, infolist->weight); infolist = infolist->next; } } } free(pbuf); fclose(fr); fclose(fw); system("pause"); return 0; }

    ??? 程序編譯運行后,生成的倒排索引文件為index.txt,其與原來給定的正排文檔對照如下:

    ??? 有沒有發(fā)現(xiàn)關鍵詞奧恰洛夫出現(xiàn)在的三篇文章是同一個日期1210的,貌似與本文開頭指定的倒排索引格式要求不符?因為第二部分開頭中,已明確說明:“注,關鍵詞所在的文章如果是同一個日期的話,是挨在同一行的,用“#”符號隔開”。OK,有疑問是好事,代表你思考了,請直接轉(zhuǎn)至下文第4部分。

    第四節(jié)、程序需求功能的改進

    4.1、對相同日期與不同日期的處理

    ??? 細心的讀者可能還是會注意到:在第二部分開頭中,要求基于給定的上述正排文檔。生成如第二十四章所示的倒排索引文件是下面這樣子的,即是:

    ??? 也就是說,上面建索引的過程本該是如下的:


    ??? 與第一部分所述的SMIPI算法有什么區(qū)別?對的,就在于對在同一個日期的出現(xiàn)的關鍵詞的處理。如果是遇一舊詞,則找到其倒排記錄表的位置:相同日期,添加到之前同一日期的記錄之后(第一個記錄的后面記下同一日期的記錄數(shù)目);不同日期,另起一行新增記錄

    相同(單個)日期,根據(jù)文檔權值排序
    不同日期,根據(jù)時間排序

    ??? 代碼主要修改如下:

    [cpp] view plaincopyprint?
  • //function: 對鏈表進行冒泡排序?
  • void ListSort(key_list keylist)??
  • {?
  • ??? doc_list p = keylist->next;?
  • ??? doc_list final = NULL;?
  • ??? while (true)?
  • ??? {?
  • ??????? bool isfinish = true;?
  • ??????? while (p->next != final) {?
  • ??????????? if (strcmp(p->time, p->next->time) < 0)?
  • ??????????? {?
  • ??????????????? SwapDocNode(p);?
  • ??????????????? isfinish = false;?
  • ??????????? }?
  • ??????????? p = p->next;?
  • ??????? }?
  • ??????? final = p;?
  • ??????? p = keylist->next;?
  • ??????? if (isfinish || p->next == final) {?
  • ??????????? break;?
  • ??????? }?
  • ??? }?
  • }?
  • ?
  • int main()??
  • {?
  • ??? key_list keylist;?
  • ??? char *pbuf, *move;?
  • ??? int filenum = GetFileName(filename);?
  • ??? FILE *frp;?
  • ??? pbuf = (char *)malloc(BUF_MAX_LEN);?
  • ??? memset(pbuf, 0, BUF_MAX_LEN);?
  • ?
  • ??? FILE *fwp = OpenWriteFile("index.txt");?
  • ??? if (fwp == NULL) {?
  • ??????? return 0;?
  • ??? }?
  • ?
  • ??? PrepareCryptTable();?
  • ?
  • ??? int wordnum = 0;?
  • ??? for (int i = 0; i < filenum; i++)?
  • ??? {?
  • ??????? frp = OpenReadFile(i, filename);?
  • ??????? if (frp == NULL) {?
  • ??????????? break;?
  • ??????? }?
  • ?
  • ??????? // 每次讀取一行處理?
  • ??????? while (fgets(pbuf, BUF_MAX_LEN, frp))?
  • ??????? {?
  • ??????????? int count = 0;?
  • ??????????? move = pbuf;?
  • ??????????? if (GetRealString(pbuf) <= 1)?
  • ??????????????? continue;?
  • ?
  • ??????????? while (move != NULL)?
  • ??????????? {?
  • ??????????????? // 找到第一個非'#'的字符?
  • ??????????????? while (*move == '#')?
  • ??????????????????? move++;?
  • ?
  • ??????????????? if (!strcmp(move, ""))?
  • ??????????????????? break;?
  • ?
  • ??????????????? GetItems(move, count, wordnum);?
  • ??????????? }?
  • ?
  • ??????????? for (int i = 7; i < count; i++) {?
  • ??????????????? // 將關鍵字對應的文檔內(nèi)容加入文檔結點鏈表中?
  • ??????????????? // 如果關鍵字第一次出現(xiàn),則將其加入hash表?
  • ??????????????? if (keylist = SearchByString(items[i])) {?
  • ??????????????????? doc_list infolist = SaveItems();?
  • ??????????????????? infolist->next = keylist->next;?
  • ??????????????????? keylist->count++;?
  • ??????????????????? keylist->next = infolist;?
  • ??????????????? } else {?
  • ??????????????????? int pos = InsertString(items[i]);?
  • ??????????????????? keylist = key_array[pos];?
  • ??????????????????? doc_list infolist = SaveItems();?
  • ??????????????????? infolist->next = NULL;?
  • ??????????????????? keylist->next = infolist;?
  • ??????????????????? if (pos != -1) {?
  • ??????????????????????? strcpy_s(words[wordnum++], items[i]);?
  • ??????????????????? }?
  • ??????????????? }?
  • ??????????? }?
  • ??????? }?
  • ??? }?
  • ?
  • ??? // 通過快排對關鍵字進行排序?
  • ??? qsort(words, WORD_MAX_NUM, WORD_MAX_LEN, strcoll);?
  • ?
  • ??? // 遍歷關鍵字數(shù)組,將關鍵字及其對應的文檔內(nèi)容寫入文件中?
  • ??? int rownum = 1;?
  • ??? for (int i = 0; i < WORD_MAX_NUM; i++) {?
  • ??????? keylist = SearchByString(words[i]);?
  • ??????? if (keylist != NULL) {?
  • ??????????? doc_list infolist = keylist->next;?
  • ?
  • ??????????? char date[9];?
  • ?
  • ??????????? // 截取年月日?
  • ??????????? for (int j = 0; j < keylist->count; j++)?
  • ??????????? {?
  • ??????????????? strncpy_s(date, infolist->time, 8);?
  • ??????????????? date[8] = '\0';?
  • ??????????????? strncpy_s(infolist->time, date, 9);?
  • ??????????????? infolist = infolist->next;?
  • ??????????? }?
  • ?
  • ??????????? // 對鏈表根據(jù)時間進行排序?
  • ??????????? ListSort(keylist);?
  • ?
  • ??????????? infolist = keylist->next;?
  • ??????????? int *count = new int[WORD_MAX_NUM];?
  • ??????????? memset(count, 0, WORD_MAX_NUM);?
  • ??????????? strcpy_s(date, infolist->time);?
  • ??????????? int num = 0;?
  • ??????????? // 得到單個日期的文檔數(shù)目?
  • ??????????? for (int j = 0; j < keylist->count; j++)?
  • ??????????? {?
  • ??????????????? if (strcmp(date, infolist->time) == 0) {?
  • ??????????????????? count[num]++;?
  • ??????????????? } else {?
  • ??????????????????? count[++num]++;?
  • ??????????????? }?
  • ??????????????? strcpy_s(date, infolist->time);?
  • ??????????????? infolist = infolist->next;?
  • ??????????? }?
  • ??????????? fprintf(fwp, "%s %d %d\n", words[i], num + 1, rownum);?
  • ??????????? WriteFile(keylist, num, fwp, count);?
  • ??????????? rownum++;?
  • ??????? }?
  • ??? }?
  • ?
  • ??? free(pbuf);?
  • //? fclose(frp);?
  • ??? fclose(fwp);?
  • ??? system("pause");?
  • ??? return 0;?
  • }?
  • //function: 對鏈表進行冒泡排序 void ListSort(key_list keylist) {doc_list p = keylist->next;doc_list final = NULL;while (true){bool isfinish = true;while (p->next != final) {if (strcmp(p->time, p->next->time) < 0){SwapDocNode(p);isfinish = false;}p = p->next;}final = p;p = keylist->next;if (isfinish || p->next == final) {break;}} }int main() {key_list keylist;char *pbuf, *move;int filenum = GetFileName(filename);FILE *frp;pbuf = (char *)malloc(BUF_MAX_LEN);memset(pbuf, 0, BUF_MAX_LEN);FILE *fwp = OpenWriteFile("index.txt");if (fwp == NULL) {return 0;}PrepareCryptTable();int wordnum = 0;for (int i = 0; i < filenum; i++){frp = OpenReadFile(i, filename);if (frp == NULL) {break;}// 每次讀取一行處理while (fgets(pbuf, BUF_MAX_LEN, frp)){int count = 0;move = pbuf;if (GetRealString(pbuf) <= 1)continue;while (move != NULL){// 找到第一個非'#'的字符while (*move == '#')move++;if (!strcmp(move, ""))break;GetItems(move, count, wordnum);}for (int i = 7; i < count; i++) {// 將關鍵字對應的文檔內(nèi)容加入文檔結點鏈表中// 如果關鍵字第一次出現(xiàn),則將其加入hash表if (keylist = SearchByString(items[i])) {doc_list infolist = SaveItems();infolist->next = keylist->next;keylist->count++;keylist->next = infolist;} else {int pos = InsertString(items[i]);keylist = key_array[pos];doc_list infolist = SaveItems();infolist->next = NULL;keylist->next = infolist;if (pos != -1) {strcpy_s(words[wordnum++], items[i]);}}}}}// 通過快排對關鍵字進行排序qsort(words, WORD_MAX_NUM, WORD_MAX_LEN, strcoll);// 遍歷關鍵字數(shù)組,將關鍵字及其對應的文檔內(nèi)容寫入文件中int rownum = 1;for (int i = 0; i < WORD_MAX_NUM; i++) {keylist = SearchByString(words[i]);if (keylist != NULL) {doc_list infolist = keylist->next;char date[9];// 截取年月日for (int j = 0; j < keylist->count; j++){strncpy_s(date, infolist->time, 8);date[8] = '\0';strncpy_s(infolist->time, date, 9);infolist = infolist->next;}// 對鏈表根據(jù)時間進行排序ListSort(keylist);infolist = keylist->next;int *count = new int[WORD_MAX_NUM];memset(count, 0, WORD_MAX_NUM);strcpy_s(date, infolist->time);int num = 0;// 得到單個日期的文檔數(shù)目for (int j = 0; j < keylist->count; j++){if (strcmp(date, infolist->time) == 0) {count[num]++;} else {count[++num]++;}strcpy_s(date, infolist->time);infolist = infolist->next;}fprintf(fwp, "%s %d %d\n", words[i], num + 1, rownum);WriteFile(keylist, num, fwp, count);rownum++;}}free(pbuf); // fclose(frp);fclose(fwp);system("pause");return 0; }

    ??? 修改后編譯運行,生成的index.txt文件如下:

    4.2、為關鍵詞添上編碼

    ??? 如上圖所示,已經(jīng)滿足需求了。但可以再在每個關鍵詞的背后添加一個計數(shù)表示索引到了第多少個關鍵詞:

    第五節(jié)、算法的二次改進

    5.1、省去二次Hash???

    ??? 針對本文評論下讀者的留言,做了下思考,自覺可以省去二次hash:

    [cpp] view plaincopyprint?
  • ??????? for (int i = 7; i < count; i++)??????
  • ??????? {?????
  • ??????????? // 將關鍵字對應的文檔內(nèi)容加入文檔結點鏈表中?? ?
  • ??????????? //也就是說當查詢到hash表中沒有某個關鍵詞之,后便會插入? ?
  • ??????????? //而查詢的時候,search會調(diào)用hashstring,得到了nHashC ,nHashD? ?
  • ??????????? //插入的時候又調(diào)用了一次hashstring,得到了nHashA,nHashB? ?
  • ??????????? //而如果查詢的時候,是針對同一個關鍵詞查詢的,所以也就是說nHashC&nHashD,與nHashA&nHashB是相同的,無需二次hash? ?
  • ??????????? //所以,若要改進,改的也就是下面這個if~else語句里頭。July,2011.12.30。? ?
  • ??????????? if (keylist = SearchByString(items[i]))???? //到hash表內(nèi)查詢??? ?
  • ??????????? {?????
  • ??????????????? doc_list infolist = SaveItems();?????
  • ??????????????? infolist->next = keylist->next;?????
  • ??????????????? keylist->count++;?????
  • ??????????????? keylist->next = infolist;?????
  • ??????????? }??????
  • ??????????? else?????
  • ??????????? {?????
  • ??????????????? // 如果關鍵字第一次出現(xiàn),則將其加入hash表??? ?
  • ??????????????? int pos = InsertString(items[i]);?????? //插入hash表??? ?
  • ??????????????? keylist = key_array[pos];?????
  • ??????????????? doc_list infolist = SaveItems();?????
  • ??????????????? infolist->next = NULL;?????
  • ??????????????? keylist->next = infolist;?????
  • ??????????????? if (pos != -1)??????
  • ??????????????? {?????
  • ??????????????????? strcpy_s(words[wordnum++], items[i]);?????
  • ??????????????? }?????
  • ??????????? }?????
  • ??????? }?????
  • ??? }?????
  • }?????
  • ?
  • // 通過快排對關鍵字進行排序??? ?
  • qsort(words, WORD_MAX_NUM, WORD_MAX_LEN, strcoll);???
  • for (int i = 7; i < count; i++) { // 將關鍵字對應的文檔內(nèi)容加入文檔結點鏈表中 //也就是說當查詢到hash表中沒有某個關鍵詞之,后便會插入 //而查詢的時候,search會調(diào)用hashstring,得到了nHashC ,nHashD //插入的時候又調(diào)用了一次hashstring,得到了nHashA,nHashB //而如果查詢的時候,是針對同一個關鍵詞查詢的,所以也就是說nHashC&nHashD,與nHashA&nHashB是相同的,無需二次hash //所以,若要改進,改的也就是下面這個if~else語句里頭。July,2011.12.30。 if (keylist = SearchByString(items[i])) //到hash表內(nèi)查詢 { doc_list infolist = SaveItems(); infolist->next = keylist->next; keylist->count++; keylist->next = infolist; } else { // 如果關鍵字第一次出現(xiàn),則將其加入hash表 int pos = InsertString(items[i]); //插入hash表 keylist = key_array[pos]; doc_list infolist = SaveItems(); infolist->next = NULL; keylist->next = infolist; if (pos != -1) { strcpy_s(words[wordnum++], items[i]); } } } } } // 通過快排對關鍵字進行排序 qsort(words, WORD_MAX_NUM, WORD_MAX_LEN, strcoll);

    5.2、除去排序,針對不同日期的記錄直接插入

    [cpp] view plaincopyprint?
  • //對鏈表進行冒泡排序。這里可以改成快速排序:等到統(tǒng)計完所有有關這個關鍵詞的文章之后,才能對他集體快排。?
  • //但其實完全可以用插入排序,不同日期的,根據(jù)時間的先后找到插入位置進行插入:?
  • //假如說已有三條不同日期的記錄 A B C?
  • //來了D后,發(fā)現(xiàn)D在C之前,B之后,那么就必須為它找到B C之間的插入位置,?
  • //A B D C。July、2011.12.31。?
  • void ListSort(key_list keylist)??
  • {?
  • ??? doc_list p = keylist->next;?
  • ??? doc_list final = NULL;?
  • ??? while (true)?
  • ??? {?
  • ??????? bool isfinish = true;?
  • ??????? while (p->next != final) {?
  • ??????????? if (strcmp(p->time, p->next->time) < 0) //不同日期的按最早到最晚排序?
  • ??????????? {?
  • ??????????????? SwapDocNode(p);?
  • ??????????????? isfinish = false;?
  • ??????????? }?
  • ??????????? p = p->next;?
  • ??????? }?
  • ??????? final = p;?
  • ??????? p = keylist->next;?
  • ??????? if (isfinish || p->next == final) {?
  • ??????????? break;?
  • ??????? }?
  • ??? }?
  • }?
  • //對鏈表進行冒泡排序。這里可以改成快速排序:等到統(tǒng)計完所有有關這個關鍵詞的文章之后,才能對他集體快排。 //但其實完全可以用插入排序,不同日期的,根據(jù)時間的先后找到插入位置進行插入: //假如說已有三條不同日期的記錄 A B C //來了D后,發(fā)現(xiàn)D在C之前,B之后,那么就必須為它找到B C之間的插入位置, //A B D C。July、2011.12.31。 void ListSort(key_list keylist) {doc_list p = keylist->next;doc_list final = NULL;while (true){bool isfinish = true;while (p->next != final) {if (strcmp(p->time, p->next->time) < 0) //不同日期的按最早到最晚排序{SwapDocNode(p);isfinish = false;}p = p->next;}final = p;p = keylist->next;if (isfinish || p->next == final) {break;}} }

    ??? 綜上5.1、5.2兩節(jié)免去冒泡排序和,省去二次hash和免去冒泡排序,修改后如下:

    [cpp] view plaincopyprint?
  • ??????? for (int i = 7; i < count; i++) {???
  • ??????????? // 將關鍵字對應的文檔內(nèi)容加入文檔結點鏈表中? ?
  • ??????????? // 如果關鍵字第一次出現(xiàn),則將其加入hash表? ?
  • ??????????? InitHashValue(items[i], hashvalue);???
  • ??????????? if (keynode = SearchByString(items[i], hashvalue)) {???
  • ??????????????? doc_list infonode = SaveItems();???
  • ??????????????? doc_list p = keynode->next;???
  • ??????????????? // 根據(jù)時間由早到晚排序? ?
  • ??????????????? if (strcmp(infonode->time, p->time) < 0) {???
  • ??????????????????? //考慮infonode插入keynode后的情況? ?
  • ??????????????????? infonode->next = p;???
  • ??????????????????? keynode->next = infonode;???
  • ??????????????? } else {???
  • ??????????????????? //考慮其他情況? ?
  • ??????????????????? doc_list pre = p;???
  • ??????????????????? p = p->next;???
  • ??????????????????? while (p)???
  • ??????????????????? {???
  • ??????????????????????? if (strcmp(infonode->time, p->time) > 0) {???
  • ??????????????????????????? p = p->next;???
  • ??????????????????????????? pre = pre->next;???
  • ??????????????????????? } else {???
  • ??????????????????????????? break;???
  • ??????????????????????? }??????
  • ??????????????????? }???
  • ??????????????????? infonode->next = p;???
  • ??????????????????? pre->next = infonode;???
  • ??????????????? }???
  • ??????????????? keynode->count++;???
  • ??????????? } else {???
  • ??????????????? int pos = InsertString(items[i], hashvalue);???
  • ??????????????? keynode = key_array[pos];???
  • ??????????????? doc_list infolist = SaveItems();???
  • ??????????????? infolist->next = NULL;???
  • ??????????????? keynode->next = infolist;???
  • ??????????????? if (pos != -1) {???
  • ??????????????????? strcpy_s(words[wordnum++], items[i]);???
  • ??????????????? }???
  • ??????????? }???
  • ??????? }???
  • ??? }???
  • }???
  • ?
  • // 通過快排對關鍵字進行排序? ?
  • qsort(words, WORD_MAX_NUM, WORD_MAX_LEN, strcoll);???
  • for (int i = 7; i < count; i++) { // 將關鍵字對應的文檔內(nèi)容加入文檔結點鏈表中 // 如果關鍵字第一次出現(xiàn),則將其加入hash表 InitHashValue(items[i], hashvalue); if (keynode = SearchByString(items[i], hashvalue)) { doc_list infonode = SaveItems(); doc_list p = keynode->next; // 根據(jù)時間由早到晚排序 if (strcmp(infonode->time, p->time) < 0) { //考慮infonode插入keynode后的情況 infonode->next = p; keynode->next = infonode; } else { //考慮其他情況 doc_list pre = p; p = p->next; while (p) { if (strcmp(infonode->time, p->time) > 0) { p = p->next; pre = pre->next; } else { break; } } infonode->next = p; pre->next = infonode; } keynode->count++; } else { int pos = InsertString(items[i], hashvalue); keynode = key_array[pos]; doc_list infolist = SaveItems(); infolist->next = NULL; keynode->next = infolist; if (pos != -1) { strcpy_s(words[wordnum++], items[i]); } } } } } // 通過快排對關鍵字進行排序 qsort(words, WORD_MAX_NUM, WORD_MAX_LEN, strcoll);

    ??? 修改后編譯運行的效果圖如下(用了另外一份更大的數(shù)據(jù)文件進行測試):

    ?

    轉(zhuǎn)載于:https://www.cnblogs.com/javaspring/archive/2012/08/14/2656226.html

    總結

    以上是生活随笔為你收集整理的海量数据处理之倒排索引的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。