/*
* 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 IndexReader = Lucene.Net.Index.IndexReader;
namespace Lucene.Net.Search
{
/// A {@link Collector} implementation that collects the top-scoring hits,
/// returning them as a {@link TopDocs}. This is used by {@link IndexSearcher} to
/// implement {@link TopDocs}-based search. Hits are sorted by score descending
/// and then (when the scores are tied) docID ascending. When you create an
/// instance of this collector you should know in advance whether documents are
/// going to be collected in doc Id order or not.
///
/// NOTE: The values {@link Float#NaN} and
/// {Float#NEGATIVE_INFINITY} are not valid scores. This
/// collector will not properly collect hits with such
/// scores.
///
public abstract class TopScoreDocCollector:TopDocsCollector
{
// Assumes docs are scored in order.
private class InOrderTopScoreDocCollector:TopScoreDocCollector
{
internal InOrderTopScoreDocCollector(int numHits):base(numHits)
{
}
public override void Collect(int doc)
{
float score = scorer.Score();
// This collector cannot handle these scores:
System.Diagnostics.Debug.Assert(score != float.NegativeInfinity);
System.Diagnostics.Debug.Assert(!float.IsNaN(score));
totalHits++;
if (score <= pqTop.score)
{
// Since docs are returned in-order (i.e., increasing doc Id), a document
// with equal score to pqTop.score cannot compete since HitQueue favors
// documents with lower doc Ids. Therefore reject those docs too.
return ;
}
pqTop.doc = doc + docBase;
pqTop.score = score;
pqTop = (ScoreDoc) pq.UpdateTop();
}
public override bool AcceptsDocsOutOfOrder()
{
return false;
}
}
// Assumes docs are scored out of order.
private class OutOfOrderTopScoreDocCollector:TopScoreDocCollector
{
internal OutOfOrderTopScoreDocCollector(int numHits):base(numHits)
{
}
public override void Collect(int doc)
{
float score = scorer.Score();
// This collector cannot handle NaN
System.Diagnostics.Debug.Assert(!float.IsNaN(score));
totalHits++;
doc += docBase;
if (score < pqTop.score || (score == pqTop.score && doc > pqTop.doc))
{
return ;
}
pqTop.doc = doc;
pqTop.score = score;
pqTop = (ScoreDoc) pq.UpdateTop();
}
public override bool AcceptsDocsOutOfOrder()
{
return true;
}
}
/// Creates a new {@link TopScoreDocCollector} given the number of hits to
/// collect and whether documents are scored in order by the input
/// {@link Scorer} to {@link #SetScorer(Scorer)}.
///
/// NOTE: The instances returned by this method
/// pre-allocate a full array of length
/// numHits
, and fill the array with sentinel
/// objects.
///
public static TopScoreDocCollector create(int numHits, bool docsScoredInOrder)
{
if (docsScoredInOrder)
{
return new InOrderTopScoreDocCollector(numHits);
}
else
{
return new OutOfOrderTopScoreDocCollector(numHits);
}
}
internal ScoreDoc pqTop;
internal int docBase = 0;
internal Scorer scorer;
// prevents instantiation
private TopScoreDocCollector(int numHits):base(new HitQueue(numHits, true))
{
// HitQueue implements getSentinelObject to return a ScoreDoc, so we know
// that at this point top() is already initialized.
pqTop = (ScoreDoc) pq.Top();
}
public /*protected internal*/ override TopDocs NewTopDocs(ScoreDoc[] results, int start)
{
if (results == null)
{
return EMPTY_TOPDOCS;
}
// We need to compute maxScore in order to set it in TopDocs. If start == 0,
// it means the largest element is already in results, use its score as
// maxScore. Otherwise pop everything else, until the largest element is
// extracted and use its score as maxScore.
float maxScore = System.Single.NaN;
if (start == 0)
{
maxScore = results[0].score;
}
else
{
for (int i = pq.Size(); i > 1; i--)
{
pq.Pop();
}
maxScore = ((ScoreDoc) pq.Pop()).score;
}
return new TopDocs(totalHits, results, maxScore);
}
public override void SetNextReader(IndexReader reader, int base_Renamed)
{
docBase = base_Renamed;
}
public override void SetScorer(Scorer scorer)
{
this.scorer = scorer;
}
}
}