Erase algorithm: Make an empty slot where the iterator is pointing. Scan forwards until we hit another empty slot. If an element in between doesn't rehash to the range from the current empty slot to the iterator. It must be before the empty slot, in that case we can move it to the empty slot and set the empty slot to be the location we just moved from. Relies on maintaining the invariant that ther
| 318 | // Relies on maintaining the invariant that there's no empty slots from the 'ideal' index of an |
| 319 | // element to its actual location/index. |
| 320 | iterator Erase(iterator it) { |
| 321 | // empty_index is the index that will become empty. |
| 322 | size_t empty_index = it.index_; |
| 323 | DCHECK(!IsFreeSlot(empty_index)); |
| 324 | size_t next_index = empty_index; |
| 325 | bool filled = false; // True if we filled the empty index. |
| 326 | while (true) { |
| 327 | next_index = NextIndex(next_index); |
| 328 | T& next_element = ElementForIndex(next_index); |
| 329 | // If the next element is empty, we are done. Make sure to clear the current empty index. |
| 330 | if (emptyfn_.IsEmpty(next_element)) { |
| 331 | emptyfn_.MakeEmpty(ElementForIndex(empty_index)); |
| 332 | break; |
| 333 | } |
| 334 | // Otherwise try to see if the next element can fill the current empty index. |
| 335 | const size_t next_hash = hashfn_(next_element); |
| 336 | // Calculate the ideal index, if it is within empty_index + 1 to next_index then there is |
| 337 | // nothing we can do. |
| 338 | size_t next_ideal_index = IndexForHash(next_hash); |
| 339 | // Loop around if needed for our check. |
| 340 | size_t unwrapped_next_index = next_index; |
| 341 | if (unwrapped_next_index < empty_index) { |
| 342 | unwrapped_next_index += NumBuckets(); |
| 343 | } |
| 344 | // Loop around if needed for our check. |
| 345 | size_t unwrapped_next_ideal_index = next_ideal_index; |
| 346 | if (unwrapped_next_ideal_index < empty_index) { |
| 347 | unwrapped_next_ideal_index += NumBuckets(); |
| 348 | } |
| 349 | if (unwrapped_next_ideal_index <= empty_index || |
| 350 | unwrapped_next_ideal_index > unwrapped_next_index) { |
| 351 | // If the target index isn't within our current range it must have been probed from before |
| 352 | // the empty index. |
| 353 | ElementForIndex(empty_index) = std::move(next_element); |
| 354 | filled = true; // TODO: Optimize |
| 355 | empty_index = next_index; |
| 356 | } |
| 357 | } |
| 358 | --num_elements_; |
| 359 | // If we didn't fill the slot then we need go to the next non free slot. |
| 360 | if (!filled) { |
| 361 | ++it; |
| 362 | } |
| 363 | return it; |
| 364 | } |
| 365 | |
| 366 | // Find an element, returns end() if not found. |
| 367 | // Allows custom key (K) types, example of when this is useful: |