1 /** 2 * 3 * Copyright: 4 * Copyright (C) 2008 Chris Wright. 5 * Some parts copyright (c) 2009-2016 dunnhumby Germany GmbH. 6 * All rights reserved. 7 * 8 * License: 9 * Tango Dual License: 3-Clause BSD License / Academic Free License v3.0. 10 * See LICENSE_TANGO.txt for details. 11 * 12 * Version: Oct 2008: Initial release 13 * 14 * Authors: Chris Wright, aka dhasenan 15 * 16 */ 17 18 module ocean.util.container.more.Heap; 19 20 import ocean.core.ExceptionDefinitions; 21 22 version (unittest) import ocean.core.Test; 23 24 bool minHeapCompare(T)(T a, T b) {return a <= b;} 25 bool maxHeapCompare(T)(T a, T b) {return a >= b;} 26 void defaultHeapSwap(T)(T t, size_t index) {} 27 28 /** A heap is a data structure where you can insert items in random order and extract them in sorted order. 29 * Pushing an element into the heap takes O(lg n) and popping the top of the heap takes O(lg n). Heaps are 30 * thus popular for sorting, among other things. 31 * 32 * No opApply is provided, since most people would expect this to return the contents in sorted order, 33 * not do significant heap allocation, not modify the collection, and complete in linear time. This 34 * combination is not possible with a heap. 35 * 36 * Note: always pass by reference when modifying a heap. 37 * 38 * The template arguments to the heap are: 39 * T = the element type 40 * Compare = a function called when ordering elements. Its signature should be bool(T, T). 41 * see minHeapCompare() and maxHeapCompare() for examples. 42 * Move = a function called when swapping elements. Its signature should be void(T, uint). 43 * The default does nothing, and should suffice for most users. You 44 * probably want to keep this function small; it's called O(log N) 45 * times per insertion or removal. 46 */ 47 48 struct Heap (T, alias Compare = minHeapCompare!(T), alias Move = defaultHeapSwap!(T)) 49 { 50 import ocean.core.Verify; 51 52 alias pop remove; 53 54 public template opOpAssign ( string op : "~" ) 55 { 56 alias opOpAssign = push; 57 } 58 59 // The actual data. 60 private T[] heap; 61 62 // The index of the cell into which the next element will go. 63 private uint next; 64 65 /** Inserts the given element into the heap. */ 66 void push (T t) 67 { 68 auto index = next++; 69 while (heap.length <= index) 70 heap.length = 2 * heap.length + 32; 71 72 heap [index] = t; 73 Move (t, index); 74 fixup (index); 75 } 76 77 /** Inserts all elements in the given array into the heap. */ 78 void push (T[] array) 79 { 80 if (heap.length < next + array.length) 81 heap.length = next + array.length + 32; 82 83 foreach (t; array) push (t); 84 } 85 86 /** Removes the top of this heap and returns it. */ 87 T pop () 88 { 89 return removeAt (0); 90 } 91 92 /** Remove the every instance that matches the given item. */ 93 void removeAll (T t) 94 { 95 // TODO: this is slower than it could be. 96 // I am reasonably certain we can do the O(n) scan, but I want to 97 // look at it a bit more. 98 while (remove (t)) {} 99 } 100 101 /** Remove the first instance that matches the given item. 102 * Returns: true iff the item was found, otherwise false. */ 103 bool remove (T t) 104 { 105 foreach (i, a; heap) 106 { 107 if (a is t || a == t) 108 { 109 removeAt (i); 110 return true; 111 } 112 } 113 return false; 114 } 115 116 /** Remove the element at the given index from the heap. 117 * The index is according to the heap's internal layout; you are 118 * responsible for making sure the index is correct. 119 * The heap invariant is maintained. */ 120 T removeAt (size_t index) 121 { 122 if (next <= index) 123 { 124 throw new NoSuchElementException ("Heap :: tried to remove an" 125 ~ " element with index greater than the size of the heap " 126 ~ "(did you call pop() from an empty heap?)"); 127 } 128 next--; 129 auto t = heap[index]; 130 // if next == index, then we have nothing valid on the heap 131 // so popping does nothing but change the length 132 // the other calls are irrelevant, but we surely don't want to 133 // call Move with invalid data 134 if (next > index) 135 { 136 heap[index] = heap[next]; 137 Move(heap[index], index); 138 fixdown(index); 139 140 // added via ticket 1885 (kudos to wolfwood) 141 if (heap[index] is heap[next]) 142 fixup(index); 143 } 144 return t; 145 } 146 147 /** Gets the value at the top of the heap without removing it. */ 148 T peek () 149 { 150 verify (next > 0); 151 return heap[0]; 152 } 153 154 /** Returns the number of elements in this heap. */ 155 size_t size () 156 { 157 return next; 158 } 159 160 /** Reset this heap. */ 161 void clear () 162 { 163 next = 0; 164 } 165 166 /** reset this heap, and use the provided host for value elements */ 167 void clear (T[] host) 168 { 169 this.heap = host; 170 clear; 171 } 172 173 /** Get the reserved capacity of this heap. */ 174 size_t capacity () 175 { 176 return heap.length; 177 } 178 179 /** Reserve enough space in this heap for value elements. The reserved space is truncated or extended as necessary. If the value is less than the number of elements already in the heap, throw an exception. */ 180 uint capacity (uint value) 181 { 182 if (value < next) 183 { 184 throw new IllegalArgumentException ("Heap :: illegal truncation"); 185 } 186 heap.length = value; 187 return value; 188 } 189 190 /** Return a shallow copy of this heap. */ 191 Heap clone () 192 { 193 Heap other; 194 other.heap = this.heap.dup; 195 other.next = this.next; 196 return other; 197 } 198 199 // Get the index of the parent for the element at the given index. 200 private size_t parent (size_t index) 201 { 202 verify (index > 0); 203 return (index - 1) / 2; 204 } 205 206 // Having just inserted, restore the heap invariant (that a node's value is greater than its children) 207 private void fixup (size_t index) 208 { 209 if (index == 0) return; 210 auto par = parent (index); 211 if (!Compare(heap[par], heap[index])) 212 { 213 swap (par, index); 214 fixup (par); 215 } 216 } 217 218 // Having just removed and replaced the top of the heap with the last inserted element, 219 // restore the heap invariant. 220 private void fixdown (size_t index) 221 { 222 size_t left = 2 * index + 1; 223 size_t down; 224 if (left >= next) 225 { 226 return; 227 } 228 229 if (left == next - 1) 230 { 231 down = left; 232 } 233 else if (Compare (heap[left], heap[left + 1])) 234 { 235 down = left; 236 } 237 else 238 { 239 down = left + 1; 240 } 241 242 if (!Compare(heap[index], heap[down])) 243 { 244 swap (index, down); 245 fixdown (down); 246 } 247 } 248 249 // Swap two elements in the array. 250 private void swap (size_t a, size_t b) 251 { 252 auto t1 = heap[a]; 253 auto t2 = heap[b]; 254 heap[a] = t2; 255 Move(t2, a); 256 heap[b] = t1; 257 Move(t1, b); 258 } 259 } 260 261 262 /** A minheap implementation. This will have the smallest item as the top of the heap. 263 * 264 * Note: always pass by reference when modifying a heap. 265 * 266 */ 267 268 template MinHeap(T) 269 { 270 alias Heap!(T, minHeapCompare) MinHeap; 271 } 272 273 /** A maxheap implementation. This will have the largest item as the top of the heap. 274 * 275 * Note: always pass by reference when modifying a heap. 276 * 277 */ 278 279 template MaxHeap(T) 280 { 281 alias Heap!(T, maxHeapCompare) MaxHeap; 282 } 283 284 285 286 unittest 287 { 288 MinHeap!(size_t) h; 289 test (h.size is 0); 290 h ~= 1; 291 h ~= 3; 292 h ~= 2; 293 h ~= 4; 294 test (h.size is 4); 295 296 test (h.peek is 1); 297 test (h.peek is 1); 298 test (h.size is 4); 299 h.pop; 300 test (h.peek is 2); 301 test (h.size is 3); 302 } 303 304 unittest 305 { 306 MinHeap!(size_t) h; 307 test (h.size is 0); 308 h ~= 1; 309 h ~= 3; 310 h ~= 2; 311 h ~= 4; 312 test (h.size is 4); 313 314 test (h.pop is 1); 315 test (h.size is 3); 316 test (h.pop is 2); 317 test (h.size is 2); 318 test (h.pop is 3); 319 test (h.size is 1); 320 test (h.pop is 4); 321 test (h.size is 0); 322 } 323 324 unittest 325 { 326 MaxHeap!(size_t) h; 327 h ~= 1; 328 h ~= 3; 329 h ~= 2; 330 h ~= 4; 331 332 test (h.pop is 4); 333 test (h.pop is 3); 334 test (h.pop is 2); 335 test (h.pop is 1); 336 } 337 338 unittest 339 { 340 MaxHeap!(size_t) h; 341 h ~= 1; 342 h ~= 3; 343 h ~= 2; 344 h ~= 4; 345 h.remove(3); 346 test (h.pop is 4); 347 test (h.pop is 2); 348 test (h.pop is 1); 349 test (h.size == 0); 350 } 351 352 version (unittest) 353 { 354 long[] swapped; 355 size_t[] indices; 356 void onMove(long a, size_t b) 357 { 358 swapped ~= a; 359 indices ~= b; 360 } 361 } 362 363 unittest 364 { 365 // this tests that onMove is called with fixdown 366 swapped = null; 367 indices = null; 368 Heap!(long, minHeapCompare, onMove) h; 369 // no swap 370 h ~= 1; 371 // no swap 372 h ~= 3; 373 374 // onMove() is called for all insertions 375 swapped = null; 376 indices = null; 377 // pop: you replace the top with the last and 378 // percolate down. So you have to swap once when 379 // popping at a minimum, and that's if you have only two 380 // items in the heap. 381 test (h.pop is 1); 382 test (swapped.length == 1, "" ~ cast(char)('a' + swapped.length)); 383 test (swapped[0] == 3); 384 test (indices[0] == 0); 385 test (h.pop is 3); 386 test (swapped.length == 1, "" ~ cast(char)('a' + swapped.length)); 387 } 388 unittest 389 { 390 // this tests that onMove is called with fixup 391 swapped = null; 392 indices = null; 393 Heap!(long, minHeapCompare, onMove) h; 394 // no swap 395 h ~= 1; 396 // no swap 397 h ~= 3; 398 // swap: move 0 to position 0, 1 to position 2 399 h ~= 0; 400 int n=3; // onMove() called for insertions too 401 if (swapped[n] == 0) 402 { 403 test (swapped[n+1] == 1); 404 test (indices[n+0] == 0); 405 test (indices[n+1] == 2); 406 } 407 else 408 { 409 test (swapped[n+1] == 0); 410 test (swapped[n+0] == 1); 411 test (indices[n+0] == 2); 412 test (indices[n+1] == 0); 413 } 414 } 415 416 unittest 417 { 418 MaxHeap!(uint) h; 419 h ~= 1; 420 h ~= 3; 421 h ~= 2; 422 h ~= 4; 423 auto other = h.clone; 424 425 test (other.pop is 4); 426 test (other.pop is 3); 427 test (other.pop is 2); 428 test (other.pop is 1); 429 test (h.size is 4, "cloned heap shares data with original heap"); 430 test (h.pop is 4, "cloned heap shares data with original heap"); 431 test (h.pop is 3, "cloned heap shares data with original heap"); 432 test (h.pop is 2, "cloned heap shares data with original heap"); 433 test (h.pop is 1, "cloned heap shares data with original heap"); 434 }