Lucene.Net  3.0.3
Lucene.Net is a port of the Lucene search engine library, written in C# and targeted at .NET runtime users.
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Properties Pages
PriorityQueue.cs
Go to the documentation of this file.
1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one or more
3  * contributor license agreements. See the NOTICE file distributed with
4  * this work for additional information regarding copyright ownership.
5  * The ASF licenses this file to You under the Apache License, Version 2.0
6  * (the "License"); you may not use this file except in compliance with
7  * the License. You may obtain a copy of the License at
8  *
9  * http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17 
18 using System;
19 using Lucene.Net.Support;
20 
21 namespace Lucene.Net.Util
22 {
23 
24  /// <summary>A PriorityQueue maintains a partial ordering of its elements such that the
25  /// least element can always be found in constant time. Put()'s and pop()'s
26  /// require log(size) time.
27  ///
28  /// <p/><b>NOTE</b>: This class pre-allocates a full array of
29  /// length <c>maxSize+1</c>, in <see cref="Initialize" />.
30  ///
31  /// </summary>
32  // TODO: T needs to be able to return null. Behavior might be unexpected otherwise, since it returns default(T)
33  // I only see a non-nullable type used in PriorityQueue in the tests. may be possible to re-write tests to
34  // use an IComparable class, and this can be changed back to constraining on class, to return null, or should
35  // we leave as is?
36  public abstract class PriorityQueue<T> //where T : class
37  {
38  private int size;
39  private int maxSize;
40  protected internal T[] heap;
41 
42  /// <summary>Determines the ordering of objects in this priority queue. Subclasses
43  /// must define this one method.
44  /// </summary>
45  public abstract bool LessThan(T a, T b);
46 
47  /// <summary> This method can be overridden by extending classes to return a sentinel
48  /// object which will be used by <see cref="Initialize(int)" /> to fill the queue, so
49  /// that the code which uses that queue can always assume it's full and only
50  /// change the top without attempting to insert any new object.<br/>
51  ///
52  /// Those sentinel values should always compare worse than any non-sentinel
53  /// value (i.e., <see cref="LessThan" /> should always favor the
54  /// non-sentinel values).<br/>
55  ///
56  /// By default, this method returns false, which means the queue will not be
57  /// filled with sentinel values. Otherwise, the value returned will be used to
58  /// pre-populate the queue. Adds sentinel values to the queue.<br/>
59  ///
60  /// If this method is extended to return a non-null value, then the following
61  /// usage pattern is recommended:
62  ///
63  /// <code>
64  /// // extends getSentinelObject() to return a non-null value.
65  /// PriorityQueue&lt;MyObject&gt; pq = new MyQueue&lt;MyObject&gt;(numHits);
66  /// // save the 'top' element, which is guaranteed to not be null.
67  /// MyObject pqTop = pq.top();
68  /// &lt;...&gt;
69  /// // now in order to add a new element, which is 'better' than top (after
70  /// // you've verified it is better), it is as simple as:
71  /// pqTop.change().
72  /// pqTop = pq.updateTop();
73  /// </code>
74  ///
75  /// <b>NOTE:</b> if this method returns a non-null value, it will be called by
76  /// <see cref="Initialize(int)" /> <see cref="Size()" /> times, relying on a new object to
77  /// be returned and will not check if it's null again. Therefore you should
78  /// ensure any call to this method creates a new instance and behaves
79  /// consistently, e.g., it cannot return null if it previously returned
80  /// non-null.
81  ///
82  /// </summary>
83  /// <value> the sentinel object to use to pre-populate the queue, or null if sentinel objects are not supported. </value>
84  protected internal virtual T SentinelObject
85  {
86  get { return default(T); }
87  }
88 
89  /// <summary>Subclass constructors must call this. </summary>
90  protected internal void Initialize(int maxSize)
91  {
92  size = 0;
93  int heapSize;
94  if (0 == maxSize)
95  // We allocate 1 extra to avoid if statement in top()
96  heapSize = 2;
97  else
98  {
99  if (maxSize == Int32.MaxValue)
100  {
101  // Don't wrap heapSize to -1, in this case, which
102  // causes a confusing NegativeArraySizeException.
103  // Note that very likely this will simply then hit
104  // an OOME, but at least that's more indicative to
105  // caller that this values is too big. We don't +1
106  // in this case, but it's very unlikely in practice
107  // one will actually insert this many objects into
108  // the PQ:
109  heapSize = Int32.MaxValue;
110  }
111  else
112  {
113  // NOTE: we add +1 because all access to heap is
114  // 1-based not 0-based. heap[0] is unused.
115  heapSize = maxSize + 1;
116  }
117  }
118  heap = new T[heapSize];
119  this.maxSize = maxSize;
120 
121  // If sentinel objects are supported, populate the queue with them
122  T sentinel = SentinelObject;
123  if (sentinel != null)
124  {
125  heap[1] = sentinel;
126  for (int i = 2; i < heap.Length; i++)
127  {
128  heap[i] = SentinelObject;
129  }
130  size = maxSize;
131  }
132  }
133 
134  /// <summary>
135  /// Adds an Object to a PriorityQueue in log(size) time. If one tries to add
136  /// more objects than maxSize from initialize an
137  /// <see cref="System.IndexOutOfRangeException" /> is thrown.
138  /// </summary>
139  /// <returns> the new 'top' element in the queue.
140  /// </returns>
141  public T Add(T element)
142  {
143  size++;
144  heap[size] = element;
145  UpHeap();
146  return heap[1];
147  }
148 
149  /// <summary> Adds an Object to a PriorityQueue in log(size) time.
150  /// It returns the object (if any) that was
151  /// dropped off the heap because it was full. This can be
152  /// the given parameter (in case it is smaller than the
153  /// full heap's minimum, and couldn't be added), or another
154  /// object that was previously the smallest value in the
155  /// heap and now has been replaced by a larger one, or null
156  /// if the queue wasn't yet full with maxSize elements.
157  /// </summary>
158  public virtual T InsertWithOverflow(T element)
159  {
160  if (size < maxSize)
161  {
162  Add(element);
163  return default(T);
164  }
165  else if (size > 0 && !LessThan(element, heap[1]))
166  {
167  T ret = heap[1];
168  heap[1] = element;
169  UpdateTop();
170  return ret;
171  }
172  else
173  {
174  return element;
175  }
176  }
177 
178  /// <summary>Returns the least element of the PriorityQueue in constant time. </summary>
179  public T Top()
180  {
181  // We don't need to check size here: if maxSize is 0,
182  // then heap is length 2 array with both entries null.
183  // If size is 0 then heap[1] is already null.
184  return heap[1];
185  }
186 
187  /// <summary>
188  /// Removes and returns the least element of the
189  /// PriorityQueue in log(size) time.
190  /// </summary>
191  public T Pop()
192  {
193  if (size > 0)
194  {
195  T result = heap[1]; // save first value
196  heap[1] = heap[size]; // move last to first
197  heap[size] = default(T); // permit GC of objects
198  size--;
199  DownHeap(); // adjust heap
200  return result;
201  }
202  else
203  return default(T);
204  }
205 
206  /// <summary> Should be called when the Object at top changes values.
207  /// Still log(n) worst case, but it's at least twice as fast to
208  /// <code>
209  /// pq.top().change();
210  /// pq.updateTop();
211  /// </code>
212  /// instead of
213  /// <code>
214  /// o = pq.pop();
215  /// o.change();
216  /// pq.push(o);
217  /// </code>
218  /// </summary>
219  /// <returns> the new 'top' element.</returns>
220  public T UpdateTop()
221  {
222  DownHeap();
223  return heap[1];
224  }
225 
226  /// <summary>Returns the number of elements currently stored in the PriorityQueue. </summary>
227  public int Size()
228  {
229  return size;
230  }
231 
232  /// <summary>Removes all entries from the PriorityQueue. </summary>
233  public void Clear()
234  {
235  for (int i = 0; i <= size; i++)
236  {
237  heap[i] = default(T);
238  }
239  size = 0;
240  }
241 
242  private void UpHeap()
243  {
244  int i = size;
245  T node = heap[i]; // save bottom node
246  int j = Number.URShift(i, 1);
247  while (j > 0 && LessThan(node, heap[j]))
248  {
249  heap[i] = heap[j]; // shift parents down
250  i = j;
251  j = Number.URShift(j, 1);
252  }
253  heap[i] = node; // install saved node
254  }
255 
256  private void DownHeap()
257  {
258  int i = 1;
259  T node = heap[i]; // save top node
260  int j = i << 1; // find smaller child
261  int k = j + 1;
262  if (k <= size && LessThan(heap[k], heap[j]))
263  {
264  j = k;
265  }
266  while (j <= size && LessThan(heap[j], node))
267  {
268  heap[i] = heap[j]; // shift up child
269  i = j;
270  j = i << 1;
271  k = j + 1;
272  if (k <= size && LessThan(heap[k], heap[j]))
273  {
274  j = k;
275  }
276  }
277  heap[i] = node; // install saved node
278  }
279  }
280 }