多個SHOULD的倒排表合併

BooleanScorer中的一些變量跟方法

static final int SHIFT = 11;
static final int SIZE = 1 << SHIFT;
static final int SET_SIZE = 1 << (SHIFT - 6);
static class Bucket {
   // 謀篇文檔的分數
   double score;
   // 某篇文檔包含查詢關鍵字的個數(去重)
   int freq;
}
// 初始化一個SIZE大小的數組, 數組下標是文檔號。
final Bucket[] buckets = new Bucket[SIZE];
// 每一個數組元素的二進制值的每一個bit位用來描述文檔號是否出現(後面會解釋這句話)
final long[] matching = new long[SET_SIZE];

每一個term匹配到的每一個文檔號都會作爲參數傳入到下面的collect(int doc)方法中,用來統計文檔號出現的次數

public void collect(int doc) throws IOException {
     //doc爲文檔號, MASK的值爲2047,作爲理解倒排表合併的原理,我們不用考慮文檔號大於2047的情況
     final int i = doc & MASK;
     // 每32篇文檔都會記錄在matching[idx]數組的同一個元素中
     // 比如說 0~31的文檔號就被記錄在 matching[0]這個數組元素中(看不懂?沒關係,往下看就知道了)
     final int idx = i >>> 6;
     // 用來去重的存儲文檔號, 二進制表示的數值中,每個爲1的bit位的所屬第幾位就是文檔號的值
     // 比如 00...01001(32位二進制), 說明存儲了 文檔號 0跟3
     // matching在後面遍歷中使用,因爲我們還要判斷每一篇文檔出現的次數是否滿足minSHouldMatch
     // 那麼通過這個matching值就可以從buckets[]數組中以O(1)的複雜度找到每一篇文檔出現的次數
     matching[idx] |= 1L << i;
     // 引用bucket對象,buckets[]數組下標是文檔號
     // bucket中的freq統計某個文檔號出現的次數
     final Bucket bucket = buckets[i];
     bucket.freq++;
     // 這裏可以看出,打分是一個累加的過程
     bucket.score += scorer.score();
  }

在上面的collect(int doc)方法調用結束後,每篇文檔的包含查詢關鍵字的個數(去重)都已經計算完畢,接着只需要一一取出跟minShouldMatch的值進行比較,大於minShouldMatch的文檔號即是滿足要求長度文檔。 前面說道,Bucket[]數組的下標是文檔號,那麼我們通過matching[idx],就能以O(1)的時間複雜度知道Bucket[]數組中哪些下標是有值的。

 private void scoreMatches(LeafCollector collector, int base) throws IOException {
   // 取出前面計算出的數組
   long matching[] = this.matching;
   for (int idx = 0; idx < matching.length; idx++) {
     // 取出其中一個matching[]數組的值
     long bits = matching[idx];
     // 反序列化的過程,得到所有的文檔號
     while (bits != 0L) {
       int ntz = Long.numberOfTrailingZeros(bits);
       int doc = idx << 6 | ntz;
       // 每得到一個文檔號就調用下面的方法去跟minShouldMatch比較
       scoreDocument(collector, base, doc);
       bits ^= 1L << ntz;
    }
  }
}
 
   private void scoreDocument(LeafCollector collector, int base, int i) throws IOException {
   final FakeScorer fakeScorer = this.fakeScorer;
   final Bucket bucket = buckets[i];
   // if語句爲true:文檔號出現的次數滿足minShouldMatch
   if (bucket.freq >= minShouldMatch) {
    ... ...
     // 根據之前得到的windowBase值,恢復文檔號真正的值
     final int doc = base | i;
    ... ...
     // 滿足minShouldMatch的文檔號傳給Collector,完成結果的收集
     collector.collect(doc);
  }
   bucket.freq = 0;
   bucket.score = 0;
}

例子

  文檔0:a
  文檔1:b
  文檔2:c
  文檔3:a c e
  文檔4:h
  文檔5:c e
  文檔6:c a
  文檔7:f
  文檔8:b c d e c e
  文檔9:a c e a b c

查詢關鍵字如下

BooleanQuery.Builder query = new BooleanQuery.Builder();
query.add(new TermQuery(new Term("content", "a")), BooleanClause.Occur.SHOULD);
query.add(new TermQuery(new Term("content", "b")), BooleanClause.Occur.SHOULD);
query.add(new TermQuery(new Term("content", "c")), BooleanClause.Occur.SHOULD);
query.add(new TermQuery(new Term("content", "e")), BooleanClause.Occur.SHOULD);
query.setMinimumNumberShouldMatch(2);

處理包含關鍵字“a”的文檔

將包含“a”的文檔號記錄到bucket[]數組中(圖中書寫錯誤。。。諒解~應該是的bucket[])

 

 

處理包含關鍵字“b”的文檔

將包含“b”的文檔號記錄到Bucket[]數組中

 

 

處理包含關鍵字“c”的文檔

將包含“c”的文檔號記錄到Bucket[]數組中

 

 

處理包含關鍵字“e”的文檔

將包含“e”的文檔號記錄到Bucket[]數組中

 

統計文檔號

接着我們根據matching[]數組中的元素以 0(1) 的時間複雜度找到Bucket[]中所有的文檔號,如果不通過matching[],那麼我們必須對Bucket[]的每一個元素遍歷查找

在當前的例子中,我們只要用到matching[]的第一個元素,第一個元素的值是879(爲什麼只要用到第一個元素跟第一個元素的是怎麼得來的,在BooleanScorer類中我加了詳細的註釋,這裏省略)

 

根據二進制中bit位的值爲1,這個bit位的位置來記錄包含查詢關鍵字的文檔號,包含查詢關鍵字的文檔號只有0,1,2,3,5,6,8,9一共8篇文檔,接着根據這些文檔號,把他們作爲bucket[]數組的下標,去找到每一個數組元素中的值,如果元素的值大於等於minShouldMatch,對應的文檔就是我們最終的結果,我們的例子中

query.setMinimumNumberShouldMatch(2);

所以根據最終的bucket[]

 

只有文檔號3,文檔號5,文檔6,文檔8,文檔9對應元素值大於minShouldMatch,滿足查詢要求

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章