/* * 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 ScorerDocQueue = Lucene.Net.Util.ScorerDocQueue; namespace Lucene.Net.Search { /// A Scorer for OR like queries, counterpart of ConjunctionScorer. /// This Scorer implements {@link Scorer#SkipTo(int)} and uses skipTo() on the given Scorers. /// TODO: Implement score(HitCollector, int). /// 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 scorerDocQueue contains all subscorers ordered by their current doc(), /// with the minimum at the top. ///
The scorerDocQueue is initialized the first time next() or skipTo() is called. ///
An exhausted scorer is immediately removed from the scorerDocQueue. ///
If less than the minimumNrMatchers scorers /// remain in the scorerDocQueue 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 ScorerDocQueue scorerDocQueue; /// 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; InitScorerDocQueue(); } /// 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 scorerDocQueue. /// private void InitScorerDocQueue() { System.Collections.IEnumerator si = subScorers.GetEnumerator(); scorerDocQueue = new ScorerDocQueue(nrScorers); while (si.MoveNext()) { Scorer se = (Scorer) si.Current; if (se.NextDoc() != NO_MORE_DOCS) { // doc() method will be used in scorerDocQueue. scorerDocQueue.Insert(se); } } } /// Scores and collects all matching documents. /// The collector to which all matching documents are passed through /// {@link HitCollector#Collect(int, float)}. ///
When this method is used the {@link #Explain(int)} method should not be used. /// /// use {@link #Score(Collector)} instead. /// [Obsolete("use Score(Collector) instead.")] public override void Score(HitCollector hc) { Score(new HitCollectorWrapper(hc)); } /// Scores and collects all matching documents. /// The collector to which all matching documents are passed through. ///
When this method is used the {@link #Explain(int)} method should not be used. /// public override void Score(Collector collector) { collector.SetScorer(this); while (NextDoc() != NO_MORE_DOCS) { collector.Collect(currentDoc); } } /// Expert: Collects matching documents in a range. Hook for optimization. /// Note that {@link #Next()} must be called once before this method is called /// for the first time. /// /// The collector to which all matching documents are passed through /// {@link HitCollector#Collect(int, float)}. /// /// Do not score documents past this. /// /// true if more matching documents may remain. /// /// use {@link #Score(Collector, int, int)} instead. /// [Obsolete("use Score(Collector, int, int) instead.")] protected internal override bool Score(HitCollector hc, int max) { return Score(new HitCollectorWrapper(hc), max, DocID()); } /// Expert: Collects matching documents in a range. Hook for optimization. /// Note that {@link #Next()} must be called once before this method is called /// for the first time. /// /// The collector to which all matching documents are passed through. /// /// Do not score documents past this. /// /// true if more matching documents may remain. /// public /*protected internal*/ override bool Score(Collector collector, int max, int firstDocID) { // firstDocID is ignored since nextDoc() sets 'currentDoc' collector.SetScorer(this); while (currentDoc < max) { collector.Collect(currentDoc); if (NextDoc() == NO_MORE_DOCS) { return false; } } return true; } /// use {@link #NextDoc()} instead. /// [Obsolete("use NextDoc() instead. ")] public override bool Next() { return NextDoc() != NO_MORE_DOCS; } public override int NextDoc() { if (scorerDocQueue.Size() < minimumNrMatchers || !AdvanceAfterCurrent()) { currentDoc = NO_MORE_DOCS; } return currentDoc; } /// Advance all subscorers after the current document determined by the /// top of the scorerDocQueue. /// 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 scorerDocQueue 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. /// /// TODO: 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. /// Also delay calling score() on the sub scorers until the minimum number of /// matchers is reached. ///
For this, a Scorer array with minimumNrMatchers elements might /// hold Scorers at currentDoc that are temporarily popped from scorerQueue. ///
protected internal virtual bool AdvanceAfterCurrent() { do { // repeat until minimum nr of matchers currentDoc = scorerDocQueue.TopDoc(); currentScore = scorerDocQueue.TopScore(); nrMatchers = 1; do { // Until all subscorers are after currentDoc if (!scorerDocQueue.TopNextAndAdjustElsePop()) { if (scorerDocQueue.Size() == 0) { break; // nothing more to advance, check for last match. } } if (scorerDocQueue.TopDoc() != currentDoc) { break; // All remaining subscorers are after currentDoc. } currentScore += scorerDocQueue.TopScore(); nrMatchers++; } while (true); if (nrMatchers >= minimumNrMatchers) { return true; } else if (scorerDocQueue.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; } /// use {@link #DocID()} instead. /// [Obsolete("use DocID() instead. ")] public override int Doc() { return currentDoc; } public override int DocID() { 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. /// /// use {@link #Advance(int)} instead. /// [Obsolete("use Advance(int) instead.")] public override bool SkipTo(int target) { return Advance(target) != NO_MORE_DOCS; } /// Advances 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. /// /// the document whose number is greater than or equal to the given /// target, or -1 if none exist. /// public override int Advance(int target) { if (scorerDocQueue.Size() < minimumNrMatchers) { return currentDoc = NO_MORE_DOCS; } if (target <= currentDoc) { return currentDoc; } do { if (scorerDocQueue.TopDoc() >= target) { return AdvanceAfterCurrent()?currentDoc:(currentDoc = NO_MORE_DOCS); } else if (!scorerDocQueue.TopSkipToAndAdjustElsePop(target)) { if (scorerDocQueue.Size() < minimumNrMatchers) { return currentDoc = NO_MORE_DOCS; } } } while (true); } /// An explanation for the score of a given document. /// public override Explanation Explain(int doc) { Explanation res = new Explanation(); System.Collections.IEnumerator ssi = subScorers.GetEnumerator(); float sumScore = 0.0f; int nrMatches = 0; while (ssi.MoveNext()) { Explanation es = ((Scorer) ssi.Current).Explain(doc); if (es.GetValue() > 0.0f) { // indicates match sumScore += es.GetValue(); nrMatches++; } res.AddDetail(es); } if (nrMatchers >= minimumNrMatchers) { res.SetValue(sumScore); res.SetDescription("sum over at least " + minimumNrMatchers + " of " + subScorers.Count + ":"); } else { res.SetValue(0.0f); res.SetDescription(nrMatches + " match(es) but at least " + minimumNrMatchers + " of " + subScorers.Count + " needed"); } return res; } } }