xref: /aosp_15_r20/external/pigweed/pw_containers/public/pw_containers/flat_map.h (revision 61c4878ac05f98d0ceed94b57d316916de578985)
1 // Copyright 2020 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 <algorithm>
17 #include <array>
18 #include <cstddef>
19 #include <cstdint>
20 #include <iterator>
21 #include <type_traits>
22 
23 #include "pw_assert/assert.h"
24 
25 namespace pw::containers {
26 
27 // Define and use a custom Pair object. This is because std::pair does not
28 // support constexpr assignment until C++20. The assignment is needed since
29 // the array of pairs will be sorted in the constructor (if not already).
30 template <typename First, typename Second>
31 struct Pair {
32   First first;
33   Second second;
34 
35   bool operator==(const Pair& p) const {
36     return first == p.first && second == p.second;
37   }
38 
39   bool operator!=(const Pair& p) const { return !(*this == p); }
40 };
41 
42 template <typename T1, typename T2>
43 Pair(T1, T2) -> Pair<T1, T2>;
44 
45 /// A simple, fixed-size associative array with lookup by key or value.
46 ///
47 /// FlatMaps can be initialized by:
48 /// @rst
49 /// .. literalinclude:: examples/flat_map.cc
50 ///    :language: cpp
51 ///    :linenos:
52 ///    :start-after: [pw_containers-flat_map]
53 ///    :end-before: [pw_containers-flat_map]
54 /// @endrst
55 ///
56 /// The keys do not need to be sorted as the constructor will sort the items
57 /// if need be.
58 template <typename Key, typename Value, size_t kArraySize>
59 class FlatMap {
60  public:
61   using key_type = Key;
62   using mapped_type = Value;
63   using value_type = Pair<key_type, mapped_type>;
64   using pointer = value_type*;
65   using reference = value_type&;
66   using size_type = size_t;
67   using difference_type = ptrdiff_t;
68   using container_type = typename std::array<value_type, kArraySize>;
69   using iterator = typename container_type::iterator;
70   using const_iterator = typename container_type::const_iterator;
71 
72   /// A bidirectional iterator for the mapped values.
73   ///
74   /// Also an output iterator.
75   class mapped_iterator {
76    public:
77     using value_type = FlatMap::mapped_type;
78     using difference_type = std::ptrdiff_t;
79     using pointer = value_type*;
80     using reference = value_type&;
81     using iterator_category = std::bidirectional_iterator_tag;
82 
mapped_iterator()83     constexpr mapped_iterator() : current_{} {}
84 
85     constexpr mapped_iterator(const mapped_iterator& other) = default;
86     constexpr mapped_iterator& operator=(const mapped_iterator& other) =
87         default;
88 
89     constexpr reference operator*() const {
90       reference value = current_->second;
91       return value;
92     }
93 
94     constexpr pointer operator->() const { return &operator*(); }
95 
96     constexpr mapped_iterator& operator++() {
97       ++current_;
98       return *this;
99     }
100 
101     constexpr mapped_iterator operator++(int) {
102       mapped_iterator original = *this;
103       operator++();
104       return original;
105     }
106 
107     constexpr mapped_iterator& operator--() {
108       --current_;
109       return *this;
110     }
111 
112     constexpr mapped_iterator operator--(int) {
113       mapped_iterator original = *this;
114       operator--();
115       return original;
116     }
117 
118     constexpr bool operator==(const mapped_iterator& other) const {
119       return current_ == other.current_;
120     }
121 
122     constexpr bool operator!=(const mapped_iterator& other) const {
123       return !(*this == other);
124     }
125 
126    private:
127     friend class FlatMap;
128 
mapped_iterator(FlatMap::iterator current)129     constexpr mapped_iterator(FlatMap::iterator current) : current_(current) {}
130 
131     FlatMap::iterator current_;
132   };
133 
FlatMap(const std::array<value_type,kArraySize> & items)134   constexpr FlatMap(const std::array<value_type, kArraySize>& items)
135       : items_(items) {
136     ConstexprSort(items_.begin(), kArraySize);
137   }
138 
139   // Omits explicit here to support assignment-like syntax, which is common to
140   // initialize a container.
141   template <typename... Items,
142             typename = std::enable_if_t<
143                 std::conjunction_v<std::is_same<Items, value_type>...>>>
FlatMap(const Items &...items)144   constexpr FlatMap(const Items&... items) : FlatMap(std::array{items...}) {}
145 
146   FlatMap(FlatMap&) = delete;
147   FlatMap& operator=(FlatMap&) = delete;
148 
149   // Capacity.
size()150   constexpr size_type size() const { return kArraySize; }
empty()151   constexpr size_type empty() const { return size() == 0; }
max_size()152   constexpr size_type max_size() const { return kArraySize; }
153 
154   // Lookup.
155   /// Accesses a mutable mapped value.
156   ///
157   /// @pre The key must exist.
158   ///
159   /// @param[in] key The key to the mapped value.
160   ///
161   /// @returns A reference to the mapped value.
at(const key_type & key)162   constexpr mapped_type& at(const key_type& key) {
163     PW_ASSERT(end() != begin());
164     iterator it = std::lower_bound(
165         items_.begin(), items_.end(), key, IsItemKeyLessThanGivenKey);
166     const key_type& found_key = it->first;
167     PW_ASSERT(it != items_.end() && found_key == key);
168     mapped_type& found_value = it->second;
169     return found_value;
170   }
171 
172   /// Accesses a mapped value.
173   ///
174   /// @pre The key must exist.
175   ///
176   /// @param[in] key The key to the mapped value.
177   ///
178   /// @returns A const reference to the mapped value.
at(const key_type & key)179   constexpr const mapped_type& at(const key_type& key) const {
180     PW_ASSERT(end() != begin());
181     const_iterator it = std::lower_bound(
182         items_.cbegin(), items_.cend(), key, IsItemKeyLessThanGivenKey);
183     const key_type& found_key = it->first;
184     PW_ASSERT(it != items_.cend() && found_key == key);
185     const mapped_type& found_value = it->second;
186     return found_value;
187   }
188 
contains(const key_type & key)189   constexpr bool contains(const key_type& key) const {
190     return find(key) != end();
191   }
192 
find(const key_type & key)193   constexpr const_iterator find(const key_type& key) const {
194     if (end() == begin()) {
195       return end();
196     }
197 
198     const_iterator it = lower_bound(key);
199     if (it == end() || it->first != key) {
200       return end();
201     }
202     return it;
203   }
204 
lower_bound(const key_type & key)205   constexpr const_iterator lower_bound(const key_type& key) const {
206     return std::lower_bound(begin(), end(), key, IsItemKeyLessThanGivenKey);
207   }
208 
upper_bound(const key_type & key)209   constexpr const_iterator upper_bound(const key_type& key) const {
210     return std::upper_bound(
211         begin(), end(), key, [](key_type lkey, const value_type& item) {
212           return item.first > lkey;
213         });
214   }
215 
equal_range(const key_type & key)216   constexpr std::pair<const_iterator, const_iterator> equal_range(
217       const key_type& key) const {
218     if (end() == begin()) {
219       return std::make_pair(end(), end());
220     }
221 
222     return std::make_pair(lower_bound(key), upper_bound(key));
223   }
224 
225   // Iterators.
begin()226   constexpr const_iterator begin() const { return cbegin(); }
cbegin()227   constexpr const_iterator cbegin() const { return items_.cbegin(); }
end()228   constexpr const_iterator end() const { return cend(); }
cend()229   constexpr const_iterator cend() const { return items_.cend(); }
230 
231   /// Gets an iterator to the first mapped value.
232   ///
233   /// Mapped iterators iterate through the mapped values, and allow mutation of
234   /// those values (for a non-const FlatMap object).
235   ///
236   /// @returns An iterator to the first mapped value.
mapped_begin()237   constexpr mapped_iterator mapped_begin() {
238     return mapped_iterator(items_.begin());
239   }
240 
241   /// Gets an iterator to one past the last mapped value.
242   ///
243   /// Mapped iterators iterate through the mapped values, and allow mutation of
244   /// those values (for a non-const FlatMap object).
245   ///
246   /// @returns An iterator to one past the last mapped value.
mapped_end()247   constexpr mapped_iterator mapped_end() {
248     return mapped_iterator(items_.end());
249   }
250 
251  private:
252   // Simple stable insertion sort function for constexpr support.
253   // std::stable_sort is not constexpr. Should not be a problem with performance
254   // in regards to the sizes that are typically dealt with.
ConstexprSort(iterator data,size_type size)255   static constexpr void ConstexprSort(iterator data, size_type size) {
256     if (size < 2) {
257       return;
258     }
259 
260     for (iterator it = data + 1,
261                   end = data + static_cast<difference_type>(size);
262          it < end;
263          ++it) {
264       if (it->first < it[-1].first) {
265         // Rotate the value into place.
266         value_type temp = std::move(*it);
267         iterator it2 = it - 1;
268         while (true) {
269           *(it2 + 1) = std::move(*it2);
270           if (it2 == data || !(temp.first < it2[-1].first)) {
271             break;
272           }
273           --it2;
274         }
275         *it2 = std::move(temp);
276       }
277     }
278   }
279 
IsItemKeyLessThanGivenKey(const value_type & item,key_type key)280   static constexpr bool IsItemKeyLessThanGivenKey(const value_type& item,
281                                                   key_type key) {
282     const key_type& item_key = item.first;
283     return item_key < key;
284   }
285 
286   std::array<value_type, kArraySize> items_;
287 };
288 
289 template <typename K, typename V, typename... Items>
290 FlatMap(const Pair<K, V>& item1, const Items&... items)
291     -> FlatMap<K, V, 1 + sizeof...(items)>;
292 
293 }  // namespace pw::containers
294