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 alias push opCatAssign; 54 55 // The actual data. 56 private T[] heap; 57 58 // The index of the cell into which the next element will go. 59 private uint next; 60 61 /** Inserts the given element into the heap. */ 62 void push (T t) 63 { 64 auto index = next++; 65 while (heap.length <= index) 66 heap.length = 2 * heap.length + 32; 67 68 heap [index] = t; 69 Move (t, index); 70 fixup (index); 71 } 72 73 /** Inserts all elements in the given array into the heap. */ 74 void push (T[] array) 75 { 76 if (heap.length < next + array.length) 77 heap.length = next + array.length + 32; 78 79 foreach (t; array) push (t); 80 } 81 82 /** Removes the top of this heap and returns it. */ 83 T pop () 84 { 85 return removeAt (0); 86 } 87 88 /** Remove the every instance that matches the given item. */ 89 void removeAll (T t) 90 { 91 // TODO: this is slower than it could be. 92 // I am reasonably certain we can do the O(n) scan, but I want to 93 // look at it a bit more. 94 while (remove (t)) {} 95 } 96 97 /** Remove the first instance that matches the given item. 98 * Returns: true iff the item was found, otherwise false. */ 99 bool remove (T t) 100 { 101 foreach (i, a; heap) 102 { 103 if (a is t || a == t) 104 { 105 removeAt (i); 106 return true; 107 } 108 } 109 return false; 110 } 111 112 /** Remove the element at the given index from the heap. 113 * The index is according to the heap's internal layout; you are 114 * responsible for making sure the index is correct. 115 * The heap invariant is maintained. */ 116 T removeAt (size_t index) 117 { 118 if (next <= index) 119 { 120 throw new NoSuchElementException ("Heap :: tried to remove an" 121 ~ " element with index greater than the size of the heap " 122 ~ "(did you call pop() from an empty heap?)"); 123 } 124 next--; 125 auto t = heap[index]; 126 // if next == index, then we have nothing valid on the heap 127 // so popping does nothing but change the length 128 // the other calls are irrelevant, but we surely don't want to 129 // call Move with invalid data 130 if (next > index) 131 { 132 heap[index] = heap[next]; 133 Move(heap[index], index); 134 fixdown(index); 135 136 // added via ticket 1885 (kudos to wolfwood) 137 if (heap[index] is heap[next]) 138 fixup(index); 139 } 140 return t; 141 } 142 143 /** Gets the value at the top of the heap without removing it. */ 144 T peek () 145 { 146 verify (next > 0); 147 return heap[0]; 148 } 149 150 /** Returns the number of elements in this heap. */ 151 size_t size () 152 { 153 return next; 154 } 155 156 /** Reset this heap. */ 157 void clear () 158 { 159 next = 0; 160 } 161 162 /** reset this heap, and use the provided host for value elements */ 163 void clear (T[] host) 164 { 165 (&this).heap = host; 166 clear; 167 } 168 169 /** Get the reserved capacity of this heap. */ 170 size_t capacity () 171 { 172 return heap.length; 173 } 174 175 /** 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. */ 176 uint capacity (uint value) 177 { 178 if (value < next) 179 { 180 throw new IllegalArgumentException ("Heap :: illegal truncation"); 181 } 182 heap.length = value; 183 return value; 184 } 185 186 /** Return a shallow copy of this heap. */ 187 Heap clone () 188 { 189 Heap other; 190 other.heap = (&this).heap.dup; 191 other.next = (&this).next; 192 return other; 193 } 194 195 // Get the index of the parent for the element at the given index. 196 private size_t parent (size_t index) 197 { 198 verify (index > 0); 199 return (index - 1) / 2; 200 } 201 202 // Having just inserted, restore the heap invariant (that a node's value is greater than its children) 203 private void fixup (size_t index) 204 { 205 if (index == 0) return; 206 auto par = parent (index); 207 if (!Compare(heap[par], heap[index])) 208 { 209 swap (par, index); 210 fixup (par); 211 } 212 } 213 214 // Having just removed and replaced the top of the heap with the last inserted element, 215 // restore the heap invariant. 216 private void fixdown (size_t index) 217 { 218 size_t left = 2 * index + 1; 219 size_t down; 220 if (left >= next) 221 { 222 return; 223 } 224 225 if (left == next - 1) 226 { 227 down = left; 228 } 229 else if (Compare (heap[left], heap[left + 1])) 230 { 231 down = left; 232 } 233 else 234 { 235 down = left + 1; 236 } 237 238 if (!Compare(heap[index], heap[down])) 239 { 240 swap (index, down); 241 fixdown (down); 242 } 243 } 244 245 // Swap two elements in the array. 246 private void swap (size_t a, size_t b) 247 { 248 auto t1 = heap[a]; 249 auto t2 = heap[b]; 250 heap[a] = t2; 251 Move(t2, a); 252 heap[b] = t1; 253 Move(t1, b); 254 } 255 } 256 257 258 /** A minheap implementation. This will have the smallest item as the top of the heap. 259 * 260 * Note: always pass by reference when modifying a heap. 261 * 262 */ 263 264 template MinHeap(T) 265 { 266 alias Heap!(T, minHeapCompare) MinHeap; 267 } 268 269 /** A maxheap implementation. This will have the largest item as the top of the heap. 270 * 271 * Note: always pass by reference when modifying a heap. 272 * 273 */ 274 275 template MaxHeap(T) 276 { 277 alias Heap!(T, maxHeapCompare) MaxHeap; 278 } 279 280 281 282 unittest 283 { 284 MinHeap!(size_t) h; 285 test (h.size is 0); 286 h ~= 1; 287 h ~= 3; 288 h ~= 2; 289 h ~= 4; 290 test (h.size is 4); 291 292 test (h.peek is 1); 293 test (h.peek is 1); 294 test (h.size is 4); 295 h.pop; 296 test (h.peek is 2); 297 test (h.size is 3); 298 } 299 300 unittest 301 { 302 MinHeap!(size_t) h; 303 test (h.size is 0); 304 h ~= 1; 305 h ~= 3; 306 h ~= 2; 307 h ~= 4; 308 test (h.size is 4); 309 310 test (h.pop is 1); 311 test (h.size is 3); 312 test (h.pop is 2); 313 test (h.size is 2); 314 test (h.pop is 3); 315 test (h.size is 1); 316 test (h.pop is 4); 317 test (h.size is 0); 318 } 319 320 unittest 321 { 322 MaxHeap!(size_t) h; 323 h ~= 1; 324 h ~= 3; 325 h ~= 2; 326 h ~= 4; 327 328 test (h.pop is 4); 329 test (h.pop is 3); 330 test (h.pop is 2); 331 test (h.pop is 1); 332 } 333 334 unittest 335 { 336 MaxHeap!(size_t) h; 337 h ~= 1; 338 h ~= 3; 339 h ~= 2; 340 h ~= 4; 341 h.remove(3); 342 test (h.pop is 4); 343 test (h.pop is 2); 344 test (h.pop is 1); 345 test (h.size == 0); 346 } 347 348 version (UnitTest) 349 { 350 long[] swapped; 351 size_t[] indices; 352 void onMove(long a, size_t b) 353 { 354 swapped ~= a; 355 indices ~= b; 356 } 357 } 358 359 unittest 360 { 361 // this tests that onMove is called with fixdown 362 swapped = null; 363 indices = null; 364 Heap!(long, minHeapCompare, onMove) h; 365 // no swap 366 h ~= 1; 367 // no swap 368 h ~= 3; 369 370 // onMove() is called for all insertions 371 swapped = null; 372 indices = null; 373 // pop: you replace the top with the last and 374 // percolate down. So you have to swap once when 375 // popping at a minimum, and that's if you have only two 376 // items in the heap. 377 test (h.pop is 1); 378 test (swapped.length == 1, "" ~ cast(char)('a' + swapped.length)); 379 test (swapped[0] == 3); 380 test (indices[0] == 0); 381 test (h.pop is 3); 382 test (swapped.length == 1, "" ~ cast(char)('a' + swapped.length)); 383 } 384 unittest 385 { 386 // this tests that onMove is called with fixup 387 swapped = null; 388 indices = null; 389 Heap!(long, minHeapCompare, onMove) h; 390 // no swap 391 h ~= 1; 392 // no swap 393 h ~= 3; 394 // swap: move 0 to position 0, 1 to position 2 395 h ~= 0; 396 int n=3; // onMove() called for insertions too 397 if (swapped[n] == 0) 398 { 399 test (swapped[n+1] == 1); 400 test (indices[n+0] == 0); 401 test (indices[n+1] == 2); 402 } 403 else 404 { 405 test (swapped[n+1] == 0); 406 test (swapped[n+0] == 1); 407 test (indices[n+0] == 2); 408 test (indices[n+1] == 0); 409 } 410 } 411 412 unittest 413 { 414 MaxHeap!(uint) h; 415 h ~= 1; 416 h ~= 3; 417 h ~= 2; 418 h ~= 4; 419 auto other = h.clone; 420 421 test (other.pop is 4); 422 test (other.pop is 3); 423 test (other.pop is 2); 424 test (other.pop is 1); 425 test (h.size is 4, "cloned heap shares data with original heap"); 426 test (h.pop is 4, "cloned heap shares data with original heap"); 427 test (h.pop is 3, "cloned heap shares data with original heap"); 428 test (h.pop is 2, "cloned heap shares data with original heap"); 429 test (h.pop is 1, "cloned heap shares data with original heap"); 430 }