Index: src/test/org/apache/lucene/util/TestPriorityQueue.java =================================================================== --- src/test/org/apache/lucene/util/TestPriorityQueue.java (revision 601666) +++ src/test/org/apache/lucene/util/TestPriorityQueue.java (working copy) @@ -107,4 +107,25 @@ assertEquals(3, pq.size()); assertEquals(3, ((Integer) pq.top()).intValue()); } + + public void testInsertWithOverflow() { + int size = 4; + PriorityQueue pq = new IntegerQueue(size); + Integer i1 = new Integer(2); + Integer i2 = new Integer(3); + Integer i3 = new Integer(1); + Integer i4 = new Integer(5); + Integer i5 = new Integer(7); + Integer i6 = new Integer(1); + + assertNull(pq.insertWithOverflow(i1)); + assertNull(pq.insertWithOverflow(i2)); + assertNull(pq.insertWithOverflow(i3)); + assertNull(pq.insertWithOverflow(i4)); + assertTrue(pq.insertWithOverflow(i5) == i3); // i3 should have been dropped + assertTrue(pq.insertWithOverflow(i6) == i6); // i6 should not have been inserted + assertEquals(size, pq.size()); + assertEquals(2, ((Integer) pq.top()).intValue()); + } + } Index: src/java/org/apache/lucene/search/TopFieldDocCollector.java =================================================================== --- src/java/org/apache/lucene/search/TopFieldDocCollector.java (revision 601666) +++ src/java/org/apache/lucene/search/TopFieldDocCollector.java (working copy) @@ -41,12 +41,8 @@ super(numHits, new FieldSortedHitQueue(reader, sort.fields, numHits)); } - // javadoc inherited - public void collect(int doc, float score) { - if (score > 0.0f) { - totalHits++; - hq.insert(new FieldDoc(doc, score)); - } + protected ScoreDoc newScoreDoc(int doc, float score) { + return new FieldDoc(doc, score); } // javadoc inherited Index: src/java/org/apache/lucene/search/FuzzyQuery.java =================================================================== --- src/java/org/apache/lucene/search/FuzzyQuery.java (revision 601666) +++ src/java/org/apache/lucene/search/FuzzyQuery.java (working copy) @@ -104,20 +104,21 @@ FilteredTermEnum enumerator = getEnum(reader); int maxClauseCount = BooleanQuery.getMaxClauseCount(); ScoreTermQueue stQueue = new ScoreTermQueue(maxClauseCount); - + ScoreTerm reusableST = null; + try { do { - float minScore = 0.0f; float score = 0.0f; Term t = enumerator.term(); if (t != null) { score = enumerator.difference(); - // terms come in alphabetical order, therefore if queue is full and score - // not bigger than minScore, we can skip - if(stQueue.size() < maxClauseCount || score > minScore){ - stQueue.insert(new ScoreTerm(t, score)); - minScore = ((ScoreTerm)stQueue.top()).score; // maintain minScore + if (reusableST == null) { + reusableST = new ScoreTerm(t, score); + } else { + reusableST.score = score; + reusableST.term = t; } + reusableST = (ScoreTerm) stQueue.insertWithOverflow(reusableST); } } while (enumerator.next()); } finally { Index: src/java/org/apache/lucene/search/TopDocCollector.java =================================================================== --- src/java/org/apache/lucene/search/TopDocCollector.java (revision 601666) +++ src/java/org/apache/lucene/search/TopDocCollector.java (working copy) @@ -28,12 +28,12 @@ * documents are collected. **/ public class TopDocCollector extends HitCollector { - private int numHits; - private float minScore = 0.0f; + protected ScoreDoc reusableSD; + int totalHits; PriorityQueue hq; - + /** Construct to collect a given number of hits. * @param numHits the maximum number of hits to collect */ @@ -42,19 +42,30 @@ } TopDocCollector(int numHits, PriorityQueue hq) { - this.numHits = numHits; this.hq = hq; } + /** + * Creates a new ScoreDoc instance. Should be overriden by sub-classes, such + * as TopFieldDocCollector which create sub-classes of ScoreDoc. + */ + protected ScoreDoc newScoreDoc(int doc, float score) { + return new ScoreDoc(doc, score); + } + // javadoc inherited public void collect(int doc, float score) { - if (score > 0.0f) { - totalHits++; - if (hq.size() < numHits || score >= minScore) { - hq.insert(new ScoreDoc(doc, score)); - minScore = ((ScoreDoc)hq.top()).score; // maintain minScore - } - } + if (score <= 0.0f) { + return; + } + totalHits++; + if (reusableSD == null) { + reusableSD = newScoreDoc(doc, score); + } else { + reusableSD.doc = doc; + reusableSD.score = score; + } + reusableSD = (ScoreDoc) hq.insertWithOverflow(reusableSD); } /** The total number of documents that matched this query. */ Index: src/java/org/apache/lucene/search/FieldSortedHitQueue.java =================================================================== --- src/java/org/apache/lucene/search/FieldSortedHitQueue.java (revision 601666) +++ src/java/org/apache/lucene/search/FieldSortedHitQueue.java (working copy) @@ -80,11 +80,16 @@ return maxscore; } + // Update maxscore. + private void updateMaxScore(FieldDoc fdoc) { + maxscore = Math.max(maxscore, fdoc.score); + } + // The signature of this method takes a FieldDoc in order to avoid // the unneeded cast to retrieve the score. // inherit javadoc public boolean insert(FieldDoc fdoc) { - maxscore = Math.max(maxscore,fdoc.score); + updateMaxScore(fdoc); return super.insert(fdoc); } @@ -95,6 +100,14 @@ return insert((FieldDoc)fdoc); } + // This overrides PriorityQueue.insertWithOverflow() so that + // updateMaxScore(FieldDoc) that keeps track of the score isn't accidentally + // bypassed. + public Object insertWithOverflow(Object element) { + updateMaxScore((FieldDoc) element); + return super.insertWithOverflow(element); + } + /** * Returns whether a is less relevant than b. * @param a ScoreDoc Index: src/java/org/apache/lucene/util/PriorityQueue.java =================================================================== --- src/java/org/apache/lucene/util/PriorityQueue.java (revision 601666) +++ src/java/org/apache/lucene/util/PriorityQueue.java (working copy) @@ -21,9 +21,9 @@ least element can always be found in constant time. Put()'s and pop()'s require log(size) time. */ public abstract class PriorityQueue { - private Object[] heap; private int size; private int maxSize; + protected Object[] heap; /** Determines the ordering of objects in this priority queue. Subclasses must define this one method. */ @@ -54,26 +54,44 @@ * @param element * @return true if element is added, false otherwise. */ - public boolean insert(Object element){ - if(size < maxSize){ + public boolean insert(Object element) { + if (size < maxSize) { put(element); return true; - } - else if(size > 0 && !lessThan(element, top())){ + } else if (size > 0 && !lessThan(element, heap[1])) { heap[1] = element; adjustTop(); return true; + } else { + return false; } - else - return false; - } + } + /** + * insertWithOverflow() is similar to insert(), except its return value: it + * returns the object (if any) that was dropped off the heap because it was + * full. This can be the given parameter (in case it is smaller than the full + * heap's minimum, and couldn't be added) or another object that was + * previously the smallest value in the heap and now has been replaced by a + * larger one. + */ + public Object insertWithOverflow(Object element) { + if (size < maxSize) { + put(element); + return null; + } else if (size > 0 && !lessThan(element, heap[1])) { + Object ret = heap[1]; + heap[1] = element; + adjustTop(); + return ret; + } else { + return element; + } + } + /** Returns the least element of the PriorityQueue in constant time. */ public final Object top() { - if (size > 0) - return heap[1]; - else - return null; + return size > 0 ? heap[1] : null; } /** Removes and returns the least element of the PriorityQueue in log(size) @@ -86,8 +104,8 @@ size--; downHeap(); // adjust heap return result; - } else - return null; + } + return null; } /** Should be called when the Object at top changes values. Still log(n) @@ -101,7 +119,6 @@ downHeap(); } - /** Returns the number of elements currently stored in the PriorityQueue. */ public final int size() { return size; @@ -140,7 +157,7 @@ j = i << 1; k = j + 1; if (k <= size && lessThan(heap[k], heap[j])) { - j = k; + j = k; } } heap[i] = node; // install saved node