• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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