304 lines
11 KiB
C++
304 lines
11 KiB
C++
/*
|
|
* 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, typename KeyEqual = std::equal_to<K>>
|
|
class SmallMap final {
|
|
using Map = SmallVector<std::pair<const K, V>, N>;
|
|
|
|
template <typename, typename, std::size_t, typename>
|
|
friend class SmallMap;
|
|
|
|
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();
|
|
}
|
|
|
|
// Copies or moves key-value pairs from a convertible map.
|
|
template <typename Q, typename W, std::size_t M, typename E>
|
|
SmallMap(SmallMap<Q, W, M, E> other) : map_(std::move(other.map_)) {}
|
|
|
|
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 (KeyEqual{}(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()); }
|
|
|
|
// Removes all mappings.
|
|
//
|
|
// All iterators are invalidated.
|
|
//
|
|
void clear() { map_.clear(); }
|
|
|
|
private:
|
|
iterator find(const key_type& key, iterator first) {
|
|
return std::find_if(first, end(),
|
|
[&key](const auto& pair) { return KeyEqual{}(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, typename E, std::size_t... Sizes, typename... Types>
|
|
SmallMap(InitializerList<KeyValue<K, V, E>, std::index_sequence<Sizes...>, Types...>&&)
|
|
-> SmallMap<K, V, sizeof...(Sizes), E>;
|
|
|
|
// 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, typename E>
|
|
bool operator==(const SmallMap<K, V, N, E>& lhs, const SmallMap<Q, W, M, E>& 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, typename E>
|
|
inline bool operator!=(const SmallMap<K, V, N, E>& lhs, const SmallMap<Q, W, M, E>& rhs) {
|
|
return !(lhs == rhs);
|
|
}
|
|
|
|
} // namespace android::ftl
|