/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ using System; using PriorityQueue = Lucene.Net.Util.PriorityQueue; namespace Lucene.Net.Search { /// A Scorer for OR like queries, counterpart of Lucene's ConjunctionScorer. /// This Scorer implements {@link Scorer#SkipTo(int)} and uses skipTo() on the given Scorers. /// class DisjunctionSumScorer : Scorer { /// The number of subscorers. private int nrScorers; /// The subscorers. protected internal System.Collections.IList subScorers; /// The minimum number of scorers that should match. private int minimumNrMatchers; /// The scorerQueue contains all subscorers ordered by their current doc(), /// with the minimum at the top. ///
The scorerQueue is initialized the first time next() or skipTo() is called. ///
An exhausted scorer is immediately removed from the scorerQueue. ///
If less than the minimumNrMatchers scorers /// remain in the scorerQueue next() and skipTo() return false. ///

/// After each to call to next() or skipTo() /// currentSumScore is the total score of the current matching doc, /// nrMatchers is the number of matching scorers, /// and all scorers are after the matching doc, or are exhausted. ///

private ScorerQueue scorerQueue = null; /// The document number of the current match. private int currentDoc = - 1; /// The number of subscorers that provide the current match. protected internal int nrMatchers = - 1; private float currentScore = System.Single.NaN; /// Construct a DisjunctionScorer. /// A collection of at least two subscorers. /// /// The positive minimum number of subscorers that should /// match to match this query. ///
When minimumNrMatchers is bigger than /// the number of subScorers, /// no matches will be produced. ///
When minimumNrMatchers equals the number of subScorers, /// it more efficient to use ConjunctionScorer. /// public DisjunctionSumScorer(System.Collections.IList subScorers, int minimumNrMatchers) : base(null) { nrScorers = subScorers.Count; if (minimumNrMatchers <= 0) { throw new System.ArgumentException("Minimum nr of matchers must be positive"); } if (nrScorers <= 1) { throw new System.ArgumentException("There must be at least 2 subScorers"); } this.minimumNrMatchers = minimumNrMatchers; this.subScorers = subScorers; } /// Construct a DisjunctionScorer, using one as the minimum number /// of matching subscorers. /// public DisjunctionSumScorer(System.Collections.IList subScorers) : this(subScorers, 1) { } /// Called the first time next() or skipTo() is called to /// initialize scorerQueue. /// private void InitScorerQueue() { System.Collections.IEnumerator si = subScorers.GetEnumerator(); scorerQueue = new ScorerQueue(this, nrScorers); while (si.MoveNext()) { Scorer se = (Scorer) si.Current; if (se.Next()) { // doc() method will be used in scorerQueue. scorerQueue.Insert(se); } } } /// A PriorityQueue that orders by {@link Scorer#Doc()}. private class ScorerQueue : PriorityQueue { private void InitBlock(DisjunctionSumScorer enclosingInstance) { this.enclosingInstance = enclosingInstance; } private DisjunctionSumScorer enclosingInstance; public DisjunctionSumScorer Enclosing_Instance { get { return enclosingInstance; } } internal ScorerQueue(DisjunctionSumScorer enclosingInstance, int size) { InitBlock(enclosingInstance); Initialize(size); } public override bool LessThan(System.Object o1, System.Object o2) { return ((Scorer) o1).Doc() < ((Scorer) o2).Doc(); } } public override bool Next() { if (scorerQueue == null) { InitScorerQueue(); } if (scorerQueue.Size() < minimumNrMatchers) { return false; } else { return AdvanceAfterCurrent(); } } /// Advance all subscorers after the current document determined by the /// top of the scorerQueue. /// Repeat until at least the minimum number of subscorers match on the same /// document and all subscorers are after that document or are exhausted. ///
On entry the scorerQueue has at least minimumNrMatchers /// available. At least the scorer with the minimum document number will be advanced. ///
/// true iff there is a match. ///
In case there is a match, currentDoc, currentSumScore, /// and nrMatchers describe the match. /// ///
/// Investigate whether it is possible to use skipTo() when /// the minimum number of matchers is bigger than one, ie. try and use the /// character of ConjunctionScorer for the minimum number of matchers. /// protected internal virtual bool AdvanceAfterCurrent() { do { // repeat until minimum nr of matchers Scorer top = (Scorer) scorerQueue.Top(); currentDoc = top.Doc(); currentScore = top.Score(); nrMatchers = 1; do { // Until all subscorers are after currentDoc if (top.Next()) { scorerQueue.AdjustTop(); } else { scorerQueue.Pop(); if (scorerQueue.Size() < (minimumNrMatchers - nrMatchers)) { // Not enough subscorers left for a match on this document, // and also no more chance of any further match. return false; } if (scorerQueue.Size() == 0) { break; // nothing more to advance, check for last match. } } top = (Scorer) scorerQueue.Top(); if (top.Doc() != currentDoc) { break; // All remaining subscorers are after currentDoc. } else { currentScore += top.Score(); nrMatchers++; } } while (true); if (nrMatchers >= minimumNrMatchers) { return true; } else if (scorerQueue.Size() < minimumNrMatchers) { return false; } } while (true); } /// Returns the score of the current document matching the query. /// Initially invalid, until {@link #Next()} is called the first time. /// public override float Score() { return currentScore; } public override int Doc() { return currentDoc; } /// Returns the number of subscorers matching the current document. /// Initially invalid, until {@link #Next()} is called the first time. /// public virtual int NrMatchers() { return nrMatchers; } /// Skips to the first match beyond the current whose document number is /// greater than or equal to a given target. ///
When this method is used the {@link #Explain(int)} method should not be used. ///
The implementation uses the skipTo() method on the subscorers. ///
/// The target document number. /// /// true iff there is such a match. /// public override bool SkipTo(int target) { if (scorerQueue == null) { InitScorerQueue(); } if (scorerQueue.Size() < minimumNrMatchers) { return false; } if (target <= currentDoc) { return true; } do { Scorer top = (Scorer) scorerQueue.Top(); if (top.Doc() >= target) { return AdvanceAfterCurrent(); } else if (top.SkipTo(target)) { scorerQueue.AdjustTop(); } else { scorerQueue.Pop(); if (scorerQueue.Size() < minimumNrMatchers) { return false; } } } while (true); } /// Gives and explanation for the score of a given document. /// Show the resulting score. See BooleanScorer.explain() on how to do this. public override Explanation Explain(int doc) { Explanation res = new Explanation(); res.SetDescription("At least " + minimumNrMatchers + " of"); System.Collections.IEnumerator ssi = subScorers.GetEnumerator(); while (ssi.MoveNext()) { res.AddDetail(((Scorer) ssi.Current).Explain(doc)); } return res; } } }