1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
base / containers / intrusive_heap.h [blame]
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_CONTAINERS_INTRUSIVE_HEAP_H_
#define BASE_CONTAINERS_INTRUSIVE_HEAP_H_
// Implements a standard max-heap, but with arbitrary element removal. To
// facilitate this, each element has associated with it a HeapHandle (an opaque
// wrapper around the index at which the element is stored), which is maintained
// by the heap as elements move within it.
//
// An IntrusiveHeap is implemented as a standard max-heap over a std::vector<T>,
// like std::make_heap. Insertion, removal and updating are amortized O(lg size)
// (occasional O(size) cost if a new vector allocation is required). Retrieving
// an element by handle is O(1). Looking up the top element is O(1). Insertions,
// removals and updates invalidate all iterators, but handles remain valid.
// Similar to a std::set, all iterators are read-only so as to disallow changing
// elements and violating the heap property. That being said, if the type you
// are storing is able to have its sort key be changed externally you can
// repair the heap by resorting the modified element via a call to "Update".
//
// Example usage:
//
// // Create a heap, wrapping integer elements with WithHeapHandle in order to
// // endow them with heap handles.
// IntrusiveHeap<WithHeapHandle<int>> heap;
//
// // WithHeapHandle<T> is for simple or opaque types. In cases where you
// // control the type declaration you can also provide HeapHandle storage by
// // deriving from InternalHeapHandleStorage.
// class Foo : public InternalHeapHandleStorage {
// public:
// explicit Foo(int);
// ...
// };
// IntrusiveHeap<Foo> heap2;
//
// // Insert some elements. Like most containers, "insert" returns an iterator
// // to the element in the container.
// heap.insert(3);
// heap.insert(1);
// auto it = heap.insert(4);
//
// // By default this is a max heap, so the top element should be 4 at this
// // point.
// EXPECT_EQ(4, heap.top().value());
//
// // Iterators are invalidated by further heap operations, but handles are
// // not. Grab a handle to the current top element so we can track it across
// // changes.
// HeapHandle* handle = it->handle();
//
// // Insert a new max element. 4 should no longer be the top.
// heap.insert(5);
// EXPECT_EQ(5, heap.top().value());
//
// // We can lookup and erase element 4 by its handle, even though it has
// // moved. Note that erasing the element invalidates the handle to it.
// EXPECT_EQ(4, heap.at(*handle).value());
// heap.erase(*handle);
// handle = nullptr;
//
// // Popping the current max (5), makes 3 the new max, as we already erased
// // element 4.
// heap.pop();
// EXPECT_EQ(3, heap.top().value());
//
// Under the hood the HeapHandle is managed by an object implementing the
// HeapHandleAccess interface, which is passed as a parameter to the
// IntrusiveHeap template:
//
// // Gets the heap handle associated with the element. This should return the
// // most recently set handle value, or HeapHandle::Invalid(). This is only
// // called in DCHECK builds.
// HeapHandle GetHeapHandle(const T*);
//
// // Changes the result of GetHeapHandle. GetHeapHandle() must return the
// // most recent value provided to SetHeapHandle() or HeapHandle::Invalid().
// // In some implementations, where GetHeapHandle() can independently
// // reproduce the correct value, it is possible that SetHeapHandle() does
// // nothing.
// void SetHeapHandle(T*, HeapHandle);
//
// // Clears the heap handle associated with the given element. After calling
// // this GetHeapHandle() must return HeapHandle::Invalid().
// void ClearHeapHandle(T*);
//
// The default implementation of HeapHandleAccess assumes that your type
// provides HeapHandle storage and will simply forward these calls to equivalent
// member functions on the type T:
//
// void T::SetHeapHandle(HeapHandle)
// void T::ClearHeapHandle()
// HeapHandle T::GetHeapHandle() const
//
// The WithHeapHandle and InternalHeapHandleStorage classes in turn provide
// implementations of that contract.
//
// In summary, to provide heap handle support for your type, you can do one of
// the following (from most manual / least magical, to least manual / most
// magical):
//
// 0. use a custom HeapHandleAccessor, and implement storage however you want;
// 1. use the default HeapHandleAccessor, and manually provide storage on your
// your element type and implement the IntrusiveHeap contract;
// 2. use the default HeapHandleAccessor, and endow your type with handle
// storage by deriving from a helper class (see InternalHeapHandleStorage);
// or,
// 3. use the default HeapHandleAccessor, and wrap your type in a container that
// provides handle storage (see WithHeapHandle<T>).
//
// Approach 0 is suitable for custom types that already implement something akin
// to heap handles, via back pointers or any other mechanism, but where the
// storage is external to the objects in the heap. If you already have the
// ability to determine where in a container an object lives despite it
// being moved, then you don't need the overhead of storing an actual HeapHandle
// whose value can be inferred.
//
// Approach 1 is is suitable in cases like the above, but where the data
// allowing you to determine the index of an element in a container is stored
// directly in the object itself.
//
// Approach 2 is suitable for types whose declarations you control, where you
// are able to use inheritance.
//
// Finally, approach 3 is suitable when you are storing PODs, or a type whose
// declaration you can not change.
//
// Most users should be using approach 2 or 3.
#include <algorithm>
#include <compare>
#include <functional>
#include <iterator>
#include <limits>
#include <memory>
#include <type_traits>
#include <utility>
#include <vector>
#include "base/base_export.h"
#include "base/check.h"
#include "base/check_op.h"
#include "base/compiler_specific.h"
#include "base/memory/ptr_util.h"
#include "base/ranges/algorithm.h"
#include "base/ranges/from_range.h"
#include "third_party/abseil-cpp/absl/container/inlined_vector.h"
namespace base {
// Intended as a wrapper around an |index_| in the vector storage backing an
// IntrusiveHeap. A HeapHandle is associated with each element in an
// IntrusiveHeap, and is maintained by the heap as the object moves around
// within it. It can be used to subsequently remove the element, or update it
// in place.
class BASE_EXPORT HeapHandle {
public:
enum : size_t { kInvalidIndex = std::numeric_limits<size_t>::max() };
constexpr HeapHandle() = default;
constexpr HeapHandle(const HeapHandle& other) = default;
HeapHandle(HeapHandle&& other) noexcept
: index_(std::exchange(other.index_, kInvalidIndex)) {}
~HeapHandle() = default;
HeapHandle& operator=(const HeapHandle& other) = default;
HeapHandle& operator=(HeapHandle&& other) noexcept {
index_ = std::exchange(other.index_, kInvalidIndex);
return *this;
}
static HeapHandle Invalid();
// Resets this handle back to an invalid state.
void reset() { index_ = kInvalidIndex; }
// Accessors.
size_t index() const { return index_; }
bool IsValid() const { return index_ != kInvalidIndex; }
// Comparison operators.
friend bool operator==(const HeapHandle& lhs,
const HeapHandle& rhs) = default;
friend std::strong_ordering operator<=>(const HeapHandle& lhs,
const HeapHandle& rhs) = default;
private:
template <typename T, typename Compare, typename HeapHandleAccessor>
friend class IntrusiveHeap;
// Only IntrusiveHeaps can create valid HeapHandles.
explicit HeapHandle(size_t index) : index_(index) {}
size_t index_ = kInvalidIndex;
};
// The default HeapHandleAccessor, which simply forwards calls to the underlying
// type.
template <typename T>
struct DefaultHeapHandleAccessor {
void SetHeapHandle(T* element, HeapHandle handle) const {
element->SetHeapHandle(handle);
}
void ClearHeapHandle(T* element) const { element->ClearHeapHandle(); }
HeapHandle GetHeapHandle(const T* element) const {
return element->GetHeapHandle();
}
};
// Intrusive heap class. This is something like a std::vector (insertion and
// removal are similar, objects don't have a fixed address in memory) crossed
// with a std::set (elements are considered immutable once they're in the
// container).
template <typename T,
typename Compare = std::less<T>,
typename HeapHandleAccessor = DefaultHeapHandleAccessor<T>>
class IntrusiveHeap {
private:
using UnderlyingType = std::vector<T>;
public:
//////////////////////////////////////////////////////////////////////////////
// Types.
using value_type = typename UnderlyingType::value_type;
using size_type = typename UnderlyingType::size_type;
using difference_type = typename UnderlyingType::difference_type;
using value_compare = Compare;
using heap_handle_accessor = HeapHandleAccessor;
using reference = typename UnderlyingType::reference;
using const_reference = typename UnderlyingType::const_reference;
using pointer = typename UnderlyingType::pointer;
using const_pointer = typename UnderlyingType::const_pointer;
// Iterators are read-only.
using iterator = typename UnderlyingType::const_iterator;
using const_iterator = typename UnderlyingType::const_iterator;
using reverse_iterator = typename UnderlyingType::const_reverse_iterator;
using const_reverse_iterator =
typename UnderlyingType::const_reverse_iterator;
//////////////////////////////////////////////////////////////////////////////
// Lifetime.
// Constructs an empty heap, with the default comparator and handle accessor.
IntrusiveHeap() = default;
// Constructs an empty heap.
IntrusiveHeap(const value_compare& comp, const heap_handle_accessor& access)
: impl_(comp, access) {}
// Constructs a heap containing all elements in the `range`.
template <class Range>
requires(std::ranges::input_range<Range>)
IntrusiveHeap(base::from_range_t,
Range&& range,
const value_compare& comp = value_compare(),
const heap_handle_accessor& access = heap_handle_accessor())
: impl_(comp, access) {
insert_range(std::forward<Range>(range));
}
// Construct a heap with elements from `[first, last)`. Prefer the
// `from_range_t` constructor as it avoid iterator pairs.
//
// # Safety
// The `first` and `last` must be a valid iterator pair for a container with
// `first <= last` or Undefined Behaviour results.
template <class InputIterator>
requires(std::input_iterator<InputIterator>)
UNSAFE_BUFFER_USAGE IntrusiveHeap(
InputIterator first,
InputIterator last,
const value_compare& comp = value_compare(),
const heap_handle_accessor& access = heap_handle_accessor())
: impl_(comp, access) {
// SAFETY: The caller must provide a valid iterator pair.
UNSAFE_BUFFERS(insert(first, last));
}
// Moves an intrusive heap. The outstanding handles remain valid and end up
// pointing to the new heap.
IntrusiveHeap(IntrusiveHeap&& other) = default;
// Copy constructor for an intrusive heap.
IntrusiveHeap(const IntrusiveHeap&);
// Initializer list constructor.
template <typename U>
IntrusiveHeap(std::initializer_list<U> ilist,
const value_compare& comp = value_compare(),
const heap_handle_accessor& access = heap_handle_accessor())
: impl_(comp, access) {
insert_range(ilist);
}
~IntrusiveHeap();
//////////////////////////////////////////////////////////////////////////////
// Assignment.
IntrusiveHeap& operator=(IntrusiveHeap&&) noexcept;
IntrusiveHeap& operator=(const IntrusiveHeap&);
IntrusiveHeap& operator=(std::initializer_list<value_type> ilist);
//////////////////////////////////////////////////////////////////////////////
// Element access.
//
// These provide O(1) const access to the elements in the heap. If you wish to
// modify an element in the heap you should first remove it from the heap, and
// then reinsert it into the heap, or use the "Replace*" helper functions. In
// the rare case where you directly modify an element in the heap you can
// subsequently repair the heap with "Update".
const_reference at(size_type pos) const { return impl_.heap_.at(pos); }
const_reference at(HeapHandle pos) const {
return impl_.heap_.at(pos.index());
}
const_reference operator[](size_type pos) const { return impl_.heap_[pos]; }
const_reference operator[](HeapHandle pos) const {
return impl_.heap_[pos.index()];
}
const_reference front() const { return impl_.heap_.front(); }
const_reference back() const { return impl_.heap_.back(); }
const_reference top() const { return impl_.heap_.front(); }
// May or may not return a null pointer if size() is zero.
const_pointer data() const { return impl_.heap_.data(); }
//////////////////////////////////////////////////////////////////////////////
// Memory management.
void reserve(size_type new_capacity) { impl_.heap_.reserve(new_capacity); }
size_type capacity() const { return impl_.heap_.capacity(); }
void shrink_to_fit() { impl_.heap_.shrink_to_fit(); }
//////////////////////////////////////////////////////////////////////////////
// Size management.
void clear();
size_type size() const { return impl_.heap_.size(); }
size_type max_size() const { return impl_.heap_.max_size(); }
bool empty() const { return impl_.heap_.empty(); }
//////////////////////////////////////////////////////////////////////////////
// Iterators.
//
// Only constant iterators are allowed.
const_iterator begin() const { return impl_.heap_.cbegin(); }
const_iterator cbegin() const { return impl_.heap_.cbegin(); }
const_iterator end() const { return impl_.heap_.cend(); }
const_iterator cend() const { return impl_.heap_.cend(); }
const_reverse_iterator rbegin() const { return impl_.heap_.crbegin(); }
const_reverse_iterator crbegin() const { return impl_.heap_.crbegin(); }
const_reverse_iterator rend() const { return impl_.heap_.crend(); }
const_reverse_iterator crend() const { return impl_.heap_.crend(); }
//////////////////////////////////////////////////////////////////////////////
// Insertion (these are std::multiset like, with no position hints).
//
// All insertion operations invalidate iterators, pointers and references.
// Handles remain valid. Insertion of one element is amortized O(lg size)
// (occasional O(size) cost if a new vector allocation is required).
const_iterator insert(const value_type& value) { return InsertImpl(value); }
const_iterator insert(value_type&& value) {
return InsertImpl(std::move(value));
}
// Inserts all elements in `range` into the heap.
template <class Range>
requires(std::ranges::input_range<Range>)
void insert_range(Range&& range) {
for (const auto& i : range) {
InsertImpl(value_type(i));
}
}
// Inserts all elements in `[first, last)` into the heap. Prefer to use
// `insert_range()` as it avoids iterator pairs.
//
// # Safety
// The `first` and `last` must be a valid iterator pair for a container with
// `first <= last` or Undefined Behaviour results.
template <class InputIterator>
requires(std::input_iterator<InputIterator>)
UNSAFE_BUFFER_USAGE void insert(InputIterator first, InputIterator last) {
for (auto it = first; it != last;
// SAFETY: The caller must provide a valid iterator pair.
UNSAFE_BUFFERS(++it)) {
InsertImpl(value_type(*it));
}
}
template <typename... Args>
const_iterator emplace(Args&&... args) {
return InsertImpl(value_type(std::forward<Args>(args)...));
}
//////////////////////////////////////////////////////////////////////////////
// Removing elements.
//
// Erasing invalidates all outstanding iterators, pointers and references.
// Handles remain valid. Removing one element is amortized O(lg size)
// (occasional O(size) cost if a new vector allocation is required).
//
// Note that it is safe for the element being removed to be in an invalid
// state (modified such that it may currently violate the heap property)
// when this called.
// Takes the element from the heap at the given position, erasing that entry
// from the heap. This can only be called if |value_type| is movable.
value_type take(size_type pos);
// Version of take that will accept iterators and handles. This can only be
// called if |value_type| is movable.
template <typename P>
value_type take(P pos) {
return take(ToIndex(pos));
}
// Takes the top element from the heap.
value_type take_top() { return take(0u); }
// Erases the element at the given position |pos|.
void erase(size_type pos);
// Version of erase that will accept iterators and handles.
template <typename P>
void erase(P pos) {
erase(ToIndex(pos));
}
// Removes the element at the top of the heap (accessible via "top", or
// "front" or "take").
void pop() { erase(0u); }
// Erases every element that matches the predicate. This is done in-place for
// maximum efficiency. Also, to avoid re-entrancy issues, elements are deleted
// at the very end.
// Note: This function is currently tuned for a use-case where there are
// usually 8 or less elements removed at a time. Consider adding a template
// parameter if a different tuning is needed.
template <typename Functor>
void EraseIf(Functor predicate) {
// Stable partition ensures that if no elements are erased, the heap remains
// intact.
auto erase_start = std::stable_partition(
impl_.heap_.begin(), impl_.heap_.end(),
[&](const auto& element) { return !predicate(element); });
// Clear the heap handle of every element that will be erased.
for (size_t i = static_cast<size_t>(erase_start - impl_.heap_.begin());
i < impl_.heap_.size(); ++i) {
ClearHeapHandle(i);
}
// Deleting an element can potentially lead to reentrancy, we move all the
// elements to be erased into a temporary container before deleting them.
// This is to avoid changing the underlying container during the erase()
// call.
absl::InlinedVector<value_type, 8> elements_to_delete;
std::move(erase_start, impl_.heap_.end(),
std::back_inserter(elements_to_delete));
impl_.heap_.erase(erase_start, impl_.heap_.end());
// If no elements were removed, then the heap is still intact.
if (elements_to_delete.empty()) {
return;
}
// Repair the heap and ensure handles are pointing to the right index.
ranges::make_heap(impl_.heap_, value_comp());
for (size_t i = 0; i < size(); ++i)
SetHeapHandle(i);
// Explicitly delete elements last.
elements_to_delete.clear();
}
//////////////////////////////////////////////////////////////////////////////
// Updating.
//
// Amortized cost of O(lg size).
// Replaces the element corresponding to |handle| with a new |element|.
const_iterator Replace(size_type pos, const T& element) {
return ReplaceImpl(pos, element);
}
const_iterator Replace(size_type pos, T&& element) {
return ReplaceImpl(pos, std::move_if_noexcept(element));
}
// Versions of Replace that will accept handles and iterators.
template <typename P>
const_iterator Replace(P pos, const T& element) {
return ReplaceImpl(ToIndex(pos), element);
}
template <typename P>
const_iterator Replace(P pos, T&& element) {
return ReplaceImpl(ToIndex(pos), std::move_if_noexcept(element));
}
// Replaces the top element in the heap with the provided element.
const_iterator ReplaceTop(const T& element) {
return ReplaceTopImpl(element);
}
const_iterator ReplaceTop(T&& element) {
return ReplaceTopImpl(std::move_if_noexcept(element));
}
// Causes the object at the given location to be resorted into an appropriate
// position in the heap. To be used if the object in the heap was externally
// modified, and the heap needs to be repaired. This only works if a single
// heap element has been modified, otherwise the behaviour is undefined.
const_iterator Update(size_type pos);
template <typename P>
const_iterator Update(P pos) {
return Update(ToIndex(pos));
}
// Applies a modification function to the object at the given location, then
// repairs the heap. To be used to modify an element in the heap in-place
// while keeping the heap intact.
template <typename P, typename UnaryOperation>
const_iterator Modify(P pos, UnaryOperation unary_op) {
size_type index = ToIndex(pos);
unary_op(impl_.heap_.at(index));
return Update(index);
}
//////////////////////////////////////////////////////////////////////////////
// Access to helper functors.
const value_compare& value_comp() const { return impl_.get_value_compare(); }
const heap_handle_accessor& heap_handle_access() const {
return impl_.get_heap_handle_access();
}
//////////////////////////////////////////////////////////////////////////////
// General operations.
void swap(IntrusiveHeap& other) noexcept;
friend void swap(IntrusiveHeap& lhs, IntrusiveHeap& rhs) { lhs.swap(rhs); }
// Comparison operators. These check for exact equality. Two heaps that are
// semantically equivalent (contain the same elements, but in different
// orders) won't compare as equal using these operators.
friend bool operator==(const IntrusiveHeap& lhs, const IntrusiveHeap& rhs) {
return lhs.impl_.heap_ == rhs.impl_.heap_;
}
//////////////////////////////////////////////////////////////////////////////
// Utility functions.
// Converts iterators and handles to indices. Helpers for templated versions
// of insert/erase/Replace.
size_type ToIndex(HeapHandle handle) { return handle.index(); }
size_type ToIndex(const_iterator pos);
size_type ToIndex(const_reverse_iterator pos);
private:
// Templated version of ToIndex that lets insert/erase/Replace work with all
// integral types.
template <typename I, typename = std::enable_if_t<std::is_integral_v<I>>>
size_type ToIndex(I pos) {
return static_cast<size_type>(pos);
}
// Returns the last valid index in |heap_|.
size_type GetLastIndex() const { return impl_.heap_.size() - 1; }
// Helper functions for setting heap handles.
void SetHeapHandle(size_type i);
void ClearHeapHandle(size_type i);
HeapHandle GetHeapHandle(size_type i);
// Helpers for doing comparisons between elements inside and outside of the
// heap.
bool Less(size_type i, size_type j);
bool Less(const T& element, size_type i);
bool Less(size_type i, const T& element);
// The following function are all related to the basic heap algorithm
// underpinning this data structure. They are templated so that they work with
// both movable (U = T&&) and non-movable (U = const T&) types.
// Primitive helpers for adding removing / elements to the heap. To minimize
// moves, the heap is implemented by making a hole where an element used to
// be (or where a new element will soon be), and moving the hole around,
// before finally filling the hole or deleting the entry corresponding to the
// hole.
void MakeHole(size_type pos);
template <typename U>
void FillHole(size_type hole, U element);
void MoveHole(size_type new_hole_pos, size_type old_hole_pos);
// Moves a hold up the tree and fills it with the provided |element|. Returns
// the final index of the element.
template <typename U>
size_type MoveHoleUpAndFill(size_type hole_pos, U element);
// Moves a hole down the tree and fills it with the provided |element|. If
// |kFillWithLeaf| is true it will deterministically move the hole all the
// way down the tree, avoiding a second comparison per level, before
// potentially moving it back up the tree.
struct WithLeafElement {
static constexpr bool kIsLeafElement = true;
};
struct WithElement {
static constexpr bool kIsLeafElement = false;
};
template <typename FillElementType, typename U>
size_type MoveHoleDownAndFill(size_type hole_pos, U element);
// Implementation of Insert and Replace built on top of the MoveHole
// primitives.
template <typename U>
const_iterator InsertImpl(U element);
template <typename U>
const_iterator ReplaceImpl(size_type pos, U element);
template <typename U>
const_iterator ReplaceTopImpl(U element);
// To support comparators that may not be possible to default-construct, we
// have to store an instance of value_compare. Using this to store all
// internal state of IntrusiveHeap and using private inheritance to store
// compare lets us take advantage of an empty base class optimization to avoid
// extra space in the common case when Compare has no state.
struct Impl : private value_compare, private heap_handle_accessor {
Impl(const value_compare& value_comp,
const heap_handle_accessor& heap_handle_access)
: value_compare(value_comp), heap_handle_accessor(heap_handle_access) {}
Impl() = default;
Impl(Impl&&) = default;
Impl(const Impl&) = default;
Impl& operator=(Impl&& other) = default;
Impl& operator=(const Impl& other) = default;
const value_compare& get_value_compare() const { return *this; }
value_compare& get_value_compare() { return *this; }
const heap_handle_accessor& get_heap_handle_access() const { return *this; }
heap_handle_accessor& get_heap_handle_access() { return *this; }
// The items in the heap.
UnderlyingType heap_;
} impl_;
};
// Helper class to endow an object with internal HeapHandle storage. By deriving
// from this type you endow your class with self-owned storage for a HeapHandle.
// This is a move-only type so that the handle follows the element across moves
// and resizes of the underlying vector.
class BASE_EXPORT InternalHeapHandleStorage {
public:
InternalHeapHandleStorage();
InternalHeapHandleStorage(const InternalHeapHandleStorage&) = delete;
InternalHeapHandleStorage(InternalHeapHandleStorage&& other) noexcept;
virtual ~InternalHeapHandleStorage();
InternalHeapHandleStorage& operator=(const InternalHeapHandleStorage&) =
delete;
InternalHeapHandleStorage& operator=(
InternalHeapHandleStorage&& other) noexcept;
// Allows external clients to get a pointer to the heap handle. This allows
// them to remove the element from the heap regardless of its location.
HeapHandle* handle() const { return handle_.get(); }
// Implementation of IntrusiveHeap contract. Inlined to keep heap code as fast
// as possible.
void SetHeapHandle(HeapHandle handle) {
DCHECK(handle.IsValid());
if (handle_)
*handle_ = handle;
}
void ClearHeapHandle() {
if (handle_)
handle_->reset();
}
HeapHandle GetHeapHandle() const {
if (handle_)
return *handle_;
return HeapHandle::Invalid();
}
// Utility functions.
void swap(InternalHeapHandleStorage& other) noexcept;
friend void swap(InternalHeapHandleStorage& lhs,
InternalHeapHandleStorage& rhs) {
lhs.swap(rhs);
}
private:
std::unique_ptr<HeapHandle> handle_;
};
// Spiritually akin to a std::pair<T, std::unique_ptr<HeapHandle>>. Can be used
// to wrap arbitrary types and provide them with a HeapHandle, making them
// appropriate for use in an IntrusiveHeap. This is a move-only type.
template <typename T>
class WithHeapHandle : public InternalHeapHandleStorage {
public:
WithHeapHandle() = default;
// Allow implicit conversion of any type that T supports for ease of use with
// InstrusiveHeap constructors/insert/emplace.
template <typename U>
WithHeapHandle(U value) : value_(std::move_if_noexcept(value)) {}
WithHeapHandle(T&& value) noexcept : value_(std::move(value)) {}
// Constructor that forwards all arguments along to |value_|.
template <class... Args>
explicit WithHeapHandle(Args&&... args);
WithHeapHandle(const WithHeapHandle&) = delete;
WithHeapHandle(WithHeapHandle&& other) noexcept = default;
~WithHeapHandle() override = default;
WithHeapHandle& operator=(const WithHeapHandle&) = delete;
WithHeapHandle& operator=(WithHeapHandle&& other) = default;
T& value() LIFETIME_BOUND { return value_; }
const T& value() const LIFETIME_BOUND { return value_; }
// Utility functions.
void swap(WithHeapHandle& other) noexcept;
friend void swap(WithHeapHandle& lhs, WithHeapHandle& rhs) { lhs.swap(rhs); }
// Comparison operators, for compatibility with ordered STL containers.
friend bool operator==(const WithHeapHandle& lhs, const WithHeapHandle& rhs) {
return lhs.value_ == rhs.value_;
}
friend auto operator<=>(const WithHeapHandle& lhs,
const WithHeapHandle& rhs) {
return lhs.value_ <=> rhs.value_;
}
private:
T value_;
};
////////////////////////////////////////////////////////////////////////////////
// IMPLEMENTATION DETAILS
namespace intrusive_heap {
BASE_EXPORT inline size_t ParentIndex(size_t i) {
DCHECK_NE(0u, i);
return (i - 1) / 2;
}
BASE_EXPORT inline size_t LeftIndex(size_t i) {
return 2 * i + 1;
}
template <typename HandleType>
bool IsInvalid(const HandleType& handle) {
return !handle || !handle->IsValid();
}
BASE_EXPORT inline void CheckInvalidOrEqualTo(HeapHandle handle, size_t index) {
if (handle.IsValid())
DCHECK_EQ(index, handle.index());
}
} // namespace intrusive_heap
////////////////////////////////////////////////////////////////////////////////
// IntrusiveHeap
template <typename T, typename Compare, typename HeapHandleAccessor>
IntrusiveHeap<T, Compare, HeapHandleAccessor>::IntrusiveHeap(
const IntrusiveHeap& other)
: impl_(other.impl_) {
for (size_t i = 0; i < size(); ++i) {
SetHeapHandle(i);
}
}
template <typename T, typename Compare, typename HeapHandleAccessor>
IntrusiveHeap<T, Compare, HeapHandleAccessor>::~IntrusiveHeap() {
clear();
}
template <typename T, typename Compare, typename HeapHandleAccessor>
IntrusiveHeap<T, Compare, HeapHandleAccessor>&
IntrusiveHeap<T, Compare, HeapHandleAccessor>::operator=(
IntrusiveHeap&& other) noexcept {
clear();
impl_ = std::move(other.impl_);
return *this;
}
template <typename T, typename Compare, typename HeapHandleAccessor>
IntrusiveHeap<T, Compare, HeapHandleAccessor>&
IntrusiveHeap<T, Compare, HeapHandleAccessor>::operator=(
const IntrusiveHeap& other) {
clear();
impl_ = other.impl_;
for (size_t i = 0; i < size(); ++i) {
SetHeapHandle(i);
}
return *this;
}
template <typename T, typename Compare, typename HeapHandleAccessor>
IntrusiveHeap<T, Compare, HeapHandleAccessor>&
IntrusiveHeap<T, Compare, HeapHandleAccessor>::operator=(
std::initializer_list<value_type> ilist) {
clear();
insert_range(ilist);
}
template <typename T, typename Compare, typename HeapHandleAccessor>
void IntrusiveHeap<T, Compare, HeapHandleAccessor>::clear() {
// Make all of the handles invalid before cleaning up the heap.
for (size_type i = 0; i < size(); ++i) {
ClearHeapHandle(i);
}
// Clear the heap.
impl_.heap_.clear();
}
template <typename T, typename Compare, typename HeapHandleAccessor>
typename IntrusiveHeap<T, Compare, HeapHandleAccessor>::value_type
IntrusiveHeap<T, Compare, HeapHandleAccessor>::take(size_type pos) {
// Make a hole by taking the element out of the heap.
MakeHole(pos);
value_type val = std::move(impl_.heap_[pos]);
// If the element being taken is already the last element then the heap
// doesn't need to be repaired.
if (pos != GetLastIndex()) {
MakeHole(GetLastIndex());
// Move the hole down the heap, filling it with the current leaf at the
// very end of the heap.
MoveHoleDownAndFill<WithLeafElement>(
pos, std::move(impl_.heap_[GetLastIndex()]));
}
impl_.heap_.pop_back();
return val;
}
// This is effectively identical to "take", but it avoids an unnecessary move.
template <typename T, typename Compare, typename HeapHandleAccessor>
void IntrusiveHeap<T, Compare, HeapHandleAccessor>::erase(size_type pos) {
DCHECK_LT(pos, size());
// Make a hole by taking the element out of the heap.
MakeHole(pos);
// If the element being erased is already the last element then the heap
// doesn't need to be repaired.
if (pos != GetLastIndex()) {
MakeHole(GetLastIndex());
// Move the hole down the heap, filling it with the current leaf at the
// very end of the heap.
MoveHoleDownAndFill<WithLeafElement>(
pos, std::move_if_noexcept(impl_.heap_[GetLastIndex()]));
}
impl_.heap_.pop_back();
}
template <typename T, typename Compare, typename HeapHandleAccessor>
typename IntrusiveHeap<T, Compare, HeapHandleAccessor>::const_iterator
IntrusiveHeap<T, Compare, HeapHandleAccessor>::Update(size_type pos) {
DCHECK_LT(pos, size());
MakeHole(pos);
// Determine if we're >= parent, in which case we may need to go up.
bool child_greater_eq_parent = false;
size_type i = 0;
if (pos > 0) {
i = intrusive_heap::ParentIndex(pos);
child_greater_eq_parent = !Less(pos, i);
}
if (child_greater_eq_parent) {
i = MoveHoleUpAndFill(pos, std::move_if_noexcept(impl_.heap_[pos]));
} else {
i = MoveHoleDownAndFill<WithElement>(
pos, std::move_if_noexcept(impl_.heap_[pos]));
}
return cbegin() + i;
}
template <typename T, typename Compare, typename HeapHandleAccessor>
void IntrusiveHeap<T, Compare, HeapHandleAccessor>::swap(
IntrusiveHeap& other) noexcept {
std::swap(impl_.get_value_compare(), other.impl_.get_value_compare());
std::swap(impl_.get_heap_handle_access(),
other.impl_.get_heap_handle_access());
std::swap(impl_.heap_, other.impl_.heap_);
}
template <typename T, typename Compare, typename HeapHandleAccessor>
typename IntrusiveHeap<T, Compare, HeapHandleAccessor>::size_type
IntrusiveHeap<T, Compare, HeapHandleAccessor>::ToIndex(const_iterator pos) {
DCHECK(cbegin() <= pos);
DCHECK(pos <= cend());
if (pos == cend())
return HeapHandle::kInvalidIndex;
return pos - cbegin();
}
template <typename T, typename Compare, typename HeapHandleAccessor>
typename IntrusiveHeap<T, Compare, HeapHandleAccessor>::size_type
IntrusiveHeap<T, Compare, HeapHandleAccessor>::ToIndex(
const_reverse_iterator pos) {
DCHECK(crbegin() <= pos);
DCHECK(pos <= crend());
if (pos == crend())
return HeapHandle::kInvalidIndex;
return (pos.base() - cbegin()) - 1;
}
template <typename T, typename Compare, typename HeapHandleAccessor>
void IntrusiveHeap<T, Compare, HeapHandleAccessor>::SetHeapHandle(size_type i) {
impl_.get_heap_handle_access().SetHeapHandle(&impl_.heap_[i], HeapHandle(i));
intrusive_heap::CheckInvalidOrEqualTo(GetHeapHandle(i), i);
}
template <typename T, typename Compare, typename HeapHandleAccessor>
void IntrusiveHeap<T, Compare, HeapHandleAccessor>::ClearHeapHandle(
size_type i) {
impl_.get_heap_handle_access().ClearHeapHandle(&impl_.heap_[i]);
DCHECK(!GetHeapHandle(i).IsValid());
}
template <typename T, typename Compare, typename HeapHandleAccessor>
HeapHandle IntrusiveHeap<T, Compare, HeapHandleAccessor>::GetHeapHandle(
size_type i) {
return impl_.get_heap_handle_access().GetHeapHandle(&impl_.heap_[i]);
}
template <typename T, typename Compare, typename HeapHandleAccessor>
bool IntrusiveHeap<T, Compare, HeapHandleAccessor>::Less(size_type i,
size_type j) {
DCHECK_LT(i, size());
DCHECK_LT(j, size());
return impl_.get_value_compare()(impl_.heap_[i], impl_.heap_[j]);
}
template <typename T, typename Compare, typename HeapHandleAccessor>
bool IntrusiveHeap<T, Compare, HeapHandleAccessor>::Less(const T& element,
size_type i) {
DCHECK_LT(i, size());
return impl_.get_value_compare()(element, impl_.heap_[i]);
}
template <typename T, typename Compare, typename HeapHandleAccessor>
bool IntrusiveHeap<T, Compare, HeapHandleAccessor>::Less(size_type i,
const T& element) {
DCHECK_LT(i, size());
return impl_.get_value_compare()(impl_.heap_[i], element);
}
template <typename T, typename Compare, typename HeapHandleAccessor>
void IntrusiveHeap<T, Compare, HeapHandleAccessor>::MakeHole(size_type pos) {
DCHECK_LT(pos, size());
ClearHeapHandle(pos);
}
template <typename T, typename Compare, typename HeapHandleAccessor>
template <typename U>
void IntrusiveHeap<T, Compare, HeapHandleAccessor>::FillHole(size_type hole_pos,
U element) {
// The hole that we're filling may not yet exist. This can occur when
// inserting a new element into the heap.
DCHECK_LE(hole_pos, size());
if (hole_pos == size()) {
impl_.heap_.push_back(std::move_if_noexcept(element));
} else {
impl_.heap_[hole_pos] = std::move_if_noexcept(element);
}
SetHeapHandle(hole_pos);
}
template <typename T, typename Compare, typename HeapHandleAccessor>
void IntrusiveHeap<T, Compare, HeapHandleAccessor>::MoveHole(
size_type new_hole_pos,
size_type old_hole_pos) {
// The old hole position may be one past the end. This occurs when a new
// element is being added.
DCHECK_NE(new_hole_pos, old_hole_pos);
DCHECK_LT(new_hole_pos, size());
DCHECK_LE(old_hole_pos, size());
if (old_hole_pos == size()) {
impl_.heap_.push_back(std::move_if_noexcept(impl_.heap_[new_hole_pos]));
} else {
impl_.heap_[old_hole_pos] =
std::move_if_noexcept(impl_.heap_[new_hole_pos]);
}
SetHeapHandle(old_hole_pos);
}
template <typename T, typename Compare, typename HeapHandleAccessor>
template <typename U>
typename IntrusiveHeap<T, Compare, HeapHandleAccessor>::size_type
IntrusiveHeap<T, Compare, HeapHandleAccessor>::MoveHoleUpAndFill(
size_type hole_pos,
U element) {
// Moving 1 spot beyond the end is fine. This happens when we insert a new
// element.
DCHECK_LE(hole_pos, size());
// Stop when the element is as far up as it can go.
while (hole_pos != 0) {
// If our parent is >= to us, we can stop.
size_type parent = intrusive_heap::ParentIndex(hole_pos);
if (!Less(parent, element))
break;
MoveHole(parent, hole_pos);
hole_pos = parent;
}
FillHole(hole_pos, std::move_if_noexcept(element));
return hole_pos;
}
template <typename T, typename Compare, typename HeapHandleAccessor>
template <typename FillElementType, typename U>
typename IntrusiveHeap<T, Compare, HeapHandleAccessor>::size_type
IntrusiveHeap<T, Compare, HeapHandleAccessor>::MoveHoleDownAndFill(
size_type hole_pos,
U element) {
DCHECK_LT(hole_pos, size());
// If we're filling with a leaf, then that leaf element is about to be erased.
// We pretend that the space doesn't exist in the heap.
const size_type n = size() - (FillElementType::kIsLeafElement ? 1 : 0);
DCHECK_LT(hole_pos, n);
DCHECK(!GetHeapHandle(hole_pos).IsValid());
while (true) {
// If this spot has no children, then we've gone down as far as we can go.
size_type left = intrusive_heap::LeftIndex(hole_pos);
if (left >= n)
break;
size_type right = left + 1;
// Get the larger of the potentially two child nodes.
size_type largest = left;
if (right < n && Less(left, right))
largest = right;
// If we're not deterministically moving the element all the way down to
// become a leaf, then stop when it is >= the largest of the children.
if (!FillElementType::kIsLeafElement && !Less(element, largest))
break;
MoveHole(largest, hole_pos);
hole_pos = largest;
}
if (FillElementType::kIsLeafElement) {
// If we're filling with a leaf node we may need to bubble the leaf back up
// the tree a bit to repair the heap.
hole_pos = MoveHoleUpAndFill(hole_pos, std::move_if_noexcept(element));
} else {
FillHole(hole_pos, std::move_if_noexcept(element));
}
return hole_pos;
}
template <typename T, typename Compare, typename HeapHandleAccessor>
template <typename U>
typename IntrusiveHeap<T, Compare, HeapHandleAccessor>::const_iterator
IntrusiveHeap<T, Compare, HeapHandleAccessor>::InsertImpl(U element) {
// MoveHoleUpAndFill can tolerate the initial hole being in a slot that
// doesn't yet exist. It will be created by MoveHole by copy/move, thus
// removing the need for a default constructor.
size_type i = MoveHoleUpAndFill(size(), std::move_if_noexcept(element));
return cbegin() + static_cast<difference_type>(i);
}
template <typename T, typename Compare, typename HeapHandleAccessor>
template <typename U>
typename IntrusiveHeap<T, Compare, HeapHandleAccessor>::const_iterator
IntrusiveHeap<T, Compare, HeapHandleAccessor>::ReplaceImpl(size_type pos,
U element) {
// If we're greater than our parent we need to go up, otherwise we may need
// to go down.
MakeHole(pos);
size_type i = 0;
if (!Less(element, pos)) {
i = MoveHoleUpAndFill(pos, std::move_if_noexcept(element));
} else {
i = MoveHoleDownAndFill<WithElement>(pos, std::move_if_noexcept(element));
}
return cbegin() + static_cast<difference_type>(i);
}
template <typename T, typename Compare, typename HeapHandleAccessor>
template <typename U>
typename IntrusiveHeap<T, Compare, HeapHandleAccessor>::const_iterator
IntrusiveHeap<T, Compare, HeapHandleAccessor>::ReplaceTopImpl(U element) {
MakeHole(0u);
size_type i =
MoveHoleDownAndFill<WithElement>(0u, std::move_if_noexcept(element));
return cbegin() + static_cast<difference_type>(i);
}
////////////////////////////////////////////////////////////////////////////////
// WithHeapHandle
template <typename T>
template <class... Args>
WithHeapHandle<T>::WithHeapHandle(Args&&... args)
: value_(std::forward<Args>(args)...) {}
template <typename T>
void WithHeapHandle<T>::swap(WithHeapHandle& other) noexcept {
InternalHeapHandleStorage::swap(other);
std::swap(value_, other.value_);
}
} // namespace base
#endif // BASE_CONTAINERS_INTRUSIVE_HEAP_H_