xref: /aosp_15_r20/external/pigweed/pw_containers/public/pw_containers/internal/intrusive_list.h (revision 61c4878ac05f98d0ceed94b57d316916de578985)
1 // Copyright 2024 The Pigweed Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy of
5 // the License at
6 //
7 //     https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations under
13 // the License.
14 #pragma once
15 
16 #include <cstddef>
17 #include <limits>
18 #include <type_traits>
19 
20 #include "pw_containers/internal/intrusive_item.h"
21 #include "pw_containers/internal/intrusive_list_item.h"
22 
23 namespace pw::containers::internal {
24 
25 /// Generic intrusive list implementation.
26 ///
27 /// This implementation relies on the `Item` type to provide details of how to
28 /// navigate the list. It provides methods similar to `std::forward_list` and
29 /// `std::list`.
30 ///
31 /// @tparam   Item  The type used to intrusive store list pointers.
32 template <typename Item>
33 class GenericIntrusiveList {
34  public:
35   static_assert(std::is_same_v<Item, IntrusiveForwardListItem> ||
36                 std::is_same_v<Item, IntrusiveListItem>);
37 
GenericIntrusiveList()38   constexpr GenericIntrusiveList() : head_() {}
39 
40   template <typename Iterator>
GenericIntrusiveList(Iterator first,Iterator last)41   GenericIntrusiveList(Iterator first, Iterator last) : GenericIntrusiveList() {
42     insert_after(&head_, first, last);
43   }
44 
45   // Intrusive lists cannot be copied, since each Item can only be in one list.
46   GenericIntrusiveList(const GenericIntrusiveList&) = delete;
47   GenericIntrusiveList& operator=(const GenericIntrusiveList&) = delete;
48 
~GenericIntrusiveList()49   ~GenericIntrusiveList() { CheckIntrusiveContainerIsEmpty(empty()); }
50 
51   template <typename Iterator>
assign(Iterator first,Iterator last)52   void assign(Iterator first, Iterator last) {
53     clear();
54     insert_after(&head_, first, last);
55   }
56 
57   // Iterators
58 
59   /// Returns a pointer to the sentinel item.
before_begin()60   constexpr Item* before_begin() noexcept { return &head_; }
before_begin()61   constexpr const Item* before_begin() const noexcept { return &head_; }
62 
63   /// Returns a pointer to the first item.
begin()64   constexpr Item* begin() noexcept { return head_.next_; }
begin()65   constexpr const Item* begin() const noexcept { return head_.next_; }
66 
67   /// Returns a pointer to the last item.
before_end()68   Item* before_end() noexcept { return before_begin()->previous(); }
69 
70   /// Returns a pointer to the sentinel item.
end()71   constexpr Item* end() noexcept { return &head_; }
end()72   constexpr const Item* end() const noexcept { return &head_; }
73 
74   // Capacity
75 
empty()76   bool empty() const noexcept { return begin() == end(); }
77 
78   /// Returns how many items can be added.
79   ///
80   /// As an intrusive container, this is effectively unbounded.
max_size()81   constexpr size_t max_size() const noexcept {
82     return std::numeric_limits<ptrdiff_t>::max();
83   }
84 
85   // Modifiers
86 
87   /// Removes all items from the list.
clear()88   void clear() {
89     while (!empty()) {
90       erase_after(before_begin());
91     }
92   }
93 
94   /// Inserts an item into a list.
95   ///
96   /// The item given by `prev` is updated to point to the item as being next in
97   /// the list, while the item itself points to what `prev` previously pointed
98   /// to as next.
99   ///
100   /// This is O(1). The ownership of the item is not changed.
101   ///
102   /// @return The item that was added.
insert_after(Item * prev,Item & item)103   static Item* insert_after(Item* prev, Item& item) {
104     CheckIntrusiveItemIsUncontained(item.unlisted());
105     item.next_ = prev->next_;
106     item.set_previous(prev);
107     prev->next_ = &item;
108     item.next_->set_previous(&item);
109     return &item;
110   }
111 
112   /// Adds items to the list from the provided range after the given item.
113   ///
114   /// This is O(n), where "n" is the number of items in the range.
115   ///
116   /// @return The last item that was added.
117   template <typename Iterator>
insert_after(Item * prev,Iterator first,Iterator last)118   static Item* insert_after(Item* prev, Iterator first, Iterator last) {
119     for (Iterator it = first; it != last; ++it) {
120       prev = insert_after(prev, *(FromIterator(it)));
121     }
122     return prev;
123   }
124 
125   /// Removes an item from a list.
126   ///
127   /// The item after the given `item` is unlisted, and the item following it is
128   /// returned.
129   ///
130   /// This is O(1). The item is not destroyed..
erase_after(Item * item)131   static Item* erase_after(Item* item) {
132     item->next_->unlist(item);
133     return item->next_;
134   }
135 
136   /// Removes the range of items exclusively between `first` and `last`.
erase_after(Item * first,Item * last)137   static Item* erase_after(Item* first, Item* last) {
138     while (first->next_ != last) {
139       erase_after(first);
140     }
141     return last;
142   }
143 
144   /// Exchanges this list's items with the `other` list's items.
swap(GenericIntrusiveList<Item> & other)145   void swap(GenericIntrusiveList<Item>& other) {
146     Item tmp;
147     tmp.replace(head_);
148     head_.replace(other.head_);
149     other.head_.replace(tmp);
150   }
151 
152   // Operations
153 
154   /// Merges the given `other` list into this one.
155   ///
156   /// After the call, the list will be sorted according to `comp`. The sort is
157   /// stable, and equivalent items in each list will remain in the same order
158   /// relative to each other.
159   template <typename Compare>
merge(GenericIntrusiveList<Item> & other,Compare comp)160   void merge(GenericIntrusiveList<Item>& other, Compare comp) {
161     Item* prev = before_begin();
162     Item* item = begin();
163     Item* other_item = other.begin();
164     while (!other.empty()) {
165       if (item == end() || !comp(*item, *other_item)) {
166         Item* tmp = other_item;
167         other_item = other.erase_after(other.before_begin());
168         prev = insert_after(prev, *tmp);
169       } else {
170         prev = item;
171         item = item->next_;
172       }
173     }
174   }
175 
176   /// Moves the items exclusively between `first` and `last` from `other` to
177   /// after `pos` in this list.
splice_after(Item * pos,GenericIntrusiveList<Item> & other,Item * first,Item * last)178   static void splice_after(Item* pos,
179                            GenericIntrusiveList<Item>& other,
180                            Item* first,
181                            Item* last) {
182     // Return if the range is empty, unless it is from before_begin to end.
183     if (first == last && first != &other.head_) {
184       return;
185     }
186     Item* first_next = first->next_;
187     if (first_next == last) {
188       return;
189     }
190     Item* last_prev = last->previous();
191     Item* pos_next = pos->next_;
192 
193     first->next_ = last;
194     last->set_previous(first);
195 
196     pos->next_ = first_next;
197     first_next->set_previous(pos);
198 
199     pos_next->set_previous(last_prev);
200     last_prev->next_ = pos_next;
201   }
202 
203   // Removes this specific item from the list, if it is present. Finds the item
204   // by identity (address comparison) rather than value equality. Returns true
205   // if the item was removed; false if it was not present.
remove(const Item & item_to_remove)206   bool remove(const Item& item_to_remove) {
207     return remove_if(
208                [&item_to_remove](const Item& item) -> bool {
209                  return &item_to_remove == &item;
210                },
211                1) != 0;
212   }
213 
214   /// Removes any item for which the given unary predicate function `p`
215   /// evaluates to true when passed that item.
216   ///
217   /// @tparam UnaryPredicate    Function with the signature `bool(const Item&)`
218   ///
219   /// @returns The number of items removed.
220   template <typename UnaryPredicate>
221   size_t remove_if(UnaryPredicate pred,
222                    size_t max = std::numeric_limits<size_t>::max()) {
223     size_t removed = 0;
224     Item* prev = before_begin();
225     while (true) {
226       Item* item = prev->next_;
227       if (item == end()) {
228         break;
229       }
230       if (pred(*item)) {
231         erase_after(prev);
232         ++removed;
233         if (removed == max) {
234           break;
235         }
236       } else {
237         prev = item;
238       }
239     }
240     return removed;
241   }
242 
243   /// Reverses the order of items in the list.
reverse()244   void reverse() {
245     GenericIntrusiveList<Item> list;
246     while (!empty()) {
247       Item* item = begin();
248       erase_after(before_begin());
249       list.insert_after(list.before_begin(), *item);
250     }
251     splice_after(before_begin(), list, list.before_begin(), list.end());
252   }
253 
254   /// Removes consecutive ietms that are equivalent accroding to the given
255   /// binary predicate `p`, leaving only the first item in the list.
256   ///
257   /// @tparam BinaryPredicate   Function with the signature
258   ///                           `bool(const Item&, const Item&)`
259   template <typename BinaryPredicate>
unique(BinaryPredicate pred)260   size_t unique(BinaryPredicate pred) {
261     if (empty()) {
262       return 0;
263     }
264     size_t removed = 0;
265     Item* prev = begin();
266     while (true) {
267       Item* item = prev->next_;
268       if (item == end()) {
269         break;
270       }
271       if (pred(*prev, *item)) {
272         erase_after(prev);
273         ++removed;
274       } else {
275         prev = item;
276       }
277     }
278     return removed;
279   }
280 
281   /// Rearranges the items in the list such that the given comparison function
282   /// `comp` evaluates to true for each pair of successive items.
283   ///
284   /// @tparam BinaryPredicate   Function with the signature
285   ///                           `bool(const Item&, const Item&)`
286   template <typename Compare>
sort(Compare comp)287   void sort(Compare comp) {
288     Item* mid = begin();
289     bool even = true;
290     for (Item* item = begin(); item != end(); item = item->next_) {
291       even = !even;
292       if (even) {
293         mid = mid->next_;
294       }
295     }
296 
297     // Partition the list.
298     GenericIntrusiveList<Item> tmp;
299     tmp.splice_after(tmp.before_begin(), *this, before_begin(), mid);
300 
301     // Check if trivially sorted.
302     if (tmp.empty()) {
303       return;
304     }
305 
306     // Sort the sublists.
307     tmp.sort(comp);
308     sort(comp);
309 
310     // Merge the sublists.
311     merge(tmp, comp);
312   }
313 
314  private:
315   template <typename Iterator>
FromIterator(Iterator & it)316   static Item* FromIterator(Iterator& it) {
317     if constexpr (std::is_pointer<std::remove_reference_t<decltype(*it)>>()) {
318       return *it;
319     } else {
320       return &(*it);
321     }
322   }
323 
324   // Use an Item for the head pointer. This gives simpler logic for inserting
325   // elements compared to using an Item*. It also makes it possible to use
326   // &head_ for end(), rather than nullptr. This makes end() unique for each
327   // List and ensures that items already in a list cannot be added to another.
328   Item head_;
329 };
330 
331 }  // namespace pw::containers::internal
332