diff options
Diffstat (limited to 'src/utils')
-rw-r--r-- | src/utils/cpp/TARGETS | 7 | ||||
-rw-r--r-- | src/utils/cpp/back_map.hpp | 182 |
2 files changed, 189 insertions, 0 deletions
diff --git a/src/utils/cpp/TARGETS b/src/utils/cpp/TARGETS index 2a0b3f10..509aae03 100644 --- a/src/utils/cpp/TARGETS +++ b/src/utils/cpp/TARGETS @@ -113,4 +113,11 @@ , "hdrs": ["expected.hpp"] , "stage": ["src", "utils", "cpp"] } +, "back_map": + { "type": ["@", "rules", "CC", "library"] + , "name": ["back_map"] + , "hdrs": ["back_map.hpp"] + , "deps": [["@", "gsl", "", "gsl"]] + , "stage": ["src", "utils", "cpp"] + } } diff --git a/src/utils/cpp/back_map.hpp b/src/utils/cpp/back_map.hpp new file mode 100644 index 00000000..5d68c887 --- /dev/null +++ b/src/utils/cpp/back_map.hpp @@ -0,0 +1,182 @@ +// Copyright 2025 Huawei Cloud Computing Technology Co., Ltd. +// +// 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. + +#ifndef INCLUDED_SRC_UTILS_CPP_BACK_MAP_HPP +#define INCLUDED_SRC_UTILS_CPP_BACK_MAP_HPP + +#include <cstddef> +#include <functional> +#include <iterator> +#include <optional> +#include <type_traits> +#include <unordered_map> +#include <unordered_set> +#include <utility> // IWYU pragma: keep + +#include "gsl/gsl" + +/// \brief Backmap a container of Values to Keys using a given Converter, and +/// provide an interface for quick lookup of values by keys. +/// \tparam TKey Result of conversion. Stored by value, std::hash<TKey> +/// MUST be available. +/// \tparam TValue Source data for conversion. Stored by pointer, the +/// source container MUST stay alive. +template <typename TKey, typename TValue> +class BackMap final { + public: + template <typename T> + using IsKeyWithError = std::bool_constant<std::is_convertible_v<TKey, T> and + std::is_convertible_v<T, bool>>; + + /// \brief Converter from TValue to TKey. The return type may be different + /// from TKey, in such a case Converter is considered a function that DOES + /// return TKey, but may fail and return std::nullopt or an unexpected<T>. + /// TResult must be either the same type as TKey, or support conversions + /// from TKey and to bool. + template <typename TResult> + requires(std::is_same_v<TResult, TKey> or + IsKeyWithError<TResult>::value) + using Converter = std::function<TResult(TValue const&)>; + + /// \brief Create a BackMap by iterating over container and applying + /// Converter. + /// \param container Container to iterate over. begin() and end() methods + /// must be available. + /// \param converter Converter to apply to elements of the container. + /// \return BackMap on success or std::nullopt on failure. In particular, if + /// any exception is thrown from internal containers or converter returns an + /// error, std::nullopt is returned. + template <typename TContainer, typename TResult> + [[nodiscard]] static auto Make(TContainer const* const container, + Converter<TResult> const& converter) noexcept + -> std::optional<BackMap> { + if (container == nullptr or converter == nullptr) { + return std::nullopt; + } + auto const hasher = std::hash<TKey>{}; + auto const size = std::distance(container->begin(), container->end()); + + std::unordered_set<TKey> keys; + std::unordered_map<std::size_t, gsl::not_null<TValue const*>> mapping; + try { + keys.reserve(size); + mapping.reserve(size); + + for (auto const& value : *container) { + std::optional<TKey> key = BackMap::Convert(converter, value); + if (not key.has_value()) { + return std::nullopt; + } + std::size_t const hash = std::invoke(hasher, *key); + if (not mapping.contains(hash)) { + keys.emplace(*std::move(key)); + mapping.insert_or_assign(hash, &value); + } + } + } catch (...) { + return std::nullopt; + } + return BackMap(std::move(keys), std::move(mapping)); + } + + template <typename TContainer, typename TConverter> + requires(std::is_invocable_v<TConverter, TValue const&>) + [[nodiscard]] static auto Make(TContainer const* const container, + TConverter const& converter) noexcept + -> std::optional<BackMap> { + using TResult = std::invoke_result_t<TConverter, TValue const&>; + return Make<TContainer, TResult>(container, converter); + } + + /// \brief Obtain all available keys. + [[nodiscard]] auto GetKeys() const noexcept + -> std::unordered_set<TKey> const& { + return keys_; + } + + /// \brief Obtain a pointer to the value corresponding to the given key. + /// Copy free. + /// \return If the key is known, a pointer to the value is returned, + /// otherwise std::nullopt. + [[nodiscard]] auto GetReference(TKey const& key) const noexcept + -> std::optional<gsl::not_null<TValue const*>> { + auto const hash = std::invoke(std::hash<TKey>{}, key); + if (auto it = mapping_.find(hash); it != mapping_.end()) { + return it->second; + } + return std::nullopt; + } + + /// \brief Obtain the set of values corresponding to given keys. If a key + /// isn't known to the container, it is ignored. Perform deep copy of + /// referenced values. + [[nodiscard]] auto GetValues(std::unordered_set<TKey> const& keys) + const noexcept -> std::unordered_set<TValue> { + std::unordered_set<TValue> result; + result.reserve(keys.size()); + for (auto const& key : keys) { + if (auto value = GetReference(key)) { + result.emplace(*value.value()); + } + } + return result; + } + + using Iterable = std::unordered_map<gsl::not_null<TKey const*>, + gsl::not_null<TValue const*>>; + + /// \brief Obtain an iterable key-value set where values correspond to the + /// given keys. If a key isn't known to the container, it is ignored. Copy + /// free. + [[nodiscard]] auto IterateReferences( + std::unordered_set<TKey> const* keys) const noexcept -> Iterable { + Iterable result; + result.reserve(keys->size()); + + for (auto const& key : *keys) { + if (auto value = GetReference(key)) { + result.insert_or_assign(&key, value.value()); + } + } + return result; + } + + private: + std::unordered_set<TKey> keys_; + std::unordered_map<std::size_t, gsl::not_null<TValue const*>> mapping_; + + explicit BackMap( + std::unordered_set<TKey> keys, + std::unordered_map<std::size_t, gsl::not_null<TValue const*>> + mapping) noexcept + : keys_{std::move(keys)}, mapping_{std::move(mapping)} {} + + template <typename TResult> + [[nodiscard]] static auto Convert(Converter<TResult> const& converter, + TValue const& value) noexcept + -> std::optional<TKey> { + if constexpr (not std::is_same_v<TKey, TResult>) { + TResult converted = std::invoke(converter, value); + if (not static_cast<bool>(converted)) { + return std::nullopt; + } + return *std::move(converted); + } + else { + return std::invoke(converter, value); + } + } +}; + +#endif // INCLUDED_SRC_UTILS_CPP_BACK_MAP_HPP |