123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077 |
- // Copyright 2019 The Chromium Authors. All rights reserved.
- // 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 <functional>
- #include <limits>
- #include <type_traits>
- #include <utility>
- #include <vector>
- #include "base/base_export.h"
- #include "base/check_op.h"
- #include "base/macros.h"
- #include "base/memory/ptr_util.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) {
- return lhs.index_ == rhs.index_;
- }
- friend bool operator!=(const HeapHandle& lhs, const HeapHandle& rhs) {
- return lhs.index_ != rhs.index_;
- }
- friend bool operator<(const HeapHandle& lhs, const HeapHandle& rhs) {
- return lhs.index_ < rhs.index_;
- }
- friend bool operator>(const HeapHandle& lhs, const HeapHandle& rhs) {
- return lhs.index_ > rhs.index_;
- }
- friend bool operator<=(const HeapHandle& lhs, const HeapHandle& rhs) {
- return lhs.index_ <= rhs.index_;
- }
- friend bool operator>=(const HeapHandle& lhs, const HeapHandle& rhs) {
- return lhs.index_ >= rhs.index_;
- }
- 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.
- IntrusiveHeap() = default;
- IntrusiveHeap(const value_compare& comp, const heap_handle_accessor& access)
- : impl_(comp, access) {}
- template <class InputIterator>
- IntrusiveHeap(InputIterator first,
- InputIterator last,
- const value_compare& comp = value_compare(),
- const heap_handle_accessor& access = heap_handle_accessor())
- : impl_(comp, access) {
- 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(std::begin(ilist), std::end(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_if_noexcept(value));
- }
- template <class InputIterator>
- void insert(InputIterator first, InputIterator last);
- template <typename... Args>
- const_iterator emplace(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); }
- //////////////////////////////////////////////////////////////////////////////
- // 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));
- }
- //////////////////////////////////////////////////////////////////////////////
- // 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_;
- }
- 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<I>::value>>
- 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() { return value_; }
- const T& value() const { 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 bool operator!=(const WithHeapHandle& lhs, const WithHeapHandle& rhs) {
- return lhs.value_ != rhs.value_;
- }
- friend bool operator<=(const WithHeapHandle& lhs, const WithHeapHandle& rhs) {
- return lhs.value_ <= rhs.value_;
- }
- friend bool operator<(const WithHeapHandle& lhs, const WithHeapHandle& rhs) {
- return lhs.value_ < rhs.value_;
- }
- friend bool operator>=(const WithHeapHandle& lhs, const WithHeapHandle& rhs) {
- return lhs.value_ >= rhs.value_;
- }
- friend bool 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(std::begin(ilist), std::end(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>
- template <class InputIterator>
- void IntrusiveHeap<T, Compare, HeapHandleAccessor>::insert(InputIterator first,
- InputIterator last) {
- for (auto it = first; it != last; ++it) {
- insert(value_type(*it));
- }
- }
- template <typename T, typename Compare, typename HeapHandleAccessor>
- template <typename... Args>
- typename IntrusiveHeap<T, Compare, HeapHandleAccessor>::const_iterator
- IntrusiveHeap<T, Compare, HeapHandleAccessor>::emplace(Args&&... args) {
- value_type value(std::forward<Args>(args)...);
- return InsertImpl(std::move_if_noexcept(value));
- }
- 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_t i = MoveHoleUpAndFill(size(), std::move_if_noexcept(element));
- return cbegin() + 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() + 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() + 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_
|