TECA
The Toolkit for Extreme Climate Analysis
teca_priority_queue.h
1 #ifndef teca_priority_queue_h
2 #define teca_priority_queue_h
3 
4 #include <ostream>
5 #include <vector>
6 #include <map>
7 #include <type_traits>
8 #include <cmath>
9 #include <memory>
10 
11 
12 /// @cond
13 
14 // use one of the following aliases for key_map_t. key_map_t
15 // is the type of container used to hold the locations of user provided
16 // keys in the heap.
17 // for keys that are not ordinals 0 to N, use the mapped_key_t alias
18 // for contiguous keys 0 to N (faster), use the contiguous_key_t alias
19 template<typename key_t>
20 using mapped_key_t = std::map<key_t, unsigned long>;
21 
22 using contiguous_key_t = std::vector<unsigned long>;
23 
24 // use one of the following objects to provide the priority for the
25 // given key. these objects internally point to the container index by
26 // key value holding the associated priority.
27 // for keys that are not ordinals 0 to N, use the mapped_key_priority_t alias
28 // for contiguous keys 0 to N (faster), use the contiguous_key_priority_t alias
29 template<typename key_t, typename priority_t>
30 struct mapped_key_priority
31 {
32  using key_map_t = mapped_key_t<key_t>;
33 
34  mapped_key_priority(std::map<key_t, priority_t> &mp) : m_map(&mp)
35  {}
36 
37  priority_t operator()(key_t i)
38  { return (*m_map)[i]; }
39 
40  std::map<key_t, priority_t> *m_map;
41 };
42 
43 template<typename key_t, typename priority_t>
44 struct contiguous_key_priority
45 {
46  using key_map_t = contiguous_key_t;
47 
48  contiguous_key_priority(const std::vector<priority_t> &vec) :
49  m_vec(vec.data())
50  {}
51 
52  priority_t operator()(key_t i)
53  { return m_vec[i]; }
54 
55  const priority_t *m_vec;
56 };
57 
58 // forward declare the queue
59 template <typename key_t, typename lookup_t, typename comp_t=std::less<>,
60  typename key_map_t=contiguous_key_t>
62 
63 // pointer type
64 template<typename key_t, typename lookup_t, typename ...Types >
65 using p_teca_priority_queue = std::shared_ptr<
66  teca_priority_queue<key_t, lookup_t, Types...>>;
67 
68 /// @endcond
69 
70 /** @brief
71  * An indirect priority queue that supports random access modification of
72  * priority.
73  *
74  * @details
75  * an indirect priority queue that supports random access modification of
76  * priority the queue works with user provided keys and lookup functor that
77  * converts keys to priorities.
78  *
79  * ### template parameters:
80  *
81  * | name | description |
82  * | ---- | ----------- |
83  * | key_t | type of the user provided keys |
84  * | lookup_t | callable that implements: priority_t operator()(key_t key) |
85  * | comp_t | callable that implements the predicate: bool(key_t, key_t), |
86  * | | used to enforce heap order. (std::less<key_t>) |
87  * | key_map_t | type of container used to track the position in the heap |
88  * | | of the keys. The default, a vector, is only valid for |
89  * | | interger ordinals from 0 to N. Use mapped_key_t<key_t> |
90  * | | for all other cases. (contiguous_key_t) |
91  *
92  * ### typical usage:
93  *
94  * construct a container of objects to prioritize, and initialize a lookup
95  * object that given a key returns the priority of the coresponding object.
96  * create an instance of the priority_queue and push the key values. as keys
97  * are pushed heap ording is imposed, this is why objects need to be in place
98  * before pushing keys. when an object's priority has been changed one must
99  * call modified passing the key of the object. the location of each object is
100  * tracked and the queue will reprioritize itself after modification.
101  *
102  * ### recomendation:
103  *
104  * to obtain high performance, it's best to avoid using std::function for
105  * lookup operations. Instead, write a small functor so that the compiler
106  * can inline lookup calls.
107  *
108  * don't forget to change key_map_t to mapped_key_t<key_t> if
109  * keys are not integer ordinals 0 to N.
110  */
111 template <typename key_t, typename lookup_t,
112  typename comp_t, typename key_map_t>
114 {
115 public:
116 
117  ~teca_priority_queue() = default;
118 
119  // return a new instance, must pass the lookup operator that
120  // translates keys into priority values
121  static p_teca_priority_queue<key_t, lookup_t, comp_t, key_map_t>
122  New(lookup_t &lookup, unsigned long init_size=256,
123  unsigned long block_size=256)
124  {
125  p_teca_priority_queue<key_t, lookup_t,
126  comp_t, key_map_t> ptr(
128  key_t, lookup_t, comp_t, key_map_t>(
129  lookup, init_size, block_size));
130  return ptr;
131  }
132 
133  // return true if the queue has no keys
134  bool empty() { return m_end == 0; }
135 
136 
137  /// add a value into the queue
138  void push(const key_t &key);
139 
140  /// free all resources and reset the queue to an empty state
141  void clear();
142 
143  // restore heap condition after an id is modified
144  void modified(const key_t &key);
145 
146  // return the id at the top of the queue, and remove it.
147  // internal memory is not deallocated.
148  key_t pop();
149 
150  // return the id in the top of queue
151  key_t peak();
152 
153  // print the state of the queue
154  void to_stream(std::ostream &os, bool priorities = true);
155 
156 protected:
157  teca_priority_queue() = default;
158 
160  void operator=(const teca_priority_queue<key_t, lookup_t> &) = delete;
161 
162  // initialize the queue with an comperator, the initial size, and declare
163  // the amount to grow the queue by during dynamic resizing.
164  template<typename u = key_map_t>
165  teca_priority_queue(lookup_t lookup, unsigned long init_size,
166  unsigned long block_size,
167  typename std::enable_if<std::is_same<
168  std::vector<unsigned long>, u>::value>::type * = 0);
169 
170  template<typename u = key_map_t>
171  teca_priority_queue(lookup_t lookup, unsigned long init_size,
172  unsigned long block_size,
173  typename std::enable_if<std::is_same<
174  std::map<key_t,unsigned long>, u>::value>::type * = 0);
175 
176  // grow the queue to the new size
177  template<typename u = key_map_t>
178  void grow(unsigned long n,
179  typename std::enable_if<std::is_same<
180  std::vector<unsigned long>, u>::value>::type * = 0);
181 
182  // grow the queue to the new size
183  template<typename u = key_map_t>
184  void grow(unsigned long n,
185  typename std::enable_if<std::is_same<
186  std::map<key_t, unsigned long>, u>::value>::type * = 0);
187 
188  // restore the heap condition starting from here
189  // and working up
190  void up_heapify(unsigned long id);
191 
192  // restore the heap condition starting from here
193  // and working down
194  void down_heapify(unsigned long id);
195 
196  // exchange two items
197  void swap(unsigned long i, unsigned long j);
198 
199  // helpers for walking tree
200  unsigned long left_child(unsigned long a_id)
201  { return a_id*2; }
202 
203  unsigned long right_child(unsigned long a_id)
204  { return a_id*2 + 1; }
205 
206  unsigned long parent(unsigned long a_id)
207  { return a_id/2; }
208 
209 
210 private:
211  lookup_t m_lookup; // callable to turn keys into priority values
212  std::vector<key_t> m_ids; // array of keys
213  key_map_t m_locs; // map indexed by key to find the current position in the queue
214  unsigned long m_size; // size of the key buffer
215  unsigned long m_end; // index of the last key in the queue
216  unsigned long m_block_size; // amount to grow the dynamically alloacted buffers by
217 };
218 
219 
220 // --------------------------------------------------------------------------
221 template <typename key_t, typename lookup_t,
222  typename comp_t, typename key_map_t>
223 void teca_priority_queue<key_t, lookup_t,
224  comp_t, key_map_t>::push(const key_t &key)
225 {
226  // extend the queue
227  ++m_end;
228 
229  // verify that there is space, if not allocate it
230  if (m_end >= m_size)
231  this->grow(m_size + m_block_size);
232 
233  // add key and it's location
234  m_ids[m_end] = key;
235  m_locs[key] = m_end;
236 
237  // restore heap condition
238  this->up_heapify(m_end);
239 }
240 
241 // --------------------------------------------------------------------------
242 template <typename key_t, typename lookup_t,
243  typename comp_t, typename key_map_t>
244 void teca_priority_queue<key_t, lookup_t,
245  comp_t, key_map_t>::clear()
246 {
247  m_ids.clear();
248  m_locs.clear();
249  m_size = 0;
250  m_end = 0;
251 }
252 
253 // --------------------------------------------------------------------------
254 template <typename key_t, typename lookup_t,
255  typename comp_t, typename key_map_t>
256 void teca_priority_queue<key_t, lookup_t,
257  comp_t, key_map_t>::modified(const key_t &key)
258 {
259  // find the loc of the modified key
260  unsigned long id = m_locs[key];
261  // fix up then down
262  this->up_heapify(id);
263  this->down_heapify(id);
264 }
265 
266 // --------------------------------------------------------------------------
267 template <typename key_t, typename lookup_t,
268  typename comp_t, typename key_map_t>
269 key_t teca_priority_queue<key_t, lookup_t,
270  comp_t, key_map_t>::pop()
271 {
272  key_t id_1 = m_ids[1];
273  if (m_end > 0)
274  {
275  this->swap(1, m_end);
276  --m_end;
277  this->down_heapify(1);
278  }
279  return id_1;
280 }
281 
282 // --------------------------------------------------------------------------
283 template <typename key_t, typename lookup_t,
284  typename comp_t, typename key_map_t>
285 key_t teca_priority_queue<key_t, lookup_t,
286  comp_t, key_map_t>::peak()
287 {
288  return m_ids[1];
289 }
290 
291 // --------------------------------------------------------------------------
292 template <typename key_t, typename lookup_t,
293  typename comp_t, typename key_map_t>
294 void teca_priority_queue<key_t, lookup_t,
295  comp_t, key_map_t>::to_stream(std::ostream &os, bool priorities)
296 {
297  long log_end = std::log2(m_end);
298  long n_rows = log_end + 1;
299  unsigned long q = 0;
300  for (long i = 0; i < n_rows; ++i)
301  {
302  if (q > m_end)
303  break;
304 
305  long n_elem = 1 << i;
306  long isp = (1 << (n_rows - 1 - i)) - 1;
307  long bsp = 2*isp + 1;
308 
309  for (long j = 0; j < isp; ++j)
310  os << " ";
311 
312  for (long j = 0; (j < n_elem) && (q < m_end); ++j)
313  {
314  if (priorities)
315  os << m_lookup(m_ids[++q]);
316  else
317  os << m_ids[++q];
318  for (long k = 0; k < bsp; ++k)
319  os << " ";
320  }
321 
322  os << std::endl;
323  }
324 }
325 
326 // --------------------------------------------------------------------------
327 template <typename key_t, typename lookup_t,
328  typename comp_t, typename key_map_t>
329 template<typename u>
330 teca_priority_queue<key_t, lookup_t,
331  comp_t, key_map_t>::teca_priority_queue(lookup_t lookup,
332  unsigned long init_size, unsigned long block_size,
333  typename std::enable_if<std::is_same<
334  std::vector<unsigned long>, u>::value>::type *) :
335  m_lookup(lookup), m_size(init_size), m_end(0),
336  m_block_size(block_size)
337 {
338  m_ids.resize(init_size);
339  m_locs.resize(init_size);
340 }
341 
342 // --------------------------------------------------------------------------
343 template <typename key_t, typename lookup_t,
344  typename comp_t, typename key_map_t>
345 template<typename u>
346 teca_priority_queue<key_t, lookup_t,
347  comp_t, key_map_t>::teca_priority_queue(lookup_t lookup,
348  unsigned long init_size, unsigned long block_size,
349  typename std::enable_if<std::is_same<
350  std::map<key_t,unsigned long>, u>::value>::type *) :
351  m_lookup(lookup), m_size(init_size), m_end(0),
352  m_block_size(block_size)
353 {
354  m_ids.resize(init_size);
355 }
356 
357 // --------------------------------------------------------------------------
358 template <typename key_t, typename lookup_t,
359  typename comp_t, typename key_map_t>
360 template<typename u>
361 void teca_priority_queue<key_t, lookup_t,
362  comp_t, key_map_t>::grow(unsigned long n,
363  typename std::enable_if<std::is_same<
364  std::vector<unsigned long>, u>::value>::type *)
365 {
366  m_ids.resize(n);
367  m_locs.resize(n);
368  m_size = n;
369 }
370 
371 // --------------------------------------------------------------------------
372 template <typename key_t, typename lookup_t,
373  typename comp_t, typename key_map_t>
374 template<typename u>
375 void teca_priority_queue<key_t, lookup_t,
376  comp_t, key_map_t>::grow(unsigned long n,
377  typename std::enable_if<std::is_same<
378  std::map<key_t, unsigned long>, u>::value>::type *)
379 {
380  m_ids.resize(n);
381  m_size = n;
382 }
383 
384 
385  // --------------------------------------------------------------------------
386 template <typename key_t, typename lookup_t,
387  typename comp_t, typename key_map_t>
388 void teca_priority_queue<key_t, lookup_t,
389  comp_t, key_map_t>::up_heapify(unsigned long id)
390 {
391  // if at tree root then stop
392  if (id < 2)
393  return;
394 
395  // else find parent and enforce heap order
396  comp_t comp;
397  unsigned long id_p = parent(id);
398  if (comp(m_lookup(m_ids[id]), m_lookup(m_ids[id_p])))
399  this->swap(id, id_p);
400 
401  // continue up toward the root
402  this->up_heapify(id_p);
403 }
404 
405 // --------------------------------------------------------------------------
406 template <typename key_t, typename lookup_t,
407  typename comp_t, typename key_map_t>
408 void teca_priority_queue<key_t, lookup_t,
409  comp_t, key_map_t>::down_heapify(unsigned long id)
410 {
411  // if no current node then stop
412  if (id > m_end)
413  return;
414 
415  // if no left child then stop
416  unsigned long lc = left_child(id);
417  if (lc > m_end)
418  return;
419 
420  // find the smaller child
421  comp_t comp;
422  unsigned long smallc = lc;
423  unsigned long rc = right_child(id);
424  if (rc <= m_end)
425  smallc = comp(m_lookup(m_ids[lc]),
426  m_lookup(m_ids[rc])) ? lc : rc;
427 
428  // if in heap order then stop
429  if (comp(m_lookup(m_ids[id]), m_lookup(m_ids[smallc])))
430  return;
431 
432  // else swap and continue
433  this->swap(id, smallc);
434  this->down_heapify(smallc);
435 }
436 
437 // --------------------------------------------------------------------------
438 template <typename key_t, typename lookup_t,
439  typename comp_t, typename key_map_t>
440 void teca_priority_queue<key_t, lookup_t,
441  comp_t, key_map_t>::swap(unsigned long i, unsigned long j)
442 {
443  key_t key_i = m_ids[i];
444  key_t key_j = m_ids[j];
445  // exchange keys
446  m_ids[i] = key_j;
447  m_ids[j] = key_i;
448  // update locs
449  m_locs[key_j] = i;
450  m_locs[key_i] = j;
451 }
452 
453 template <typename key_t, typename lookup_t, typename ... Args>
454 std::ostream & operator<<(std::ostream &os, p_teca_priority_queue<key_t, lookup_t, Args ...> &q)
455 {
456  q->to_stream(os);
457  return os;
458 }
459 
460 #endif
teca_priority_queue::clear
void clear()
free all resources and reset the queue to an empty state
Definition: teca_priority_queue.h:245
teca_priority_queue
An indirect priority queue that supports random access modification of priority.
Definition: teca_priority_queue.h:113
teca_priority_queue::push
void push(const key_t &key)
add a value into the queue
Definition: teca_priority_queue.h:224
operator<<
std::ostream & operator<<(std::ostream &os, const teca_calendar_util::time_point &tpt)
send the time_point to a stream in humnan readable form