| using J2N.Collections.Generic.Extensions; |
| using Lucene.Net.Diagnostics; |
| using System; |
| using System.Collections.Generic; |
| using System.Runtime.CompilerServices; |
| using JCG = J2N.Collections.Generic; |
| |
| namespace Lucene.Net.Search |
| { |
| /* |
| * 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 FixedBitSet = Lucene.Net.Util.FixedBitSet; |
| using Similarity = Lucene.Net.Search.Similarities.Similarity; |
| using Term = Lucene.Net.Index.Term; |
| |
| internal sealed class SloppyPhraseScorer : Scorer |
| { |
| private PhrasePositions min, max; |
| |
| private float sloppyFreq; //phrase frequency in current doc as computed by phraseFreq(). |
| |
| private readonly Similarity.SimScorer docScorer; |
| |
| private readonly int slop; |
| private readonly int numPostings; |
| private readonly PhraseQueue pq; // for advancing min position |
| |
| private int end; // current largest phrase position |
| |
| private bool hasRpts; // flag indicating that there are repetitions (as checked in first candidate doc) |
| private bool checkedRpts; // flag to only check for repetitions in first candidate doc |
| private bool hasMultiTermRpts; |
| private PhrasePositions[][] rptGroups; // in each group are PPs that repeats each other (i.e. same term), sorted by (query) offset |
| private PhrasePositions[] rptStack; // temporary stack for switching colliding repeating pps |
| |
| private int numMatches; |
| private readonly long cost; |
| |
| internal SloppyPhraseScorer(Weight weight, PhraseQuery.PostingsAndFreq[] postings, int slop, Similarity.SimScorer docScorer) |
| : base(weight) |
| { |
| this.docScorer = docScorer; |
| this.slop = slop; |
| this.numPostings = postings == null ? 0 : postings.Length; |
| pq = new PhraseQueue(postings.Length); |
| // min(cost) |
| cost = postings[0].postings.GetCost(); |
| // convert tps to a list of phrase positions. |
| // note: phrase-position differs from term-position in that its position |
| // reflects the phrase offset: pp.pos = tp.pos - offset. |
| // this allows to easily identify a matching (exact) phrase |
| // when all PhrasePositions have exactly the same position. |
| if (postings.Length > 0) |
| { |
| min = new PhrasePositions(postings[0].postings, postings[0].position, 0, postings[0].terms); |
| max = min; |
| max.doc = -1; |
| for (int i = 1; i < postings.Length; i++) |
| { |
| PhrasePositions pp = new PhrasePositions(postings[i].postings, postings[i].position, i, postings[i].terms); |
| max.next = pp; |
| max = pp; |
| max.doc = -1; |
| } |
| max.next = min; // make it cyclic for easier manipulation |
| } |
| } |
| |
| /// <summary> |
| /// Score a candidate doc for all slop-valid position-combinations (matches) |
| /// encountered while traversing/hopping the PhrasePositions. |
| /// <para/> The score contribution of a match depends on the distance: |
| /// <para/> - highest score for distance=0 (exact match). |
| /// <para/> - score gets lower as distance gets higher. |
| /// <para/>Example: for query "a b"~2, a document "x a b a y" can be scored twice: |
| /// once for "a b" (distance=0), and once for "b a" (distance=2). |
| /// <para/>Possibly not all valid combinations are encountered, because for efficiency |
| /// we always propagate the least PhrasePosition. This allows to base on |
| /// <see cref="Util.PriorityQueue{T}"/> and move forward faster. |
| /// As result, for example, document "a b c b a" |
| /// would score differently for queries "a b c"~4 and "c b a"~4, although |
| /// they really are equivalent. |
| /// Similarly, for doc "a b c b a f g", query "c b"~2 |
| /// would get same score as "g f"~2, although "c b"~2 could be matched twice. |
| /// We may want to fix this in the future (currently not, for performance reasons). |
| /// </summary> |
| private float PhraseFreq() |
| { |
| if (!InitPhrasePositions()) |
| { |
| return 0.0f; |
| } |
| float freq = 0.0f; |
| numMatches = 0; |
| PhrasePositions pp = pq.Pop(); |
| int matchLength = end - pp.position; |
| int next = pq.Top.position; |
| while (AdvancePP(pp)) |
| { |
| if (hasRpts && !AdvanceRpts(pp)) |
| { |
| break; // pps exhausted |
| } |
| if (pp.position > next) // done minimizing current match-length |
| { |
| if (matchLength <= slop) |
| { |
| freq += docScorer.ComputeSlopFactor(matchLength); // score match |
| numMatches++; |
| } |
| pq.Add(pp); |
| pp = pq.Pop(); |
| next = pq.Top.position; |
| matchLength = end - pp.position; |
| } |
| else |
| { |
| int matchLength2 = end - pp.position; |
| if (matchLength2 < matchLength) |
| { |
| matchLength = matchLength2; |
| } |
| } |
| } |
| if (matchLength <= slop) |
| { |
| freq += docScorer.ComputeSlopFactor(matchLength); // score match |
| numMatches++; |
| } |
| return freq; |
| } |
| |
| /// <summary> |
| /// Advance a PhrasePosition and update 'end', return false if exhausted </summary> |
| private bool AdvancePP(PhrasePositions pp) |
| { |
| if (!pp.NextPosition()) |
| { |
| return false; |
| } |
| if (pp.position > end) |
| { |
| end = pp.position; |
| } |
| return true; |
| } |
| |
| /// <summary> |
| /// pp was just advanced. If that caused a repeater collision, resolve by advancing the lesser |
| /// of the two colliding pps. Note that there can only be one collision, as by the initialization |
| /// there were no collisions before pp was advanced. |
| /// </summary> |
| private bool AdvanceRpts(PhrasePositions pp) |
| { |
| if (pp.rptGroup < 0) |
| { |
| return true; // not a repeater |
| } |
| PhrasePositions[] rg = rptGroups[pp.rptGroup]; |
| FixedBitSet bits = new FixedBitSet(rg.Length); // for re-queuing after collisions are resolved |
| int k0 = pp.rptInd; |
| int k; |
| while ((k = Collide(pp)) >= 0) |
| { |
| pp = Lesser(pp, rg[k]); // always advance the lesser of the (only) two colliding pps |
| if (!AdvancePP(pp)) |
| { |
| return false; // exhausted |
| } |
| if (k != k0) // careful: mark only those currently in the queue |
| { |
| bits = FixedBitSet.EnsureCapacity(bits, k); |
| bits.Set(k); // mark that pp2 need to be re-queued |
| } |
| } |
| // collisions resolved, now re-queue |
| // empty (partially) the queue until seeing all pps advanced for resolving collisions |
| int n = 0; |
| // TODO would be good if we can avoid calling cardinality() in each iteration! |
| int numBits = bits.Length; // larges bit we set |
| while (bits.Cardinality() > 0) |
| { |
| PhrasePositions pp2 = pq.Pop(); |
| rptStack[n++] = pp2; |
| if (pp2.rptGroup >= 0 && pp2.rptInd < numBits && bits.Get(pp2.rptInd)) // this bit may not have been set |
| { |
| bits.Clear(pp2.rptInd); |
| } |
| } |
| // add back to queue |
| for (int i = n - 1; i >= 0; i--) |
| { |
| pq.Add(rptStack[i]); |
| } |
| return true; |
| } |
| |
| /// <summary> |
| /// Compare two pps, but only by position and offset </summary> |
| [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| private static PhrasePositions Lesser(PhrasePositions pp, PhrasePositions pp2) // LUCENENET: CA1822: Mark members as static |
| { |
| if (pp.position < pp2.position || (pp.position == pp2.position && pp.offset < pp2.offset)) |
| { |
| return pp; |
| } |
| return pp2; |
| } |
| |
| /// <summary> |
| /// Index of a pp2 colliding with pp, or -1 if none </summary> |
| private int Collide(PhrasePositions pp) |
| { |
| int tpPos = TpPos(pp); |
| PhrasePositions[] rg = rptGroups[pp.rptGroup]; |
| for (int i = 0; i < rg.Length; i++) |
| { |
| PhrasePositions pp2 = rg[i]; |
| if (pp2 != pp && TpPos(pp2) == tpPos) |
| { |
| return pp2.rptInd; |
| } |
| } |
| return -1; |
| } |
| |
| /// <summary> |
| /// Initialize <see cref="PhrasePositions"/> in place. |
| /// A one time initialization for this scorer (on first doc matching all terms): |
| /// <list type="bullet"> |
| /// <item><description>Check if there are repetitions</description></item> |
| /// <item><description>If there are, find groups of repetitions.</description></item> |
| /// </list> |
| /// Examples: |
| /// <list type="number"> |
| /// <item><description>no repetitions: <b>"ho my"~2</b></description></item> |
| /// <item><description>>repetitions: <b>"ho my my"~2</b></description></item> |
| /// <item><description>repetitions: <b>"my ho my"~2</b></description></item> |
| /// </list> |
| /// </summary> |
| /// <returns> <c>false</c> if PPs are exhausted (and so current doc will not be a match) </returns> |
| private bool InitPhrasePositions() |
| { |
| end = int.MinValue; |
| if (!checkedRpts) |
| { |
| return InitFirstTime(); |
| } |
| if (!hasRpts) |
| { |
| InitSimple(); |
| return true; // PPs available |
| } |
| return InitComplex(); |
| } |
| |
| /// <summary> |
| /// No repeats: simplest case, and most common. It is important to keep this piece of the code simple and efficient </summary> |
| private void InitSimple() |
| { |
| //System.err.println("initSimple: doc: "+min.doc); |
| pq.Clear(); |
| // position pps and build queue from list |
| for (PhrasePositions pp = min, prev = null; prev != max; pp = (prev = pp).next) // iterate cyclic list: done once handled max |
| { |
| pp.FirstPosition(); |
| if (pp.position > end) |
| { |
| end = pp.position; |
| } |
| pq.Add(pp); |
| } |
| } |
| |
| /// <summary> |
| /// With repeats: not so simple. </summary> |
| private bool InitComplex() |
| { |
| //System.err.println("initComplex: doc: "+min.doc); |
| PlaceFirstPositions(); |
| if (!AdvanceRepeatGroups()) |
| { |
| return false; // PPs exhausted |
| } |
| FillQueue(); |
| return true; // PPs available |
| } |
| |
| /// <summary> |
| /// Move all PPs to their first position </summary> |
| [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| private void PlaceFirstPositions() |
| { |
| for (PhrasePositions pp = min, prev = null; prev != max; pp = (prev = pp).next) // iterate cyclic list: done once handled max |
| { |
| pp.FirstPosition(); |
| } |
| } |
| |
| /// <summary> |
| /// Fill the queue (all pps are already placed) </summary> |
| private void FillQueue() |
| { |
| pq.Clear(); |
| for (PhrasePositions pp = min, prev = null; prev != max; pp = (prev = pp).next) // iterate cyclic list: done once handled max |
| { |
| if (pp.position > end) |
| { |
| end = pp.position; |
| } |
| pq.Add(pp); |
| } |
| } |
| |
| /// <summary> |
| /// At initialization (each doc), each repetition group is sorted by (query) offset. |
| /// this provides the start condition: no collisions. |
| /// <para/>Case 1: no multi-term repeats |
| /// <para/> |
| /// It is sufficient to advance each pp in the group by one less than its group index. |
| /// So lesser pp is not advanced, 2nd one advance once, 3rd one advanced twice, etc. |
| /// <para/>Case 2: multi-term repeats |
| /// </summary> |
| /// <returns> <c>false</c> if PPs are exhausted. </returns> |
| private bool AdvanceRepeatGroups() |
| { |
| foreach (PhrasePositions[] rg in rptGroups) |
| { |
| if (hasMultiTermRpts) |
| { |
| // more involved, some may not collide |
| int incr; |
| for (int i = 0; i < rg.Length; i += incr) |
| { |
| incr = 1; |
| PhrasePositions pp = rg[i]; |
| int k; |
| while ((k = Collide(pp)) >= 0) |
| { |
| PhrasePositions pp2 = Lesser(pp, rg[k]); |
| if (!AdvancePP(pp2)) // at initialization always advance pp with higher offset |
| { |
| return false; // exhausted |
| } |
| if (pp2.rptInd < i) // should not happen? |
| { |
| incr = 0; |
| break; |
| } |
| } |
| } |
| } |
| else |
| { |
| // simpler, we know exactly how much to advance |
| for (int j = 1; j < rg.Length; j++) |
| { |
| for (int k = 0; k < j; k++) |
| { |
| if (!rg[j].NextPosition()) |
| { |
| return false; // PPs exhausted |
| } |
| } |
| } |
| } |
| } |
| return true; // PPs available |
| } |
| |
| /// <summary> |
| /// Initialize with checking for repeats. Heavy work, but done only for the first candidate doc. |
| /// <para/> |
| /// If there are repetitions, check if multi-term postings (MTP) are involved. |
| /// <para/> |
| /// Without MTP, once PPs are placed in the first candidate doc, repeats (and groups) are visible. |
| /// <para/> |
| /// With MTP, a more complex check is needed, up-front, as there may be "hidden collisions". |
| /// <para/> |
| /// For example P1 has {A,B}, P1 has {B,C}, and the first doc is: "A C B". At start, P1 would point |
| /// to "A", p2 to "C", and it will not be identified that P1 and P2 are repetitions of each other. |
| /// <para/> |
| /// The more complex initialization has two parts: |
| /// <para/> |
| /// (1) identification of repetition groups. |
| /// <para/> |
| /// (2) advancing repeat groups at the start of the doc. |
| /// <para/> |
| /// For (1), a possible solution is to just create a single repetition group, |
| /// made of all repeating pps. But this would slow down the check for collisions, |
| /// as all pps would need to be checked. Instead, we compute "connected regions" |
| /// on the bipartite graph of postings and terms. |
| /// </summary> |
| private bool InitFirstTime() |
| { |
| //System.err.println("initFirstTime: doc: "+min.doc); |
| checkedRpts = true; |
| PlaceFirstPositions(); |
| |
| var rptTerms = RepeatingTerms(); |
| hasRpts = rptTerms.Count > 0; |
| |
| if (hasRpts) |
| { |
| rptStack = new PhrasePositions[numPostings]; // needed with repetitions |
| IList<IList<PhrasePositions>> rgs = GatherRptGroups(rptTerms); |
| SortRptGroups(rgs); |
| if (!AdvanceRepeatGroups()) |
| { |
| return false; // PPs exhausted |
| } |
| } |
| |
| FillQueue(); |
| return true; // PPs available |
| } |
| |
| /// <summary> |
| /// Sort each repetition group by (query) offset. |
| /// Done only once (at first doc) and allows to initialize faster for each doc. |
| /// </summary> |
| private void SortRptGroups(IList<IList<PhrasePositions>> rgs) |
| { |
| rptGroups = new PhrasePositions[rgs.Count][]; |
| IComparer<PhrasePositions> cmprtr = Comparer<PhrasePositions>.Create((pp1, pp2) => pp1.offset - pp2.offset); |
| for (int i = 0; i < rptGroups.Length; i++) |
| { |
| PhrasePositions[] rg = rgs[i].ToArray(); |
| Array.Sort(rg, cmprtr); |
| rptGroups[i] = rg; |
| for (int j = 0; j < rg.Length; j++) |
| { |
| rg[j].rptInd = j; // we use this index for efficient re-queuing |
| } |
| } |
| } |
| |
| /// <summary> |
| /// Detect repetition groups. Done once - for first doc. </summary> |
| private IList<IList<PhrasePositions>> GatherRptGroups(JCG.LinkedDictionary<Term, int?> rptTerms) |
| { |
| PhrasePositions[] rpp = RepeatingPPs(rptTerms); |
| IList<IList<PhrasePositions>> res = new List<IList<PhrasePositions>>(); |
| if (!hasMultiTermRpts) |
| { |
| // simpler - no multi-terms - can base on positions in first doc |
| for (int i = 0; i < rpp.Length; i++) |
| { |
| PhrasePositions pp = rpp[i]; |
| if (pp.rptGroup >= 0) // already marked as a repetition |
| { |
| continue; |
| } |
| int tpPos = TpPos(pp); |
| for (int j = i + 1; j < rpp.Length; j++) |
| { |
| PhrasePositions pp2 = rpp[j]; |
| if (pp2.rptGroup >= 0 || pp2.offset == pp.offset || TpPos(pp2) != tpPos) // not a repetition - not a repetition: two PPs are originally in same offset in the query! - already marked as a repetition |
| { |
| continue; |
| } |
| // a repetition |
| int g = pp.rptGroup; |
| if (g < 0) |
| { |
| g = res.Count; |
| pp.rptGroup = g; |
| List<PhrasePositions> rl = new List<PhrasePositions>(2) |
| { |
| pp |
| }; |
| res.Add(rl); |
| } |
| pp2.rptGroup = g; |
| res[g].Add(pp2); |
| } |
| } |
| } |
| else |
| { |
| // more involved - has multi-terms |
| IList<JCG.HashSet<PhrasePositions>> tmp = new List<JCG.HashSet<PhrasePositions>>(); |
| IList<FixedBitSet> bb = PpTermsBitSets(rpp, rptTerms); |
| UnionTermGroups(bb); |
| IDictionary<Term, int> tg = TermGroups(rptTerms, bb); |
| JCG.HashSet<int> distinctGroupIDs = new JCG.HashSet<int>(tg.Values); |
| for (int i = 0; i < distinctGroupIDs.Count; i++) |
| { |
| tmp.Add(new JCG.HashSet<PhrasePositions>()); |
| } |
| foreach (PhrasePositions pp in rpp) |
| { |
| foreach (Term t in pp.terms) |
| { |
| if (rptTerms.ContainsKey(t)) |
| { |
| int g = tg[t]; |
| tmp[g].Add(pp); |
| if (Debugging.AssertsEnabled) Debugging.Assert(pp.rptGroup == -1 || pp.rptGroup == g); |
| pp.rptGroup = g; |
| } |
| } |
| } |
| foreach (JCG.HashSet<PhrasePositions> hs in tmp) |
| { |
| res.Add(new List<PhrasePositions>(hs)); |
| } |
| } |
| return res; |
| } |
| |
| /// <summary> |
| /// Actual position in doc of a PhrasePosition, relies on that position = tpPos - offset) </summary> |
| [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| private static int TpPos(PhrasePositions pp) // LUCENENET: CA1822: Mark members as static |
| { |
| return pp.position + pp.offset; |
| } |
| |
| /// <summary> |
| /// Find repeating terms and assign them ordinal values </summary> |
| private JCG.LinkedDictionary<Term, int?> RepeatingTerms() |
| { |
| JCG.LinkedDictionary<Term, int?> tord = new JCG.LinkedDictionary<Term, int?>(); |
| Dictionary<Term, int?> tcnt = new Dictionary<Term, int?>(); |
| for (PhrasePositions pp = min, prev = null; prev != max; pp = (prev = pp).next) // iterate cyclic list: done once handled max |
| { |
| foreach (Term t in pp.terms) |
| { |
| tcnt.TryGetValue(t, out int? cnt0); |
| int? cnt = cnt0 == null ? new int?(1) : new int?(1 + (int)cnt0); |
| tcnt[t] = cnt; |
| if (cnt == 2) |
| { |
| tord[t] = tord.Count; |
| } |
| } |
| } |
| return tord; |
| } |
| |
| /// <summary> |
| /// Find repeating pps, and for each, if has multi-terms, update this.hasMultiTermRpts </summary> |
| private PhrasePositions[] RepeatingPPs(IDictionary<Term, int?> rptTerms) |
| { |
| List<PhrasePositions> rp = new List<PhrasePositions>(); |
| for (PhrasePositions pp = min, prev = null; prev != max; pp = (prev = pp).next) // iterate cyclic list: done once handled max |
| { |
| foreach (Term t in pp.terms) |
| { |
| if (rptTerms.ContainsKey(t)) |
| { |
| rp.Add(pp); |
| hasMultiTermRpts |= (pp.terms.Length > 1); |
| break; |
| } |
| } |
| } |
| return rp.ToArray(); |
| } |
| |
| /// <summary> |
| /// bit-sets - for each repeating pp, for each of its repeating terms, the term ordinal values is set </summary> |
| private static IList<FixedBitSet> PpTermsBitSets(PhrasePositions[] rpp, IDictionary<Term, int?> tord) // LUCENENET: CA1822: Mark members as static |
| { |
| List<FixedBitSet> bb = new List<FixedBitSet>(rpp.Length); |
| foreach (PhrasePositions pp in rpp) |
| { |
| FixedBitSet b = new FixedBitSet(tord.Count); |
| foreach (var t in pp.terms) |
| { |
| if (tord.TryGetValue(t, out int? ord) && ord.HasValue) |
| b.Set((int)ord); |
| } |
| bb.Add(b); |
| } |
| return bb; |
| } |
| |
| /// <summary> |
| /// Union (term group) bit-sets until they are disjoint (O(n^^2)), and each group have different terms </summary> |
| private static void UnionTermGroups(IList<FixedBitSet> bb) // LUCENENET: CA1822: Mark members as static |
| { |
| int incr; |
| for (int i = 0; i < bb.Count - 1; i += incr) |
| { |
| incr = 1; |
| int j = i + 1; |
| while (j < bb.Count) |
| { |
| if (bb[i].Intersects(bb[j])) |
| { |
| bb[i].Or(bb[j]); |
| bb.RemoveAt(j); |
| incr = 0; |
| } |
| else |
| { |
| ++j; |
| } |
| } |
| } |
| } |
| |
| /// <summary> |
| /// Map each term to the single group that contains it </summary> |
| private static IDictionary<Term, int> TermGroups(JCG.LinkedDictionary<Term, int?> tord, IList<FixedBitSet> bb) // LUCENENET: CA1822: Mark members as static |
| { |
| Dictionary<Term, int> tg = new Dictionary<Term, int>(); |
| Term[] t = tord.Keys.ToArray(/*new Term[0]*/); |
| for (int i = 0; i < bb.Count; i++) // i is the group no. |
| { |
| DocIdSetIterator bits = bb[i].GetIterator(); |
| int ord; |
| while ((ord = bits.NextDoc()) != NO_MORE_DOCS) |
| { |
| tg[t[ord]] = i; |
| } |
| } |
| return tg; |
| } |
| |
| public override int Freq => numMatches; |
| |
| internal float SloppyFreq => sloppyFreq; |
| |
| // private void printQueue(PrintStream ps, PhrasePositions ext, String title) { |
| // //if (min.doc != ?) return; |
| // ps.println(); |
| // ps.println("---- "+title); |
| // ps.println("EXT: "+ext); |
| // PhrasePositions[] t = new PhrasePositions[pq.size()]; |
| // if (pq.size()>0) { |
| // t[0] = pq.pop(); |
| // ps.println(" " + 0 + " " + t[0]); |
| // for (int i=1; i<t.length; i++) { |
| // t[i] = pq.pop(); |
| // assert t[i-1].position <= t[i].position; |
| // ps.println(" " + i + " " + t[i]); |
| // } |
| // // add them back |
| // for (int i=t.length-1; i>=0; i--) { |
| // pq.add(t[i]); |
| // } |
| // } |
| // } |
| |
| private bool AdvanceMin(int target) |
| { |
| if (!min.SkipTo(target)) |
| { |
| max.doc = NO_MORE_DOCS; // for further calls to docID() |
| return false; |
| } |
| min = min.next; // cyclic |
| max = max.next; // cyclic |
| return true; |
| } |
| |
| public override int DocID => max.doc; |
| |
| public override int NextDoc() |
| { |
| return Advance(max.doc + 1); // advance to the next doc after #docID() |
| } |
| |
| [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| public override float GetScore() |
| { |
| return docScorer.Score(max.doc, sloppyFreq); |
| } |
| |
| public override int Advance(int target) |
| { |
| if (Debugging.AssertsEnabled) Debugging.Assert(target > DocID); |
| do |
| { |
| if (!AdvanceMin(target)) |
| { |
| return NO_MORE_DOCS; |
| } |
| while (min.doc < max.doc) |
| { |
| if (!AdvanceMin(max.doc)) |
| { |
| return NO_MORE_DOCS; |
| } |
| } |
| // found a doc with all of the terms |
| sloppyFreq = PhraseFreq(); // check for phrase |
| target = min.doc + 1; // next target in case sloppyFreq is still 0 |
| } while (sloppyFreq == 0f); |
| |
| // found a match |
| return max.doc; |
| } |
| |
| [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| public override long GetCost() |
| { |
| return cost; |
| } |
| |
| public override string ToString() |
| { |
| return "scorer(" + m_weight + ")"; |
| } |
| } |
| } |