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