| /* | 
 |  * Copyright 2020 The Android Open Source Project | 
 |  * | 
 |  * Licensed under the Apache License, Version 2.0 (the "License"); | 
 |  * you may not use this file except in compliance with the License. | 
 |  * You may obtain a copy of the License at | 
 |  * | 
 |  *      http://www.apache.org/licenses/LICENSE-2.0 | 
 |  * | 
 |  * Unless required by applicable law or agreed to in writing, software | 
 |  * distributed under the License is distributed on an "AS IS" BASIS, | 
 |  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
 |  * See the License for the specific language governing permissions and | 
 |  * limitations under the License. | 
 |  */ | 
 |  | 
 | #pragma once | 
 |  | 
 | #include <ftl/initializer_list.h> | 
 | #include <ftl/small_vector.h> | 
 |  | 
 | #include <algorithm> | 
 | #include <functional> | 
 | #include <optional> | 
 | #include <type_traits> | 
 | #include <utility> | 
 |  | 
 | namespace android::ftl { | 
 |  | 
 | // Associative container with unique, unordered keys. Unlike std::unordered_map, key-value pairs are | 
 | // stored in contiguous storage for cache efficiency. The map is allocated statically until its size | 
 | // exceeds N, at which point mappings are relocated to dynamic memory. The try_emplace operation has | 
 | // a non-standard analogue try_replace that destructively emplaces. The API also defines an in-place | 
 | // counterpart to insert_or_assign: emplace_or_replace. Lookup is done not via a subscript operator, | 
 | // but immutable getters that can optionally transform the value. | 
 | // | 
 | // SmallMap<K, V, 0> unconditionally allocates on the heap. | 
 | // | 
 | // Example usage: | 
 | // | 
 | //   ftl::SmallMap<int, std::string, 3> map; | 
 | //   assert(map.empty()); | 
 | //   assert(!map.dynamic()); | 
 | // | 
 | //   map = ftl::init::map<int, std::string>(123, "abc")(-1)(42, 3u, '?'); | 
 | //   assert(map.size() == 3u); | 
 | //   assert(!map.dynamic()); | 
 | // | 
 | //   assert(map.contains(123)); | 
 | //   assert(map.get(42, [](const std::string& s) { return s.size(); }) == 3u); | 
 | // | 
 | //   const auto opt = map.get(-1); | 
 | //   assert(opt); | 
 | // | 
 | //   std::string& ref = *opt; | 
 | //   assert(ref.empty()); | 
 | //   ref = "xyz"; | 
 | // | 
 | //   map.emplace_or_replace(0, "vanilla", 2u, 3u); | 
 | //   assert(map.dynamic()); | 
 | // | 
 | //   assert(map == SmallMap(ftl::init::map(-1, "xyz")(0, "nil")(42, "???")(123, "abc"))); | 
 | // | 
 | template <typename K, typename V, std::size_t N> | 
 | class SmallMap final { | 
 |   using Map = SmallVector<std::pair<const K, V>, N>; | 
 |  | 
 |  public: | 
 |   using key_type = K; | 
 |   using mapped_type = V; | 
 |  | 
 |   using value_type = typename Map::value_type; | 
 |   using size_type = typename Map::size_type; | 
 |   using difference_type = typename Map::difference_type; | 
 |  | 
 |   using reference = typename Map::reference; | 
 |   using iterator = typename Map::iterator; | 
 |  | 
 |   using const_reference = typename Map::const_reference; | 
 |   using const_iterator = typename Map::const_iterator; | 
 |  | 
 |   // Creates an empty map. | 
 |   SmallMap() = default; | 
 |  | 
 |   // Constructs at most N key-value pairs in place by forwarding per-pair constructor arguments. | 
 |   // The template arguments K, V, and N are inferred using the deduction guide defined below. | 
 |   // The syntax for listing pairs is as follows: | 
 |   // | 
 |   //   ftl::SmallMap map = ftl::init::map<int, std::string>(123, "abc")(-1)(42, 3u, '?'); | 
 |   //   static_assert(std::is_same_v<decltype(map), ftl::SmallMap<int, std::string, 3>>); | 
 |   // | 
 |   // The types of the key and value are deduced if the first pair contains exactly two arguments: | 
 |   // | 
 |   //   ftl::SmallMap map = ftl::init::map(0, 'a')(1, 'b')(2, 'c'); | 
 |   //   static_assert(std::is_same_v<decltype(map), ftl::SmallMap<int, char, 3>>); | 
 |   // | 
 |   template <typename U, std::size_t... Sizes, typename... Types> | 
 |   SmallMap(InitializerList<U, std::index_sequence<Sizes...>, Types...>&& list) | 
 |       : map_(std::move(list)) { | 
 |     deduplicate(); | 
 |   } | 
 |  | 
 |   size_type max_size() const { return map_.max_size(); } | 
 |   size_type size() const { return map_.size(); } | 
 |   bool empty() const { return map_.empty(); } | 
 |  | 
 |   // Returns whether the map is backed by static or dynamic storage. | 
 |   bool dynamic() const { return map_.dynamic(); } | 
 |  | 
 |   iterator begin() { return map_.begin(); } | 
 |   const_iterator begin() const { return cbegin(); } | 
 |   const_iterator cbegin() const { return map_.cbegin(); } | 
 |  | 
 |   iterator end() { return map_.end(); } | 
 |   const_iterator end() const { return cend(); } | 
 |   const_iterator cend() const { return map_.cend(); } | 
 |  | 
 |   // Returns whether a mapping exists for the given key. | 
 |   bool contains(const key_type& key) const { | 
 |     return get(key, [](const mapped_type&) {}); | 
 |   } | 
 |  | 
 |   // Returns a reference to the value for the given key, or std::nullopt if the key was not found. | 
 |   // | 
 |   //   ftl::SmallMap map = ftl::init::map('a', 'A')('b', 'B')('c', 'C'); | 
 |   // | 
 |   //   const auto opt = map.get('c'); | 
 |   //   assert(opt == 'C'); | 
 |   // | 
 |   //   char d = 'd'; | 
 |   //   const auto ref = map.get('d').value_or(std::ref(d)); | 
 |   //   ref.get() = 'D'; | 
 |   //   assert(d == 'D'); | 
 |   // | 
 |   auto get(const key_type& key) const -> std::optional<std::reference_wrapper<const mapped_type>> { | 
 |     return get(key, [](const mapped_type& v) { return std::cref(v); }); | 
 |   } | 
 |  | 
 |   auto get(const key_type& key) -> std::optional<std::reference_wrapper<mapped_type>> { | 
 |     return get(key, [](mapped_type& v) { return std::ref(v); }); | 
 |   } | 
 |  | 
 |   // Returns the result R of a unary operation F on (a constant or mutable reference to) the value | 
 |   // for the given key, or std::nullopt if the key was not found. If F has a return type of void, | 
 |   // then the Boolean result indicates whether the key was found. | 
 |   // | 
 |   //   ftl::SmallMap map = ftl::init::map('a', 'x')('b', 'y')('c', 'z'); | 
 |   // | 
 |   //   assert(map.get('c', [](char c) { return std::toupper(c); }) == 'Z'); | 
 |   //   assert(map.get('c', [](char& c) { c = std::toupper(c); })); | 
 |   // | 
 |   template <typename F, typename R = std::invoke_result_t<F, const mapped_type&>> | 
 |   auto get(const key_type& key, F f) const | 
 |       -> std::conditional_t<std::is_void_v<R>, bool, std::optional<R>> { | 
 |     for (auto& [k, v] : *this) { | 
 |       if (k == key) { | 
 |         if constexpr (std::is_void_v<R>) { | 
 |           f(v); | 
 |           return true; | 
 |         } else { | 
 |           return f(v); | 
 |         } | 
 |       } | 
 |     } | 
 |  | 
 |     return {}; | 
 |   } | 
 |  | 
 |   template <typename F> | 
 |   auto get(const key_type& key, F f) { | 
 |     return std::as_const(*this).get( | 
 |         key, [&f](const mapped_type& v) { return f(const_cast<mapped_type&>(v)); }); | 
 |   } | 
 |  | 
 |   // Returns an iterator to an existing mapping for the given key, or the end() iterator otherwise. | 
 |   const_iterator find(const key_type& key) const { return const_cast<SmallMap&>(*this).find(key); } | 
 |   iterator find(const key_type& key) { return find(key, begin()); } | 
 |  | 
 |   // Inserts a mapping unless it exists. Returns an iterator to the inserted or existing mapping, | 
 |   // and whether the mapping was inserted. | 
 |   // | 
 |   // On emplace, if the map reaches its static or dynamic capacity, then all iterators are | 
 |   // invalidated. Otherwise, only the end() iterator is invalidated. | 
 |   // | 
 |   template <typename... Args> | 
 |   std::pair<iterator, bool> try_emplace(const key_type& key, Args&&... args) { | 
 |     if (const auto it = find(key); it != end()) { | 
 |       return {it, false}; | 
 |     } | 
 |  | 
 |     auto& ref = map_.emplace_back(std::piecewise_construct, std::forward_as_tuple(key), | 
 |                                   std::forward_as_tuple(std::forward<Args>(args)...)); | 
 |     return {&ref, true}; | 
 |   } | 
 |  | 
 |   // Replaces a mapping if it exists, and returns an iterator to it. Returns the end() iterator | 
 |   // otherwise. | 
 |   // | 
 |   // The value is replaced via move constructor, so type V does not need to define copy/move | 
 |   // assignment, e.g. its data members may be const. | 
 |   // | 
 |   // The arguments may directly or indirectly refer to the mapping being replaced. | 
 |   // | 
 |   // Iterators to the replaced mapping point to its replacement, and others remain valid. | 
 |   // | 
 |   template <typename... Args> | 
 |   iterator try_replace(const key_type& key, Args&&... args) { | 
 |     const auto it = find(key); | 
 |     if (it == end()) return it; | 
 |     map_.replace(it, std::piecewise_construct, std::forward_as_tuple(key), | 
 |                  std::forward_as_tuple(std::forward<Args>(args)...)); | 
 |     return it; | 
 |   } | 
 |  | 
 |   // In-place counterpart of std::unordered_map's insert_or_assign. Returns true on emplace, or | 
 |   // false on replace. | 
 |   // | 
 |   // The value is emplaced and replaced via move constructor, so type V does not need to define | 
 |   // copy/move assignment, e.g. its data members may be const. | 
 |   // | 
 |   // On emplace, if the map reaches its static or dynamic capacity, then all iterators are | 
 |   // invalidated. Otherwise, only the end() iterator is invalidated. On replace, iterators | 
 |   // to the replaced mapping point to its replacement, and others remain valid. | 
 |   // | 
 |   template <typename... Args> | 
 |   std::pair<iterator, bool> emplace_or_replace(const key_type& key, Args&&... args) { | 
 |     const auto [it, ok] = try_emplace(key, std::forward<Args>(args)...); | 
 |     if (ok) return {it, ok}; | 
 |     map_.replace(it, std::piecewise_construct, std::forward_as_tuple(key), | 
 |                  std::forward_as_tuple(std::forward<Args>(args)...)); | 
 |     return {it, ok}; | 
 |   } | 
 |  | 
 |   // Removes a mapping if it exists, and returns whether it did. | 
 |   // | 
 |   // The last() and end() iterators, as well as those to the erased mapping, are invalidated. | 
 |   // | 
 |   bool erase(const key_type& key) { return erase(key, begin()); } | 
 |  | 
 |  private: | 
 |   iterator find(const key_type& key, iterator first) { | 
 |     return std::find_if(first, end(), [&key](const auto& pair) { return pair.first == key; }); | 
 |   } | 
 |  | 
 |   bool erase(const key_type& key, iterator first) { | 
 |     const auto it = find(key, first); | 
 |     if (it == end()) return false; | 
 |     map_.unstable_erase(it); | 
 |     return true; | 
 |   } | 
 |  | 
 |   void deduplicate() { | 
 |     for (auto it = begin(); it != end();) { | 
 |       if (const auto key = it->first; ++it != end()) { | 
 |         while (erase(key, it)); | 
 |       } | 
 |     } | 
 |   } | 
 |  | 
 |   Map map_; | 
 | }; | 
 |  | 
 | // Deduction guide for in-place constructor. | 
 | template <typename K, typename V, std::size_t... Sizes, typename... Types> | 
 | SmallMap(InitializerList<KeyValue<K, V>, std::index_sequence<Sizes...>, Types...>&&) | 
 |     -> SmallMap<K, V, sizeof...(Sizes)>; | 
 |  | 
 | // Returns whether the key-value pairs of two maps are equal. | 
 | template <typename K, typename V, std::size_t N, typename Q, typename W, std::size_t M> | 
 | bool operator==(const SmallMap<K, V, N>& lhs, const SmallMap<Q, W, M>& rhs) { | 
 |   if (lhs.size() != rhs.size()) return false; | 
 |  | 
 |   for (const auto& [k, v] : lhs) { | 
 |     const auto& lv = v; | 
 |     if (!rhs.get(k, [&lv](const auto& rv) { return lv == rv; }).value_or(false)) { | 
 |       return false; | 
 |     } | 
 |   } | 
 |  | 
 |   return true; | 
 | } | 
 |  | 
 | // TODO: Remove in C++20. | 
 | template <typename K, typename V, std::size_t N, typename Q, typename W, std::size_t M> | 
 | inline bool operator!=(const SmallMap<K, V, N>& lhs, const SmallMap<Q, W, M>& rhs) { | 
 |   return !(lhs == rhs); | 
 | } | 
 |  | 
 | }  // namespace android::ftl |