diff --git a/core/src/eigen_types.hh b/core/src/eigen_types.hh
index 0d1410e65e46132113168b458fec53fe94bc33d9..89c90f06a002ddccf925389b887ecca0804b9879 100644
--- a/core/src/eigen_types.hh
+++ b/core/src/eigen_types.hh
@@ -33,9 +33,11 @@ typedef Eigen::Matrix<double,16,16> EMat16;
typedef Eigen::Matrix<Real,3,1> EVec3;
typedef Eigen::Matrix<Real,4,1> EVec4;
+typedef Eigen::Matrix<Real,Eigen::Dynamic,1> EVecX;
typedef Eigen::Matrix<Real,1,3> ERVec3;
typedef Eigen::Matrix<Real,1,4> ERVec4;
+typedef Eigen::Matrix<Real,1,Eigen::Dynamic> ERVecX;
// some special matrices used at various locations
typedef Eigen::Matrix<Real,Eigen::Dynamic,3> EMatX3;
diff --git a/modelling/pymod/export_pocket_finder.cc b/modelling/pymod/export_pocket_finder.cc
index b2e5e683fe8358f1bbb71c37efd5396cd8d83c3a..1f9482deb5c78bad4ebe4cd904279776e781e29c 100644
--- a/modelling/pymod/export_pocket_finder.cc
+++ b/modelling/pymod/export_pocket_finder.cc
@@ -27,6 +27,7 @@ using namespace boost::python;
boost::shared_ptr<PocketQuery> WrapInitPositions(const geom::Vec3List& positions,
const String& identifier) {
+
boost::shared_ptr<PocketQuery> ptr(new PocketQuery(positions, identifier));
return ptr;
}
@@ -43,16 +44,56 @@ boost::shared_ptr<PocketQuery> WrapInitQueries(const list& queries) {
}
+boost::python::list WrapFindPockets(const PocketQuery& query,
+ const geom::Vec3List& positions,
+ Real coverage_thresh,
+ Real distance_thresh) {
+
+ std::vector<PocketMatch> v_result = FindPockets(query, positions,
+ coverage_thresh,
+ distance_thresh);
+ list return_list;
+ for(std::vector<PocketMatch>::iterator it = v_result.begin();
+ it != v_result.end(); ++it) {
+ return_list.append(*it);
+ }
+ return return_list;
+}
+
+
+boost::python::list WrapGetAlignment(const PocketMatch& match) {
+
+ std::vector<std::pair<int,int> > aln = match.aln;
+ list return_list;
+ for(std::vector<std::pair<int,int> >::iterator it = aln.begin();
+ it != aln.end(); ++it) {
+ return_list.append(boost::python::make_tuple(it->first, it->second));
+ }
+ return return_list;
+}
+
+
void export_pocket_finder() {
class_<PocketQuery>("PocketQuery", no_init)
.def("__init__", make_constructor(&WrapInitPositions))
.def("__init__", make_constructor(&WrapInitQueries))
- .def("GetPositions", &PocketQuery::GetPositions, return_value_policy<copy_const_reference>(), (arg("query_idx")))
- .def("GetIdentifiers", &PocketQuery::GetIdentifiers, return_value_policy<copy_const_reference>())
+ .def("GetPositions", &PocketQuery::GetPositions,
+ return_value_policy<copy_const_reference>(), (arg("query_idx")))
+ .def("GetIdentifiers", &PocketQuery::GetIdentifiers,
+ return_value_policy<copy_const_reference>())
.def("GetN", &PocketQuery::GetN)
+ .def("Save", &PocketQuery::Save, (arg("filename")))
+ .def("Load", &PocketQuery::Load, (arg("filename"))).staticmethod("Load")
;
+ class_<PocketMatch>("PocketMatch", no_init)
+ .def_readonly("query_idx", &PocketMatch::query_idx)
+ .def_readonly("mat", &PocketMatch::mat)
+ .add_property("alignment", &WrapGetAlignment)
+ ;
- def("PocketFinder", &promod3::modelling::PocketFinder, (arg("query"), arg("target_positions")));
+ def("FindPockets", &WrapFindPockets, (arg("query"), arg("target_positions"),
+ arg("coverage_thresh")=0.5,
+ arg("distance_thresh")=1.0));
}
diff --git a/modelling/src/CMakeLists.txt b/modelling/src/CMakeLists.txt
index 6b874482860120b32aedd7555c1c01c99e0a89ed..dffc797cca3fdd98d0ad8b0cba8f9678f14a1408 100644
--- a/modelling/src/CMakeLists.txt
+++ b/modelling/src/CMakeLists.txt
@@ -42,9 +42,7 @@ set(MODELLING_HEADERS
sidechain_reconstructor.hh
sidechain_env_listener.hh
pocket_finder.hh
- hopscotch_map.h
- hopscotch_hash.h
- hopscotch_growth_policy.h
+ robin_hood.h
)
module(NAME modelling
diff --git a/modelling/src/hopscotch_growth_policy.h b/modelling/src/hopscotch_growth_policy.h
deleted file mode 100644
index 08b2ffd926e94c185f8c8cfefe557d6fe8df481f..0000000000000000000000000000000000000000
--- a/modelling/src/hopscotch_growth_policy.h
+++ /dev/null
@@ -1,301 +0,0 @@
-//////////////////////////////////////////////////////
-// SOURCE: https://github.com/Tessil/hopscotch-map //
-// COMMIT: 4d0cccd3b41294f843925e495117919a53aa61a0 //
-// WE ASSUME THAT NO UPDATES ARE REQUIRED //
-//////////////////////////////////////////////////////
-
-/**
- * MIT License
- *
- * Copyright (c) 2018 Tessil
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-#ifndef TSL_HOPSCOTCH_GROWTH_POLICY_H
-#define TSL_HOPSCOTCH_GROWTH_POLICY_H
-
-
-#include <algorithm>
-#include <array>
-#include <climits>
-#include <cmath>
-#include <cstddef>
-#include <iterator>
-#include <limits>
-#include <ratio>
-#include <stdexcept>
-
-
-namespace tsl {
-namespace hh {
-
-/**
- * Grow the hash table by a factor of GrowthFactor keeping the bucket count to a power of two. It allows
- * the table to use a mask operation instead of a modulo operation to map a hash to a bucket.
- *
- * GrowthFactor must be a power of two >= 2.
- */
-template<std::size_t GrowthFactor>
-class power_of_two_growth_policy {
-public:
- /**
- * Called on the hash table creation and on rehash. The number of buckets for the table is passed in parameter.
- * This number is a minimum, the policy may update this value with a higher value if needed (but not lower).
- *
- * If 0 is given, min_bucket_count_in_out must still be 0 after the policy creation and
- * bucket_for_hash must always return 0 in this case.
- */
- explicit power_of_two_growth_policy(std::size_t& min_bucket_count_in_out) {
- if(min_bucket_count_in_out > max_bucket_count()) {
- throw std::length_error("The hash table exceeds its maxmimum size.");
- }
-
- if(min_bucket_count_in_out > 0) {
- min_bucket_count_in_out = round_up_to_power_of_two(min_bucket_count_in_out);
- m_mask = min_bucket_count_in_out - 1;
- }
- else {
- m_mask = 0;
- }
- }
-
- /**
- * Return the bucket [0, bucket_count()) to which the hash belongs.
- * If bucket_count() is 0, it must always return 0.
- */
- std::size_t bucket_for_hash(std::size_t hash) const noexcept {
- return hash & m_mask;
- }
-
- /**
- * Return the bucket count to use when the bucket array grows on rehash.
- */
- std::size_t next_bucket_count() const {
- if((m_mask + 1) > max_bucket_count() / GrowthFactor) {
- throw std::length_error("The hash table exceeds its maxmimum size.");
- }
-
- return (m_mask + 1) * GrowthFactor;
- }
-
- /**
- * Return the maximum number of buckets supported by the policy.
- */
- std::size_t max_bucket_count() const {
- // Largest power of two.
- return (std::numeric_limits<std::size_t>::max() / 2) + 1;
- }
-
- /**
- * Reset the growth policy as if it was created with a bucket count of 0.
- * After a clear, the policy must always return 0 when bucket_for_hash is called.
- */
- void clear() noexcept {
- m_mask = 0;
- }
-
-private:
- static std::size_t round_up_to_power_of_two(std::size_t value) {
- if(is_power_of_two(value)) {
- return value;
- }
-
- if(value == 0) {
- return 1;
- }
-
- --value;
- for(std::size_t i = 1; i < sizeof(std::size_t) * CHAR_BIT; i *= 2) {
- value |= value >> i;
- }
-
- return value + 1;
- }
-
- static constexpr bool is_power_of_two(std::size_t value) {
- return value != 0 && (value & (value - 1)) == 0;
- }
-
-private:
- static_assert(is_power_of_two(GrowthFactor) && GrowthFactor >= 2, "GrowthFactor must be a power of two >= 2.");
-
- std::size_t m_mask;
-};
-
-
-/**
- * Grow the hash table by GrowthFactor::num / GrowthFactor::den and use a modulo to map a hash
- * to a bucket. Slower but it can be useful if you want a slower growth.
- */
-template<class GrowthFactor = std::ratio<3, 2>>
-class mod_growth_policy {
-public:
- explicit mod_growth_policy(std::size_t& min_bucket_count_in_out) {
- if(min_bucket_count_in_out > max_bucket_count()) {
- throw std::length_error("The hash table exceeds its maxmimum size.");
- }
-
- if(min_bucket_count_in_out > 0) {
- m_mod = min_bucket_count_in_out;
- }
- else {
- m_mod = 1;
- }
- }
-
- std::size_t bucket_for_hash(std::size_t hash) const noexcept {
- return hash % m_mod;
- }
-
- std::size_t next_bucket_count() const {
- if(m_mod == max_bucket_count()) {
- throw std::length_error("The hash table exceeds its maxmimum size.");
- }
-
- const double next_bucket_count = std::ceil(double(m_mod) * REHASH_SIZE_MULTIPLICATION_FACTOR);
- if(!std::isnormal(next_bucket_count)) {
- throw std::length_error("The hash table exceeds its maxmimum size.");
- }
-
- if(next_bucket_count > double(max_bucket_count())) {
- return max_bucket_count();
- }
- else {
- return std::size_t(next_bucket_count);
- }
- }
-
- std::size_t max_bucket_count() const {
- return MAX_BUCKET_COUNT;
- }
-
- void clear() noexcept {
- m_mod = 1;
- }
-
-private:
- static constexpr double REHASH_SIZE_MULTIPLICATION_FACTOR = 1.0 * GrowthFactor::num / GrowthFactor::den;
- static const std::size_t MAX_BUCKET_COUNT =
- std::size_t(double(
- std::numeric_limits<std::size_t>::max() / REHASH_SIZE_MULTIPLICATION_FACTOR
- ));
-
- static_assert(REHASH_SIZE_MULTIPLICATION_FACTOR >= 1.1, "Growth factor should be >= 1.1.");
-
- std::size_t m_mod;
-};
-
-
-
-namespace detail {
-
-static constexpr const std::array<std::size_t, 40> PRIMES = {{
- 1ul, 5ul, 17ul, 29ul, 37ul, 53ul, 67ul, 79ul, 97ul, 131ul, 193ul, 257ul, 389ul, 521ul, 769ul, 1031ul,
- 1543ul, 2053ul, 3079ul, 6151ul, 12289ul, 24593ul, 49157ul, 98317ul, 196613ul, 393241ul, 786433ul,
- 1572869ul, 3145739ul, 6291469ul, 12582917ul, 25165843ul, 50331653ul, 100663319ul, 201326611ul,
- 402653189ul, 805306457ul, 1610612741ul, 3221225473ul, 4294967291ul
-}};
-
-template<unsigned int IPrime>
-static constexpr std::size_t mod(std::size_t hash) { return hash % PRIMES[IPrime]; }
-
-// MOD_PRIME[iprime](hash) returns hash % PRIMES[iprime]. This table allows for faster modulo as the
-// compiler can optimize the modulo code better with a constant known at the compilation.
-static constexpr const std::array<std::size_t(*)(std::size_t), 40> MOD_PRIME = {{
- &mod<0>, &mod<1>, &mod<2>, &mod<3>, &mod<4>, &mod<5>, &mod<6>, &mod<7>, &mod<8>, &mod<9>, &mod<10>,
- &mod<11>, &mod<12>, &mod<13>, &mod<14>, &mod<15>, &mod<16>, &mod<17>, &mod<18>, &mod<19>, &mod<20>,
- &mod<21>, &mod<22>, &mod<23>, &mod<24>, &mod<25>, &mod<26>, &mod<27>, &mod<28>, &mod<29>, &mod<30>,
- &mod<31>, &mod<32>, &mod<33>, &mod<34>, &mod<35>, &mod<36>, &mod<37> , &mod<38>, &mod<39>
-}};
-
-}
-
-/**
- * Grow the hash table by using prime numbers as bucket count. Slower than tsl::hh::power_of_two_growth_policy in
- * general but will probably distribute the values around better in the buckets with a poor hash function.
- *
- * To allow the compiler to optimize the modulo operation, a lookup table is used with constant primes numbers.
- *
- * With a switch the code would look like:
- * \code
- * switch(iprime) { // iprime is the current prime of the hash table
- * case 0: hash % 5ul;
- * break;
- * case 1: hash % 17ul;
- * break;
- * case 2: hash % 29ul;
- * break;
- * ...
- * }
- * \endcode
- *
- * Due to the constant variable in the modulo the compiler is able to optimize the operation
- * by a series of multiplications, substractions and shifts.
- *
- * The 'hash % 5' could become something like 'hash - (hash * 0xCCCCCCCD) >> 34) * 5' in a 64 bits environement.
- */
-class prime_growth_policy {
-public:
- explicit prime_growth_policy(std::size_t& min_bucket_count_in_out) {
- auto it_prime = std::lower_bound(detail::PRIMES.begin(),
- detail::PRIMES.end(), min_bucket_count_in_out);
- if(it_prime == detail::PRIMES.end()) {
- throw std::length_error("The hash table exceeds its maxmimum size.");
- }
-
- m_iprime = static_cast<unsigned int>(std::distance(detail::PRIMES.begin(), it_prime));
- if(min_bucket_count_in_out > 0) {
- min_bucket_count_in_out = *it_prime;
- }
- else {
- min_bucket_count_in_out = 0;
- }
- }
-
- std::size_t bucket_for_hash(std::size_t hash) const noexcept {
- return detail::MOD_PRIME[m_iprime](hash);
- }
-
- std::size_t next_bucket_count() const {
- if(m_iprime + 1 >= detail::PRIMES.size()) {
- throw std::length_error("The hash table exceeds its maxmimum size.");
- }
-
- return detail::PRIMES[m_iprime + 1];
- }
-
- std::size_t max_bucket_count() const {
- return detail::PRIMES.back();
- }
-
- void clear() noexcept {
- m_iprime = 0;
- }
-
-private:
- unsigned int m_iprime;
-
- static_assert(std::numeric_limits<decltype(m_iprime)>::max() >= detail::PRIMES.size(),
- "The type of m_iprime is not big enough.");
-};
-
-}
-}
-
-#endif
diff --git a/modelling/src/hopscotch_hash.h b/modelling/src/hopscotch_hash.h
deleted file mode 100644
index 30fb635f594b8a9f4ace81181b0d9767a2366480..0000000000000000000000000000000000000000
--- a/modelling/src/hopscotch_hash.h
+++ /dev/null
@@ -1,1828 +0,0 @@
-//////////////////////////////////////////////////////
-// SOURCE: https://github.com/Tessil/hopscotch-map //
-// COMMIT: 4d0cccd3b41294f843925e495117919a53aa61a0 //
-// WE ASSUME THAT NO UPDATES ARE REQUIRED //
-//////////////////////////////////////////////////////
-
-/**
- * MIT License
- *
- * Copyright (c) 2017 Tessil
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-#ifndef TSL_HOPSCOTCH_HASH_H
-#define TSL_HOPSCOTCH_HASH_H
-
-
-#include <algorithm>
-#include <cassert>
-#include <cmath>
-#include <cstddef>
-#include <cstdint>
-#include <exception>
-#include <functional>
-#include <initializer_list>
-#include <iterator>
-#include <limits>
-#include <memory>
-#include <stdexcept>
-#include <tuple>
-#include <type_traits>
-#include <utility>
-#include <vector>
-#include "hopscotch_growth_policy.h"
-
-
-
-#if (defined(__GNUC__) && (__GNUC__ == 4) && (__GNUC_MINOR__ < 9))
-# define TSL_HH_NO_RANGE_ERASE_WITH_CONST_ITERATOR
-#endif
-
-
-/*
- * Only activate tsl_hh_assert if TSL_DEBUG is defined.
- * This way we avoid the performance hit when NDEBUG is not defined with assert as tsl_hh_assert is used a lot
- * (people usually compile with "-O3" and not "-O3 -DNDEBUG").
- */
-#ifdef TSL_DEBUG
-# define tsl_hh_assert(expr) assert(expr)
-#else
-# define tsl_hh_assert(expr) (static_cast<void>(0))
-#endif
-
-
-namespace tsl {
-
-namespace detail_hopscotch_hash {
-
-
-template<typename T>
-struct make_void {
- using type = void;
-};
-
-
-template<typename T, typename = void>
-struct has_is_transparent : std::false_type {
-};
-
-template<typename T>
-struct has_is_transparent<T, typename make_void<typename T::is_transparent>::type> : std::true_type {
-};
-
-
-template<typename T, typename = void>
-struct has_key_compare : std::false_type {
-};
-
-template<typename T>
-struct has_key_compare<T, typename make_void<typename T::key_compare>::type> : std::true_type {
-};
-
-
-template<typename U>
-struct is_power_of_two_policy: std::false_type {
-};
-
-template<std::size_t GrowthFactor>
-struct is_power_of_two_policy<tsl::hh::power_of_two_growth_policy<GrowthFactor>>: std::true_type {
-};
-
-
-
-
-
-/*
- * smallest_type_for_min_bits::type returns the smallest type that can fit MinBits.
- */
-static const std::size_t SMALLEST_TYPE_MAX_BITS_SUPPORTED = 64;
-template<unsigned int MinBits, typename Enable = void>
-class smallest_type_for_min_bits {
-};
-
-template<unsigned int MinBits>
-class smallest_type_for_min_bits<MinBits, typename std::enable_if<(MinBits > 0) && (MinBits <= 8)>::type> {
-public:
- using type = std::uint_least8_t;
-};
-
-template<unsigned int MinBits>
-class smallest_type_for_min_bits<MinBits, typename std::enable_if<(MinBits > 8) && (MinBits <= 16)>::type> {
-public:
- using type = std::uint_least16_t;
-};
-
-template<unsigned int MinBits>
-class smallest_type_for_min_bits<MinBits, typename std::enable_if<(MinBits > 16) && (MinBits <= 32)>::type> {
-public:
- using type = std::uint_least32_t;
-};
-
-template<unsigned int MinBits>
-class smallest_type_for_min_bits<MinBits, typename std::enable_if<(MinBits > 32) && (MinBits <= 64)>::type> {
-public:
- using type = std::uint_least64_t;
-};
-
-
-
-/*
- * Each bucket may store up to three elements:
- * - An aligned storage to store a value_type object with placement-new.
- * - An (optional) hash of the value in the bucket.
- * - An unsigned integer of type neighborhood_bitmap used to tell us which buckets in the neighborhood of the
- * current bucket contain a value with a hash belonging to the current bucket.
- *
- * For a bucket 'bct', a bit 'i' (counting from 0 and from the least significant bit to the most significant)
- * set to 1 means that the bucket 'bct + i' contains a value with a hash belonging to bucket 'bct'.
- * The bits used for that, start from the third least significant bit.
- * The two least significant bits are reserved:
- * - The least significant bit is set to 1 if there is a value in the bucket storage.
- * - The second least significant bit is set to 1 if there is an overflow. More than NeighborhoodSize values
- * give the same hash, all overflow values are stored in the m_overflow_elements list of the map.
- *
- * Details regarding hopscotch hashing an its implementation can be found here:
- * https://tessil.github.io/2016/08/29/hopscotch-hashing.html
- */
-static const std::size_t NB_RESERVED_BITS_IN_NEIGHBORHOOD = 2;
-
-
-using truncated_hash_type = std::uint_least32_t;
-
-/**
- * Helper class that stores a truncated hash if StoreHash is true and nothing otherwise.
- */
-template<bool StoreHash>
-class hopscotch_bucket_hash {
-public:
- bool bucket_hash_equal(std::size_t /*hash*/) const noexcept {
- return true;
- }
-
- truncated_hash_type truncated_bucket_hash() const noexcept {
- return 0;
- }
-
-protected:
- void copy_hash(const hopscotch_bucket_hash& ) noexcept {
- }
-
- void set_hash(truncated_hash_type /*hash*/) noexcept {
- }
-};
-
-template<>
-class hopscotch_bucket_hash<true> {
-public:
- bool bucket_hash_equal(std::size_t hash) const noexcept {
- return m_hash == truncated_hash_type(hash);
- }
-
- truncated_hash_type truncated_bucket_hash() const noexcept {
- return m_hash;
- }
-
-protected:
- void copy_hash(const hopscotch_bucket_hash& bucket) noexcept {
- m_hash = bucket.m_hash;
- }
-
- void set_hash(truncated_hash_type hash) noexcept {
- m_hash = hash;
- }
-
-private:
- truncated_hash_type m_hash;
-};
-
-
-template<typename ValueType, unsigned int NeighborhoodSize, bool StoreHash>
-class hopscotch_bucket: public hopscotch_bucket_hash<StoreHash> {
-private:
- static const std::size_t MIN_NEIGHBORHOOD_SIZE = 4;
- static const std::size_t MAX_NEIGHBORHOOD_SIZE = SMALLEST_TYPE_MAX_BITS_SUPPORTED - NB_RESERVED_BITS_IN_NEIGHBORHOOD;
-
-
- static_assert(NeighborhoodSize >= 4, "NeighborhoodSize should be >= 4.");
- // We can't put a variable in the message, ensure coherence
- static_assert(MIN_NEIGHBORHOOD_SIZE == 4, "");
-
- static_assert(NeighborhoodSize <= 62, "NeighborhoodSize should be <= 62.");
- // We can't put a variable in the message, ensure coherence
- static_assert(MAX_NEIGHBORHOOD_SIZE == 62, "");
-
-
- static_assert(!StoreHash || NeighborhoodSize <= 30,
- "NeighborhoodSize should be <= 30 if StoreHash is true.");
- // We can't put a variable in the message, ensure coherence
- static_assert(MAX_NEIGHBORHOOD_SIZE - 32 == 30, "");
-
- using bucket_hash = hopscotch_bucket_hash<StoreHash>;
-
-public:
- using value_type = ValueType;
- using neighborhood_bitmap =
- typename smallest_type_for_min_bits<NeighborhoodSize + NB_RESERVED_BITS_IN_NEIGHBORHOOD>::type;
-
-
- hopscotch_bucket() noexcept: bucket_hash(), m_neighborhood_infos(0) {
- tsl_hh_assert(empty());
- }
-
-
- hopscotch_bucket(const hopscotch_bucket& bucket)
- noexcept(std::is_nothrow_copy_constructible<value_type>::value): bucket_hash(bucket),
- m_neighborhood_infos(0)
- {
- if(!bucket.empty()) {
- ::new (static_cast<void*>(std::addressof(m_value))) value_type(bucket.value());
- }
-
- m_neighborhood_infos = bucket.m_neighborhood_infos;
- }
-
- hopscotch_bucket(hopscotch_bucket&& bucket)
- noexcept(std::is_nothrow_move_constructible<value_type>::value) : bucket_hash(std::move(bucket)),
- m_neighborhood_infos(0)
- {
- if(!bucket.empty()) {
- ::new (static_cast<void*>(std::addressof(m_value))) value_type(std::move(bucket.value()));
- }
-
- m_neighborhood_infos = bucket.m_neighborhood_infos;
- }
-
- hopscotch_bucket& operator=(const hopscotch_bucket& bucket)
- noexcept(std::is_nothrow_copy_constructible<value_type>::value)
- {
- if(this != &bucket) {
- remove_value();
-
- bucket_hash::operator=(bucket);
- if(!bucket.empty()) {
- ::new (static_cast<void*>(std::addressof(m_value))) value_type(bucket.value());
- }
-
- m_neighborhood_infos = bucket.m_neighborhood_infos;
- }
-
- return *this;
- }
-
- hopscotch_bucket& operator=(hopscotch_bucket&& ) = delete;
-
- ~hopscotch_bucket() noexcept {
- if(!empty()) {
- destroy_value();
- }
- }
-
- neighborhood_bitmap neighborhood_infos() const noexcept {
- return neighborhood_bitmap(m_neighborhood_infos >> NB_RESERVED_BITS_IN_NEIGHBORHOOD);
- }
-
- void set_overflow(bool has_overflow) noexcept {
- if(has_overflow) {
- m_neighborhood_infos = neighborhood_bitmap(m_neighborhood_infos | 2);
- }
- else {
- m_neighborhood_infos = neighborhood_bitmap(m_neighborhood_infos & ~2);
- }
- }
-
- bool has_overflow() const noexcept {
- return (m_neighborhood_infos & 2) != 0;
- }
-
- bool empty() const noexcept {
- return (m_neighborhood_infos & 1) == 0;
- }
-
- void toggle_neighbor_presence(std::size_t ineighbor) noexcept {
- tsl_hh_assert(ineighbor <= NeighborhoodSize);
- m_neighborhood_infos = neighborhood_bitmap(
- m_neighborhood_infos ^ (1ull << (ineighbor + NB_RESERVED_BITS_IN_NEIGHBORHOOD)));
- }
-
- bool check_neighbor_presence(std::size_t ineighbor) const noexcept {
- tsl_hh_assert(ineighbor <= NeighborhoodSize);
- if(((m_neighborhood_infos >> (ineighbor + NB_RESERVED_BITS_IN_NEIGHBORHOOD)) & 1) == 1) {
- return true;
- }
-
- return false;
- }
-
- value_type& value() noexcept {
- tsl_hh_assert(!empty());
- return *reinterpret_cast<value_type*>(std::addressof(m_value));
- }
-
- const value_type& value() const noexcept {
- tsl_hh_assert(!empty());
- return *reinterpret_cast<const value_type*>(std::addressof(m_value));
- }
-
- template<typename... Args>
- void set_value_of_empty_bucket(truncated_hash_type hash, Args&&... value_type_args) {
- tsl_hh_assert(empty());
-
- ::new (static_cast<void*>(std::addressof(m_value))) value_type(std::forward<Args>(value_type_args)...);
- set_empty(false);
- this->set_hash(hash);
- }
-
- void swap_value_into_empty_bucket(hopscotch_bucket& empty_bucket) {
- tsl_hh_assert(empty_bucket.empty());
- if(!empty()) {
- ::new (static_cast<void*>(std::addressof(empty_bucket.m_value))) value_type(std::move(value()));
- empty_bucket.copy_hash(*this);
- empty_bucket.set_empty(false);
-
- destroy_value();
- set_empty(true);
- }
- }
-
- void remove_value() noexcept {
- if(!empty()) {
- destroy_value();
- set_empty(true);
- }
- }
-
- void clear() noexcept {
- if(!empty()) {
- destroy_value();
- }
-
- m_neighborhood_infos = 0;
- tsl_hh_assert(empty());
- }
-
- static truncated_hash_type truncate_hash(std::size_t hash) noexcept {
- return truncated_hash_type(hash);
- }
-
-private:
- void set_empty(bool is_empty) noexcept {
- if(is_empty) {
- m_neighborhood_infos = neighborhood_bitmap(m_neighborhood_infos & ~1);
- }
- else {
- m_neighborhood_infos = neighborhood_bitmap(m_neighborhood_infos | 1);
- }
- }
-
- void destroy_value() noexcept {
- tsl_hh_assert(!empty());
- value().~value_type();
- }
-
-private:
- using storage = typename std::aligned_storage<sizeof(value_type), alignof(value_type)>::type;
-
- neighborhood_bitmap m_neighborhood_infos;
- storage m_value;
-};
-
-
-/**
- * Internal common class used by (b)hopscotch_map and (b)hopscotch_set.
- *
- * ValueType is what will be stored by hopscotch_hash (usually std::pair<Key, T> for a map and Key for a set).
- *
- * KeySelect should be a FunctionObject which takes a ValueType in parameter and returns a reference to the key.
- *
- * ValueSelect should be a FunctionObject which takes a ValueType in parameter and returns a reference to the value.
- * ValueSelect should be void if there is no value (in a set for example).
- *
- * OverflowContainer will be used as containers for overflown elements. Usually it should be a list<ValueType>
- * or a set<Key>/map<Key, T>.
- */
-template<class ValueType,
- class KeySelect,
- class ValueSelect,
- class Hash,
- class KeyEqual,
- class Allocator,
- unsigned int NeighborhoodSize,
- bool StoreHash,
- class GrowthPolicy,
- class OverflowContainer>
-class hopscotch_hash: private Hash, private KeyEqual, private GrowthPolicy {
-private:
- template<typename U>
- using has_mapped_type = typename std::integral_constant<bool, !std::is_same<U, void>::value>;
-
- static_assert(noexcept(std::declval<GrowthPolicy>().bucket_for_hash(std::size_t(0))), "GrowthPolicy::bucket_for_hash must be noexcept.");
- static_assert(noexcept(std::declval<GrowthPolicy>().clear()), "GrowthPolicy::clear must be noexcept.");
-
-public:
- template<bool IsConst>
- class hopscotch_iterator;
-
- using key_type = typename KeySelect::key_type;
- using value_type = ValueType;
- using size_type = std::size_t;
- using difference_type = std::ptrdiff_t;
- using hasher = Hash;
- using key_equal = KeyEqual;
- using allocator_type = Allocator;
- using reference = value_type&;
- using const_reference = const value_type&;
- using pointer = value_type*;
- using const_pointer = const value_type*;
- using iterator = hopscotch_iterator<false>;
- using const_iterator = hopscotch_iterator<true>;
-
-private:
- using hopscotch_bucket = tsl::detail_hopscotch_hash::hopscotch_bucket<ValueType, NeighborhoodSize, StoreHash>;
- using neighborhood_bitmap = typename hopscotch_bucket::neighborhood_bitmap;
-
- using buckets_allocator = typename std::allocator_traits<allocator_type>::template rebind_alloc<hopscotch_bucket>;
- using buckets_container_type = std::vector<hopscotch_bucket, buckets_allocator>;
-
- using overflow_container_type = OverflowContainer;
-
- static_assert(std::is_same<typename overflow_container_type::value_type, ValueType>::value,
- "OverflowContainer should have ValueType as type.");
-
- static_assert(std::is_same<typename overflow_container_type::allocator_type, Allocator>::value,
- "Invalid allocator, not the same type as the value_type.");
-
-
- using iterator_buckets = typename buckets_container_type::iterator;
- using const_iterator_buckets = typename buckets_container_type::const_iterator;
-
- using iterator_overflow = typename overflow_container_type::iterator;
- using const_iterator_overflow = typename overflow_container_type::const_iterator;
-
-public:
- /**
- * The `operator*()` and `operator->()` methods return a const reference and const pointer respectively to the
- * stored value type.
- *
- * In case of a map, to get a modifiable reference to the value associated to a key (the `.second` in the
- * stored pair), you have to call `value()`.
- */
- template<bool IsConst>
- class hopscotch_iterator {
- friend class hopscotch_hash;
- private:
- using iterator_bucket = typename std::conditional<IsConst,
- typename hopscotch_hash::const_iterator_buckets,
- typename hopscotch_hash::iterator_buckets>::type;
- using iterator_overflow = typename std::conditional<IsConst,
- typename hopscotch_hash::const_iterator_overflow,
- typename hopscotch_hash::iterator_overflow>::type;
-
-
- hopscotch_iterator(iterator_bucket buckets_iterator, iterator_bucket buckets_end_iterator,
- iterator_overflow overflow_iterator) noexcept :
- m_buckets_iterator(buckets_iterator), m_buckets_end_iterator(buckets_end_iterator),
- m_overflow_iterator(overflow_iterator)
- {
- }
-
- public:
- using iterator_category = std::forward_iterator_tag;
- using value_type = const typename hopscotch_hash::value_type;
- using difference_type = std::ptrdiff_t;
- using reference = value_type&;
- using pointer = value_type*;
-
-
- hopscotch_iterator() noexcept {
- }
-
- // Copy constructor from iterator to const_iterator.
- template<bool TIsConst = IsConst, typename std::enable_if<TIsConst>::type* = nullptr>
- hopscotch_iterator(const hopscotch_iterator<!TIsConst>& other) noexcept :
- m_buckets_iterator(other.m_buckets_iterator), m_buckets_end_iterator(other.m_buckets_end_iterator),
- m_overflow_iterator(other.m_overflow_iterator)
- {
- }
-
- hopscotch_iterator(const hopscotch_iterator& other) = default;
- hopscotch_iterator(hopscotch_iterator&& other) = default;
- hopscotch_iterator& operator=(const hopscotch_iterator& other) = default;
- hopscotch_iterator& operator=(hopscotch_iterator&& other) = default;
-
- const typename hopscotch_hash::key_type& key() const {
- if(m_buckets_iterator != m_buckets_end_iterator) {
- return KeySelect()(m_buckets_iterator->value());
- }
-
- return KeySelect()(*m_overflow_iterator);
- }
-
- template<class U = ValueSelect, typename std::enable_if<has_mapped_type<U>::value>::type* = nullptr>
- typename std::conditional<
- IsConst,
- const typename U::value_type&,
- typename U::value_type&>::type value() const
- {
- if(m_buckets_iterator != m_buckets_end_iterator) {
- return U()(m_buckets_iterator->value());
- }
-
- return U()(*m_overflow_iterator);
- }
-
- reference operator*() const {
- if(m_buckets_iterator != m_buckets_end_iterator) {
- return m_buckets_iterator->value();
- }
-
- return *m_overflow_iterator;
- }
-
- pointer operator->() const {
- if(m_buckets_iterator != m_buckets_end_iterator) {
- return std::addressof(m_buckets_iterator->value());
- }
-
- return std::addressof(*m_overflow_iterator);
- }
-
- hopscotch_iterator& operator++() {
- if(m_buckets_iterator == m_buckets_end_iterator) {
- ++m_overflow_iterator;
- return *this;
- }
-
- do {
- ++m_buckets_iterator;
- } while(m_buckets_iterator != m_buckets_end_iterator && m_buckets_iterator->empty());
-
- return *this;
- }
-
- hopscotch_iterator operator++(int) {
- hopscotch_iterator tmp(*this);
- ++*this;
-
- return tmp;
- }
-
- friend bool operator==(const hopscotch_iterator& lhs, const hopscotch_iterator& rhs) {
- return lhs.m_buckets_iterator == rhs.m_buckets_iterator &&
- lhs.m_overflow_iterator == rhs.m_overflow_iterator;
- }
-
- friend bool operator!=(const hopscotch_iterator& lhs, const hopscotch_iterator& rhs) {
- return !(lhs == rhs);
- }
-
- private:
- iterator_bucket m_buckets_iterator;
- iterator_bucket m_buckets_end_iterator;
- iterator_overflow m_overflow_iterator;
- };
-
-public:
- template<class OC = OverflowContainer, typename std::enable_if<!has_key_compare<OC>::value>::type* = nullptr>
- hopscotch_hash(size_type bucket_count,
- const Hash& hash,
- const KeyEqual& equal,
- const Allocator& alloc,
- float max_load_factor) : Hash(hash),
- KeyEqual(equal),
- GrowthPolicy(bucket_count),
- m_buckets_data(alloc),
- m_overflow_elements(alloc),
- m_buckets(static_empty_bucket_ptr()),
- m_nb_elements(0)
- {
- if(bucket_count > max_bucket_count()) {
- throw std::length_error("The map exceeds its maxmimum size.");
- }
-
- if(bucket_count > 0) {
- static_assert(NeighborhoodSize - 1 > 0, "");
-
- // Can't directly construct with the appropriate size in the initializer
- // as m_buckets_data(bucket_count, alloc) is not supported by GCC 4.8
- m_buckets_data.resize(bucket_count + NeighborhoodSize - 1);
- m_buckets = m_buckets_data.data();
- }
-
-
- this->max_load_factor(max_load_factor);
-
-
- // Check in the constructor instead of outside of a function to avoi compilation issues
- // when value_type is not complete.
- static_assert(std::is_nothrow_move_constructible<value_type>::value ||
- std::is_copy_constructible<value_type>::value,
- "value_type must be either copy constructible or nothrow move constructible.");
- }
-
- template<class OC = OverflowContainer, typename std::enable_if<has_key_compare<OC>::value>::type* = nullptr>
- hopscotch_hash(size_type bucket_count,
- const Hash& hash,
- const KeyEqual& equal,
- const Allocator& alloc,
- float max_load_factor,
- const typename OC::key_compare& comp) : Hash(hash),
- KeyEqual(equal),
- GrowthPolicy(bucket_count),
- m_buckets_data(alloc),
- m_overflow_elements(comp, alloc),
- m_buckets(static_empty_bucket_ptr()),
- m_nb_elements(0)
- {
-
- if(bucket_count > max_bucket_count()) {
- throw std::length_error("The map exceeds its maxmimum size.");
- }
-
- if(bucket_count > 0) {
- static_assert(NeighborhoodSize - 1 > 0, "");
-
- // Can't directly construct with the appropriate size in the initializer
- // as m_buckets_data(bucket_count, alloc) is not supported by GCC 4.8
- m_buckets_data.resize(bucket_count + NeighborhoodSize - 1);
- m_buckets = m_buckets_data.data();
- }
-
-
- this->max_load_factor(max_load_factor);
-
-
- // Check in the constructor instead of outside of a function to avoi compilation issues
- // when value_type is not complete.
- static_assert(std::is_nothrow_move_constructible<value_type>::value ||
- std::is_copy_constructible<value_type>::value,
- "value_type must be either copy constructible or nothrow move constructible.");
- }
-
- hopscotch_hash(const hopscotch_hash& other):
- Hash(other),
- KeyEqual(other),
- GrowthPolicy(other),
- m_buckets_data(other.m_buckets_data),
- m_overflow_elements(other.m_overflow_elements),
- m_buckets(m_buckets_data.empty()?static_empty_bucket_ptr():
- m_buckets_data.data()),
- m_nb_elements(other.m_nb_elements),
- m_max_load_factor(other.m_max_load_factor),
- m_max_load_threshold_rehash(other.m_max_load_threshold_rehash),
- m_min_load_threshold_rehash(other.m_min_load_threshold_rehash)
- {
- }
-
- hopscotch_hash(hopscotch_hash&& other)
- noexcept(
- std::is_nothrow_move_constructible<Hash>::value &&
- std::is_nothrow_move_constructible<KeyEqual>::value &&
- std::is_nothrow_move_constructible<GrowthPolicy>::value &&
- std::is_nothrow_move_constructible<buckets_container_type>::value &&
- std::is_nothrow_move_constructible<overflow_container_type>::value
- ):
- Hash(std::move(static_cast<Hash&>(other))),
- KeyEqual(std::move(static_cast<KeyEqual&>(other))),
- GrowthPolicy(std::move(static_cast<GrowthPolicy&>(other))),
- m_buckets_data(std::move(other.m_buckets_data)),
- m_overflow_elements(std::move(other.m_overflow_elements)),
- m_buckets(m_buckets_data.empty()?static_empty_bucket_ptr():
- m_buckets_data.data()),
- m_nb_elements(other.m_nb_elements),
- m_max_load_factor(other.m_max_load_factor),
- m_max_load_threshold_rehash(other.m_max_load_threshold_rehash),
- m_min_load_threshold_rehash(other.m_min_load_threshold_rehash)
- {
- other.GrowthPolicy::clear();
- other.m_buckets_data.clear();
- other.m_overflow_elements.clear();
- other.m_buckets = static_empty_bucket_ptr();
- other.m_nb_elements = 0;
- other.m_max_load_threshold_rehash = 0;
- other.m_min_load_threshold_rehash = 0;
- }
-
- hopscotch_hash& operator=(const hopscotch_hash& other) {
- if(&other != this) {
- Hash::operator=(other);
- KeyEqual::operator=(other);
- GrowthPolicy::operator=(other);
-
- m_buckets_data = other.m_buckets_data;
- m_overflow_elements = other.m_overflow_elements;
- m_buckets = m_buckets_data.empty()?static_empty_bucket_ptr():
- m_buckets_data.data();
- m_nb_elements = other.m_nb_elements;
- m_max_load_factor = other.m_max_load_factor;
- m_max_load_threshold_rehash = other.m_max_load_threshold_rehash;
- m_min_load_threshold_rehash = other.m_min_load_threshold_rehash;
- }
-
- return *this;
- }
-
- hopscotch_hash& operator=(hopscotch_hash&& other) {
- other.swap(*this);
- other.clear();
-
- return *this;
- }
-
- allocator_type get_allocator() const {
- return m_buckets_data.get_allocator();
- }
-
-
- /*
- * Iterators
- */
- iterator begin() noexcept {
- auto begin = m_buckets_data.begin();
- while(begin != m_buckets_data.end() && begin->empty()) {
- ++begin;
- }
-
- return iterator(begin, m_buckets_data.end(), m_overflow_elements.begin());
- }
-
- const_iterator begin() const noexcept {
- return cbegin();
- }
-
- const_iterator cbegin() const noexcept {
- auto begin = m_buckets_data.cbegin();
- while(begin != m_buckets_data.cend() && begin->empty()) {
- ++begin;
- }
-
- return const_iterator(begin, m_buckets_data.cend(), m_overflow_elements.cbegin());
- }
-
- iterator end() noexcept {
- return iterator(m_buckets_data.end(), m_buckets_data.end(), m_overflow_elements.end());
- }
-
- const_iterator end() const noexcept {
- return cend();
- }
-
- const_iterator cend() const noexcept {
- return const_iterator(m_buckets_data.cend(), m_buckets_data.cend(), m_overflow_elements.cend());
- }
-
-
- /*
- * Capacity
- */
- bool empty() const noexcept {
- return m_nb_elements == 0;
- }
-
- size_type size() const noexcept {
- return m_nb_elements;
- }
-
- size_type max_size() const noexcept {
- return m_buckets_data.max_size();
- }
-
- /*
- * Modifiers
- */
- void clear() noexcept {
- for(auto& bucket: m_buckets_data) {
- bucket.clear();
- }
-
- m_overflow_elements.clear();
- m_nb_elements = 0;
- }
-
-
- std::pair<iterator, bool> insert(const value_type& value) {
- return insert_impl(value);
- }
-
- template<class P, typename std::enable_if<std::is_constructible<value_type, P&&>::value>::type* = nullptr>
- std::pair<iterator, bool> insert(P&& value) {
- return insert_impl(value_type(std::forward<P>(value)));
- }
-
- std::pair<iterator, bool> insert(value_type&& value) {
- return insert_impl(std::move(value));
- }
-
-
- iterator insert(const_iterator hint, const value_type& value) {
- if(hint != cend() && compare_keys(KeySelect()(*hint), KeySelect()(value))) {
- return mutable_iterator(hint);
- }
-
- return insert(value).first;
- }
-
- template<class P, typename std::enable_if<std::is_constructible<value_type, P&&>::value>::type* = nullptr>
- iterator insert(const_iterator hint, P&& value) {
- return emplace_hint(hint, std::forward<P>(value));
- }
-
- iterator insert(const_iterator hint, value_type&& value) {
- if(hint != cend() && compare_keys(KeySelect()(*hint), KeySelect()(value))) {
- return mutable_iterator(hint);
- }
-
- return insert(std::move(value)).first;
- }
-
-
- template<class InputIt>
- void insert(InputIt first, InputIt last) {
- if(std::is_base_of<std::forward_iterator_tag,
- typename std::iterator_traits<InputIt>::iterator_category>::value)
- {
- const auto nb_elements_insert = std::distance(first, last);
- const std::size_t nb_elements_in_buckets = m_nb_elements - m_overflow_elements.size();
- const std::size_t nb_free_buckets = m_max_load_threshold_rehash - nb_elements_in_buckets;
- tsl_hh_assert(m_nb_elements >= m_overflow_elements.size());
- tsl_hh_assert(m_max_load_threshold_rehash >= nb_elements_in_buckets);
-
- if(nb_elements_insert > 0 && nb_free_buckets < std::size_t(nb_elements_insert)) {
- reserve(nb_elements_in_buckets + std::size_t(nb_elements_insert));
- }
- }
-
- for(; first != last; ++first) {
- insert(*first);
- }
- }
-
-
- template<class M>
- std::pair<iterator, bool> insert_or_assign(const key_type& k, M&& obj) {
- return insert_or_assign_impl(k, std::forward<M>(obj));
- }
-
- template<class M>
- std::pair<iterator, bool> insert_or_assign(key_type&& k, M&& obj) {
- return insert_or_assign_impl(std::move(k), std::forward<M>(obj));
- }
-
-
- template<class M>
- iterator insert_or_assign(const_iterator hint, const key_type& k, M&& obj) {
- if(hint != cend() && compare_keys(KeySelect()(*hint), k)) {
- auto it = mutable_iterator(hint);
- it.value() = std::forward<M>(obj);
-
- return it;
- }
-
- return insert_or_assign(k, std::forward<M>(obj)).first;
- }
-
- template<class M>
- iterator insert_or_assign(const_iterator hint, key_type&& k, M&& obj) {
- if(hint != cend() && compare_keys(KeySelect()(*hint), k)) {
- auto it = mutable_iterator(hint);
- it.value() = std::forward<M>(obj);
-
- return it;
- }
-
- return insert_or_assign(std::move(k), std::forward<M>(obj)).first;
- }
-
-
- template<class... Args>
- std::pair<iterator, bool> emplace(Args&&... args) {
- return insert(value_type(std::forward<Args>(args)...));
- }
-
- template<class... Args>
- iterator emplace_hint(const_iterator hint, Args&&... args) {
- return insert(hint, value_type(std::forward<Args>(args)...));
- }
-
- template<class... Args>
- std::pair<iterator, bool> try_emplace(const key_type& k, Args&&... args) {
- return try_emplace_impl(k, std::forward<Args>(args)...);
- }
-
- template<class... Args>
- std::pair<iterator, bool> try_emplace(key_type&& k, Args&&... args) {
- return try_emplace_impl(std::move(k), std::forward<Args>(args)...);
- }
-
- template<class... Args>
- iterator try_emplace(const_iterator hint, const key_type& k, Args&&... args) {
- if(hint != cend() && compare_keys(KeySelect()(*hint), k)) {
- return mutable_iterator(hint);
- }
-
- return try_emplace(k, std::forward<Args>(args)...).first;
- }
-
- template<class... Args>
- iterator try_emplace(const_iterator hint, key_type&& k, Args&&... args) {
- if(hint != cend() && compare_keys(KeySelect()(*hint), k)) {
- return mutable_iterator(hint);
- }
-
- return try_emplace(std::move(k), std::forward<Args>(args)...).first;
- }
-
-
- /**
- * Here to avoid `template<class K> size_type erase(const K& key)` being used when
- * we use an iterator instead of a const_iterator.
- */
- iterator erase(iterator pos) {
- return erase(const_iterator(pos));
- }
-
- iterator erase(const_iterator pos) {
- const std::size_t ibucket_for_hash = bucket_for_hash(hash_key(pos.key()));
-
- if(pos.m_buckets_iterator != pos.m_buckets_end_iterator) {
- auto it_bucket = m_buckets_data.begin() + std::distance(m_buckets_data.cbegin(), pos.m_buckets_iterator);
- erase_from_bucket(*it_bucket, ibucket_for_hash);
-
- return ++iterator(it_bucket, m_buckets_data.end(), m_overflow_elements.begin());
- }
- else {
- auto it_next_overflow = erase_from_overflow(pos.m_overflow_iterator, ibucket_for_hash);
- return iterator(m_buckets_data.end(), m_buckets_data.end(), it_next_overflow);
- }
- }
-
- iterator erase(const_iterator first, const_iterator last) {
- if(first == last) {
- return mutable_iterator(first);
- }
-
- auto to_delete = erase(first);
- while(to_delete != last) {
- to_delete = erase(to_delete);
- }
-
- return to_delete;
- }
-
- template<class K>
- size_type erase(const K& key) {
- return erase(key, hash_key(key));
- }
-
- template<class K>
- size_type erase(const K& key, std::size_t hash) {
- const std::size_t ibucket_for_hash = bucket_for_hash(hash);
-
- hopscotch_bucket* bucket_found = find_in_buckets(key, hash, m_buckets + ibucket_for_hash);
- if(bucket_found != nullptr) {
- erase_from_bucket(*bucket_found, ibucket_for_hash);
-
- return 1;
- }
-
- if(m_buckets[ibucket_for_hash].has_overflow()) {
- auto it_overflow = find_in_overflow(key);
- if(it_overflow != m_overflow_elements.end()) {
- erase_from_overflow(it_overflow, ibucket_for_hash);
-
- return 1;
- }
- }
-
- return 0;
- }
-
- void swap(hopscotch_hash& other) {
- using std::swap;
-
- swap(static_cast<Hash&>(*this), static_cast<Hash&>(other));
- swap(static_cast<KeyEqual&>(*this), static_cast<KeyEqual&>(other));
- swap(static_cast<GrowthPolicy&>(*this), static_cast<GrowthPolicy&>(other));
- swap(m_buckets_data, other.m_buckets_data);
- swap(m_overflow_elements, other.m_overflow_elements);
- swap(m_buckets, other.m_buckets);
- swap(m_nb_elements, other.m_nb_elements);
- swap(m_max_load_factor, other.m_max_load_factor);
- swap(m_max_load_threshold_rehash, other.m_max_load_threshold_rehash);
- swap(m_min_load_threshold_rehash, other.m_min_load_threshold_rehash);
- }
-
-
- /*
- * Lookup
- */
- template<class K, class U = ValueSelect, typename std::enable_if<has_mapped_type<U>::value>::type* = nullptr>
- typename U::value_type& at(const K& key) {
- return at(key, hash_key(key));
- }
-
- template<class K, class U = ValueSelect, typename std::enable_if<has_mapped_type<U>::value>::type* = nullptr>
- typename U::value_type& at(const K& key, std::size_t hash) {
- return const_cast<typename U::value_type&>(static_cast<const hopscotch_hash*>(this)->at(key, hash));
- }
-
-
- template<class K, class U = ValueSelect, typename std::enable_if<has_mapped_type<U>::value>::type* = nullptr>
- const typename U::value_type& at(const K& key) const {
- return at(key, hash_key(key));
- }
-
- template<class K, class U = ValueSelect, typename std::enable_if<has_mapped_type<U>::value>::type* = nullptr>
- const typename U::value_type& at(const K& key, std::size_t hash) const {
- using T = typename U::value_type;
-
- const T* value = find_value_impl(key, hash, m_buckets + bucket_for_hash(hash));
- if(value == nullptr) {
- throw std::out_of_range("Couldn't find key.");
- }
- else {
- return *value;
- }
- }
-
-
- template<class K, class U = ValueSelect, typename std::enable_if<has_mapped_type<U>::value>::type* = nullptr>
- typename U::value_type& operator[](K&& key) {
- using T = typename U::value_type;
-
- const std::size_t hash = hash_key(key);
- const std::size_t ibucket_for_hash = bucket_for_hash(hash);
-
- T* value = find_value_impl(key, hash, m_buckets + ibucket_for_hash);
- if(value != nullptr) {
- return *value;
- }
- else {
- return insert_value(ibucket_for_hash, hash, std::piecewise_construct,
- std::forward_as_tuple(std::forward<K>(key)),
- std::forward_as_tuple()).first.value();
- }
- }
-
-
- template<class K>
- size_type count(const K& key) const {
- return count(key, hash_key(key));
- }
-
- template<class K>
- size_type count(const K& key, std::size_t hash) const {
- return count_impl(key, hash, m_buckets + bucket_for_hash(hash));
- }
-
-
- template<class K>
- iterator find(const K& key) {
- return find(key, hash_key(key));
- }
-
- template<class K>
- iterator find(const K& key, std::size_t hash) {
- return find_impl(key, hash, m_buckets + bucket_for_hash(hash));
- }
-
-
- template<class K>
- const_iterator find(const K& key) const {
- return find(key, hash_key(key));
- }
-
- template<class K>
- const_iterator find(const K& key, std::size_t hash) const {
- return find_impl(key, hash, m_buckets + bucket_for_hash(hash));
- }
-
-
- template<class K>
- bool contains(const K& key) const {
- return contains(key, hash_key(key));
- }
-
- template<class K>
- bool contains(const K& key, std::size_t hash) const {
- return count(key, hash) != 0;
- }
-
-
- template<class K>
- std::pair<iterator, iterator> equal_range(const K& key) {
- return equal_range(key, hash_key(key));
- }
-
- template<class K>
- std::pair<iterator, iterator> equal_range(const K& key, std::size_t hash) {
- iterator it = find(key, hash);
- return std::make_pair(it, (it == end())?it:std::next(it));
- }
-
-
- template<class K>
- std::pair<const_iterator, const_iterator> equal_range(const K& key) const {
- return equal_range(key, hash_key(key));
- }
-
- template<class K>
- std::pair<const_iterator, const_iterator> equal_range(const K& key, std::size_t hash) const {
- const_iterator it = find(key, hash);
- return std::make_pair(it, (it == cend())?it:std::next(it));
- }
-
- /*
- * Bucket interface
- */
- size_type bucket_count() const {
- /*
- * So that the last bucket can have NeighborhoodSize neighbors, the size of the bucket array is a little
- * bigger than the real number of buckets when not empty.
- * We could use some of the buckets at the beginning, but it is faster this way as we avoid extra checks.
- */
- if(m_buckets_data.empty()) {
- return 0;
- }
-
- return m_buckets_data.size() - NeighborhoodSize + 1;
- }
-
- size_type max_bucket_count() const {
- const std::size_t max_bucket_count = std::min(GrowthPolicy::max_bucket_count(), m_buckets_data.max_size());
- return max_bucket_count - NeighborhoodSize + 1;
- }
-
-
- /*
- * Hash policy
- */
- float load_factor() const {
- if(bucket_count() == 0) {
- return 0;
- }
-
- return float(m_nb_elements)/float(bucket_count());
- }
-
- float max_load_factor() const {
- return m_max_load_factor;
- }
-
- void max_load_factor(float ml) {
- m_max_load_factor = std::max(0.1f, std::min(ml, 0.95f));
- m_max_load_threshold_rehash = size_type(float(bucket_count())*m_max_load_factor);
- m_min_load_threshold_rehash = size_type(float(bucket_count())*MIN_LOAD_FACTOR_FOR_REHASH);
- }
-
- void rehash(size_type count_) {
- count_ = std::max(count_, size_type(std::ceil(float(size())/max_load_factor())));
- rehash_impl(count_);
- }
-
- void reserve(size_type count_) {
- rehash(size_type(std::ceil(float(count_)/max_load_factor())));
- }
-
-
- /*
- * Observers
- */
- hasher hash_function() const {
- return static_cast<const Hash&>(*this);
- }
-
- key_equal key_eq() const {
- return static_cast<const KeyEqual&>(*this);
- }
-
- /*
- * Other
- */
- iterator mutable_iterator(const_iterator pos) {
- if(pos.m_buckets_iterator != pos.m_buckets_end_iterator) {
- // Get a non-const iterator
- auto it = m_buckets_data.begin() + std::distance(m_buckets_data.cbegin(), pos.m_buckets_iterator);
- return iterator(it, m_buckets_data.end(), m_overflow_elements.begin());
- }
- else {
- // Get a non-const iterator
- auto it = mutable_overflow_iterator(pos.m_overflow_iterator);
- return iterator(m_buckets_data.end(), m_buckets_data.end(), it);
- }
- }
-
- size_type overflow_size() const noexcept {
- return m_overflow_elements.size();
- }
-
- template<class U = OverflowContainer, typename std::enable_if<has_key_compare<U>::value>::type* = nullptr>
- typename U::key_compare key_comp() const {
- return m_overflow_elements.key_comp();
- }
-
-
-private:
- template<class K>
- std::size_t hash_key(const K& key) const {
- return Hash::operator()(key);
- }
-
- template<class K1, class K2>
- bool compare_keys(const K1& key1, const K2& key2) const {
- return KeyEqual::operator()(key1, key2);
- }
-
- std::size_t bucket_for_hash(std::size_t hash) const {
- const std::size_t bucket = GrowthPolicy::bucket_for_hash(hash);
- tsl_hh_assert(bucket < m_buckets_data.size() || (bucket == 0 && m_buckets_data.empty()));
-
- return bucket;
- }
-
- template<typename U = value_type,
- typename std::enable_if<std::is_nothrow_move_constructible<U>::value>::type* = nullptr>
- void rehash_impl(size_type count_) {
- hopscotch_hash new_map = new_hopscotch_hash(count_);
-
- if(!m_overflow_elements.empty()) {
- new_map.m_overflow_elements.swap(m_overflow_elements);
- new_map.m_nb_elements += new_map.m_overflow_elements.size();
-
- for(const value_type& value : new_map.m_overflow_elements) {
- const std::size_t ibucket_for_hash = new_map.bucket_for_hash(new_map.hash_key(KeySelect()(value)));
- new_map.m_buckets[ibucket_for_hash].set_overflow(true);
- }
- }
-
- try {
- const bool use_stored_hash = USE_STORED_HASH_ON_REHASH(new_map.bucket_count());
- for(auto it_bucket = m_buckets_data.begin(); it_bucket != m_buckets_data.end(); ++it_bucket) {
- if(it_bucket->empty()) {
- continue;
- }
-
- const std::size_t hash = use_stored_hash?
- it_bucket->truncated_bucket_hash():
- new_map.hash_key(KeySelect()(it_bucket->value()));
- const std::size_t ibucket_for_hash = new_map.bucket_for_hash(hash);
-
- new_map.insert_value(ibucket_for_hash, hash, std::move(it_bucket->value()));
-
-
- erase_from_bucket(*it_bucket, bucket_for_hash(hash));
- }
- }
- /*
- * The call to insert_value may throw an exception if an element is added to the overflow
- * list. Rollback the elements in this case.
- */
- catch(...) {
- m_overflow_elements.swap(new_map.m_overflow_elements);
-
- const bool use_stored_hash = USE_STORED_HASH_ON_REHASH(new_map.bucket_count());
- for(auto it_bucket = new_map.m_buckets_data.begin(); it_bucket != new_map.m_buckets_data.end(); ++it_bucket) {
- if(it_bucket->empty()) {
- continue;
- }
-
- const std::size_t hash = use_stored_hash?
- it_bucket->truncated_bucket_hash():
- hash_key(KeySelect()(it_bucket->value()));
- const std::size_t ibucket_for_hash = bucket_for_hash(hash);
-
- // The elements we insert were not in the overflow list before the switch.
- // They will not be go in the overflow list if we rollback the switch.
- insert_value(ibucket_for_hash, hash, std::move(it_bucket->value()));
- }
-
- throw;
- }
-
- new_map.swap(*this);
- }
-
- template<typename U = value_type,
- typename std::enable_if<std::is_copy_constructible<U>::value &&
- !std::is_nothrow_move_constructible<U>::value>::type* = nullptr>
- void rehash_impl(size_type count_) {
- hopscotch_hash new_map = new_hopscotch_hash(count_);
-
- const bool use_stored_hash = USE_STORED_HASH_ON_REHASH(new_map.bucket_count());
- for(const hopscotch_bucket& bucket: m_buckets_data) {
- if(bucket.empty()) {
- continue;
- }
-
- const std::size_t hash = use_stored_hash?
- bucket.truncated_bucket_hash():
- new_map.hash_key(KeySelect()(bucket.value()));
- const std::size_t ibucket_for_hash = new_map.bucket_for_hash(hash);
-
- new_map.insert_value(ibucket_for_hash, hash, bucket.value());
- }
-
- for(const value_type& value: m_overflow_elements) {
- const std::size_t hash = new_map.hash_key(KeySelect()(value));
- const std::size_t ibucket_for_hash = new_map.bucket_for_hash(hash);
-
- new_map.insert_value(ibucket_for_hash, hash, value);
- }
-
- new_map.swap(*this);
- }
-
-#ifdef TSL_HH_NO_RANGE_ERASE_WITH_CONST_ITERATOR
- iterator_overflow mutable_overflow_iterator(const_iterator_overflow it) {
- return std::next(m_overflow_elements.begin(), std::distance(m_overflow_elements.cbegin(), it));
- }
-#else
- iterator_overflow mutable_overflow_iterator(const_iterator_overflow it) {
- return m_overflow_elements.erase(it, it);
- }
-#endif
-
- // iterator is in overflow list
- iterator_overflow erase_from_overflow(const_iterator_overflow pos, std::size_t ibucket_for_hash) {
-#ifdef TSL_HH_NO_RANGE_ERASE_WITH_CONST_ITERATOR
- auto it_next = m_overflow_elements.erase(mutable_overflow_iterator(pos));
-#else
- auto it_next = m_overflow_elements.erase(pos);
-#endif
- m_nb_elements--;
-
-
- // Check if we can remove the overflow flag
- tsl_hh_assert(m_buckets[ibucket_for_hash].has_overflow());
- for(const value_type& value: m_overflow_elements) {
- const std::size_t bucket_for_value = bucket_for_hash(hash_key(KeySelect()(value)));
- if(bucket_for_value == ibucket_for_hash) {
- return it_next;
- }
- }
-
- m_buckets[ibucket_for_hash].set_overflow(false);
- return it_next;
- }
-
-
- /**
- * bucket_for_value is the bucket in which the value is.
- * ibucket_for_hash is the bucket where the value belongs.
- */
- void erase_from_bucket(hopscotch_bucket& bucket_for_value, std::size_t ibucket_for_hash) noexcept {
- const std::size_t ibucket_for_value = std::distance(m_buckets_data.data(), &bucket_for_value);
- tsl_hh_assert(ibucket_for_value >= ibucket_for_hash);
-
- bucket_for_value.remove_value();
- m_buckets[ibucket_for_hash].toggle_neighbor_presence(ibucket_for_value - ibucket_for_hash);
- m_nb_elements--;
- }
-
-
-
- template<class K, class M>
- std::pair<iterator, bool> insert_or_assign_impl(K&& key, M&& obj) {
- auto it = try_emplace_impl(std::forward<K>(key), std::forward<M>(obj));
- if(!it.second) {
- it.first.value() = std::forward<M>(obj);
- }
-
- return it;
- }
-
- template<typename P, class... Args>
- std::pair<iterator, bool> try_emplace_impl(P&& key, Args&&... args_value) {
- const std::size_t hash = hash_key(key);
- const std::size_t ibucket_for_hash = bucket_for_hash(hash);
-
- // Check if already presents
- auto it_find = find_impl(key, hash, m_buckets + ibucket_for_hash);
- if(it_find != end()) {
- return std::make_pair(it_find, false);
- }
-
- return insert_value(ibucket_for_hash, hash, std::piecewise_construct,
- std::forward_as_tuple(std::forward<P>(key)),
- std::forward_as_tuple(std::forward<Args>(args_value)...));
- }
-
- template<typename P>
- std::pair<iterator, bool> insert_impl(P&& value) {
- const std::size_t hash = hash_key(KeySelect()(value));
- const std::size_t ibucket_for_hash = bucket_for_hash(hash);
-
- // Check if already presents
- auto it_find = find_impl(KeySelect()(value), hash, m_buckets + ibucket_for_hash);
- if(it_find != end()) {
- return std::make_pair(it_find, false);
- }
-
-
- return insert_value(ibucket_for_hash, hash, std::forward<P>(value));
- }
-
- template<typename... Args>
- std::pair<iterator, bool> insert_value(std::size_t ibucket_for_hash, std::size_t hash, Args&&... value_type_args) {
- if((m_nb_elements - m_overflow_elements.size()) >= m_max_load_threshold_rehash) {
- rehash(GrowthPolicy::next_bucket_count());
- ibucket_for_hash = bucket_for_hash(hash);
- }
-
- std::size_t ibucket_empty = find_empty_bucket(ibucket_for_hash);
- if(ibucket_empty < m_buckets_data.size()) {
- do {
- tsl_hh_assert(ibucket_empty >= ibucket_for_hash);
-
- // Empty bucket is in range of NeighborhoodSize, use it
- if(ibucket_empty - ibucket_for_hash < NeighborhoodSize) {
- auto it = insert_in_bucket(ibucket_empty, ibucket_for_hash,
- hash, std::forward<Args>(value_type_args)...);
- return std::make_pair(iterator(it, m_buckets_data.end(), m_overflow_elements.begin()), true);
- }
- }
- // else, try to swap values to get a closer empty bucket
- while(swap_empty_bucket_closer(ibucket_empty));
- }
-
- // Load factor is too low or a rehash will not change the neighborhood, put the value in overflow list
- if(size() < m_min_load_threshold_rehash || !will_neighborhood_change_on_rehash(ibucket_for_hash)) {
- auto it = insert_in_overflow(ibucket_for_hash, std::forward<Args>(value_type_args)...);
- return std::make_pair(iterator(m_buckets_data.end(), m_buckets_data.end(), it), true);
- }
-
- rehash(GrowthPolicy::next_bucket_count());
- ibucket_for_hash = bucket_for_hash(hash);
-
- return insert_value(ibucket_for_hash, hash, std::forward<Args>(value_type_args)...);
- }
-
- /*
- * Return true if a rehash will change the position of a key-value in the neighborhood of
- * ibucket_neighborhood_check. In this case a rehash is needed instead of puting the value in overflow list.
- */
- bool will_neighborhood_change_on_rehash(size_t ibucket_neighborhood_check) const {
- std::size_t expand_bucket_count = GrowthPolicy::next_bucket_count();
- GrowthPolicy expand_growth_policy(expand_bucket_count);
-
- const bool use_stored_hash = USE_STORED_HASH_ON_REHASH(expand_bucket_count);
- for(size_t ibucket = ibucket_neighborhood_check;
- ibucket < m_buckets_data.size() && (ibucket - ibucket_neighborhood_check) < NeighborhoodSize;
- ++ibucket)
- {
- tsl_hh_assert(!m_buckets[ibucket].empty());
-
- const size_t hash = use_stored_hash?
- m_buckets[ibucket].truncated_bucket_hash():
- hash_key(KeySelect()(m_buckets[ibucket].value()));
- if(bucket_for_hash(hash) != expand_growth_policy.bucket_for_hash(hash)) {
- return true;
- }
- }
-
- return false;
- }
-
- /*
- * Return the index of an empty bucket in m_buckets_data.
- * If none, the returned index equals m_buckets_data.size()
- */
- std::size_t find_empty_bucket(std::size_t ibucket_start) const {
- const std::size_t limit = std::min(ibucket_start + MAX_PROBES_FOR_EMPTY_BUCKET, m_buckets_data.size());
- for(; ibucket_start < limit; ibucket_start++) {
- if(m_buckets[ibucket_start].empty()) {
- return ibucket_start;
- }
- }
-
- return m_buckets_data.size();
- }
-
- /*
- * Insert value in ibucket_empty where value originally belongs to ibucket_for_hash
- *
- * Return bucket iterator to ibucket_empty
- */
- template<typename... Args>
- iterator_buckets insert_in_bucket(std::size_t ibucket_empty, std::size_t ibucket_for_hash,
- std::size_t hash, Args&&... value_type_args)
- {
- tsl_hh_assert(ibucket_empty >= ibucket_for_hash );
- tsl_hh_assert(m_buckets[ibucket_empty].empty());
- m_buckets[ibucket_empty].set_value_of_empty_bucket(hopscotch_bucket::truncate_hash(hash), std::forward<Args>(value_type_args)...);
-
- tsl_hh_assert(!m_buckets[ibucket_for_hash].empty());
- m_buckets[ibucket_for_hash].toggle_neighbor_presence(ibucket_empty - ibucket_for_hash);
- m_nb_elements++;
-
- return m_buckets_data.begin() + ibucket_empty;
- }
-
- template<class... Args, class U = OverflowContainer, typename std::enable_if<!has_key_compare<U>::value>::type* = nullptr>
- iterator_overflow insert_in_overflow(std::size_t ibucket_for_hash, Args&&... value_type_args) {
- auto it = m_overflow_elements.emplace(m_overflow_elements.end(), std::forward<Args>(value_type_args)...);
-
- m_buckets[ibucket_for_hash].set_overflow(true);
- m_nb_elements++;
-
- return it;
- }
-
- template<class... Args, class U = OverflowContainer, typename std::enable_if<has_key_compare<U>::value>::type* = nullptr>
- iterator_overflow insert_in_overflow(std::size_t ibucket_for_hash, Args&&... value_type_args) {
- auto it = m_overflow_elements.emplace(std::forward<Args>(value_type_args)...).first;
-
- m_buckets[ibucket_for_hash].set_overflow(true);
- m_nb_elements++;
-
- return it;
- }
-
- /*
- * Try to swap the bucket ibucket_empty_in_out with a bucket preceding it while keeping the neighborhood
- * conditions correct.
- *
- * If a swap was possible, the position of ibucket_empty_in_out will be closer to 0 and true will re returned.
- */
- bool swap_empty_bucket_closer(std::size_t& ibucket_empty_in_out) {
- tsl_hh_assert(ibucket_empty_in_out >= NeighborhoodSize);
- const std::size_t neighborhood_start = ibucket_empty_in_out - NeighborhoodSize + 1;
-
- for(std::size_t to_check = neighborhood_start; to_check < ibucket_empty_in_out; to_check++) {
- neighborhood_bitmap neighborhood_infos = m_buckets[to_check].neighborhood_infos();
- std::size_t to_swap = to_check;
-
- while(neighborhood_infos != 0 && to_swap < ibucket_empty_in_out) {
- if((neighborhood_infos & 1) == 1) {
- tsl_hh_assert(m_buckets[ibucket_empty_in_out].empty());
- tsl_hh_assert(!m_buckets[to_swap].empty());
-
- m_buckets[to_swap].swap_value_into_empty_bucket(m_buckets[ibucket_empty_in_out]);
-
- tsl_hh_assert(!m_buckets[to_check].check_neighbor_presence(ibucket_empty_in_out - to_check));
- tsl_hh_assert(m_buckets[to_check].check_neighbor_presence(to_swap - to_check));
-
- m_buckets[to_check].toggle_neighbor_presence(ibucket_empty_in_out - to_check);
- m_buckets[to_check].toggle_neighbor_presence(to_swap - to_check);
-
-
- ibucket_empty_in_out = to_swap;
-
- return true;
- }
-
- to_swap++;
- neighborhood_infos = neighborhood_bitmap(neighborhood_infos >> 1);
- }
- }
-
- return false;
- }
-
-
-
- template<class K, class U = ValueSelect, typename std::enable_if<has_mapped_type<U>::value>::type* = nullptr>
- typename U::value_type* find_value_impl(const K& key, std::size_t hash, hopscotch_bucket* bucket_for_hash) {
- return const_cast<typename U::value_type*>(
- static_cast<const hopscotch_hash*>(this)->find_value_impl(key, hash, bucket_for_hash));
- }
-
- /*
- * Avoid the creation of an iterator to just get the value for operator[] and at() in maps. Faster this way.
- *
- * Return null if no value for the key (TODO use std::optional when available).
- */
- template<class K, class U = ValueSelect, typename std::enable_if<has_mapped_type<U>::value>::type* = nullptr>
- const typename U::value_type* find_value_impl(const K& key, std::size_t hash,
- const hopscotch_bucket* bucket_for_hash) const
- {
- const hopscotch_bucket* bucket_found = find_in_buckets(key, hash, bucket_for_hash);
- if(bucket_found != nullptr) {
- return std::addressof(ValueSelect()(bucket_found->value()));
- }
-
- if(bucket_for_hash->has_overflow()) {
- auto it_overflow = find_in_overflow(key);
- if(it_overflow != m_overflow_elements.end()) {
- return std::addressof(ValueSelect()(*it_overflow));
- }
- }
-
- return nullptr;
- }
-
- template<class K>
- size_type count_impl(const K& key, std::size_t hash, const hopscotch_bucket* bucket_for_hash) const {
- if(find_in_buckets(key, hash, bucket_for_hash) != nullptr) {
- return 1;
- }
- else if(bucket_for_hash->has_overflow() && find_in_overflow(key) != m_overflow_elements.cend()) {
- return 1;
- }
- else {
- return 0;
- }
- }
-
- template<class K>
- iterator find_impl(const K& key, std::size_t hash, hopscotch_bucket* bucket_for_hash) {
- hopscotch_bucket* bucket_found = find_in_buckets(key, hash, bucket_for_hash);
- if(bucket_found != nullptr) {
- return iterator(m_buckets_data.begin() + std::distance(m_buckets_data.data(), bucket_found),
- m_buckets_data.end(), m_overflow_elements.begin());
- }
-
- if(!bucket_for_hash->has_overflow()) {
- return end();
- }
-
- return iterator(m_buckets_data.end(), m_buckets_data.end(), find_in_overflow(key));
- }
-
- template<class K>
- const_iterator find_impl(const K& key, std::size_t hash, const hopscotch_bucket* bucket_for_hash) const {
- const hopscotch_bucket* bucket_found = find_in_buckets(key, hash, bucket_for_hash);
- if(bucket_found != nullptr) {
- return const_iterator(m_buckets_data.cbegin() + std::distance(m_buckets_data.data(), bucket_found),
- m_buckets_data.cend(), m_overflow_elements.cbegin());
- }
-
- if(!bucket_for_hash->has_overflow()) {
- return cend();
- }
-
-
- return const_iterator(m_buckets_data.cend(), m_buckets_data.cend(), find_in_overflow(key));
- }
-
- template<class K>
- hopscotch_bucket* find_in_buckets(const K& key, std::size_t hash, hopscotch_bucket* bucket_for_hash) {
- const hopscotch_bucket* bucket_found =
- static_cast<const hopscotch_hash*>(this)->find_in_buckets(key, hash, bucket_for_hash);
- return const_cast<hopscotch_bucket*>(bucket_found);
- }
-
-
- /**
- * Return a pointer to the bucket which has the value, nullptr otherwise.
- */
- template<class K>
- const hopscotch_bucket* find_in_buckets(const K& key, std::size_t hash, const hopscotch_bucket* bucket_for_hash) const {
- (void) hash; // Avoid warning of unused variable when StoreHash is false;
-
- // TODO Try to optimize the function.
- // I tried to use ffs and __builtin_ffs functions but I could not reduce the time the function
- // takes with -march=native
-
- neighborhood_bitmap neighborhood_infos = bucket_for_hash->neighborhood_infos();
- while(neighborhood_infos != 0) {
- if((neighborhood_infos & 1) == 1) {
- // Check StoreHash before calling bucket_hash_equal. Functionally it doesn't change anythin.
- // If StoreHash is false, bucket_hash_equal is a no-op. Avoiding the call is there to help
- // GCC optimizes `hash` parameter away, it seems to not be able to do without this hint.
- if((!StoreHash || bucket_for_hash->bucket_hash_equal(hash)) &&
- compare_keys(KeySelect()(bucket_for_hash->value()), key))
- {
- return bucket_for_hash;
- }
- }
-
- ++bucket_for_hash;
- neighborhood_infos = neighborhood_bitmap(neighborhood_infos >> 1);
- }
-
- return nullptr;
- }
-
-
-
- template<class K, class U = OverflowContainer, typename std::enable_if<!has_key_compare<U>::value>::type* = nullptr>
- iterator_overflow find_in_overflow(const K& key) {
- return std::find_if(m_overflow_elements.begin(), m_overflow_elements.end(),
- [&](const value_type& value) {
- return compare_keys(key, KeySelect()(value));
- });
- }
-
- template<class K, class U = OverflowContainer, typename std::enable_if<!has_key_compare<U>::value>::type* = nullptr>
- const_iterator_overflow find_in_overflow(const K& key) const {
- return std::find_if(m_overflow_elements.cbegin(), m_overflow_elements.cend(),
- [&](const value_type& value) {
- return compare_keys(key, KeySelect()(value));
- });
- }
-
- template<class K, class U = OverflowContainer, typename std::enable_if<has_key_compare<U>::value>::type* = nullptr>
- iterator_overflow find_in_overflow(const K& key) {
- return m_overflow_elements.find(key);
- }
-
- template<class K, class U = OverflowContainer, typename std::enable_if<has_key_compare<U>::value>::type* = nullptr>
- const_iterator_overflow find_in_overflow(const K& key) const {
- return m_overflow_elements.find(key);
- }
-
-
-
- template<class U = OverflowContainer, typename std::enable_if<!has_key_compare<U>::value>::type* = nullptr>
- hopscotch_hash new_hopscotch_hash(size_type bucket_count) {
- return hopscotch_hash(bucket_count, static_cast<Hash&>(*this), static_cast<KeyEqual&>(*this),
- get_allocator(), m_max_load_factor);
- }
-
- template<class U = OverflowContainer, typename std::enable_if<has_key_compare<U>::value>::type* = nullptr>
- hopscotch_hash new_hopscotch_hash(size_type bucket_count) {
- return hopscotch_hash(bucket_count, static_cast<Hash&>(*this), static_cast<KeyEqual&>(*this),
- get_allocator(), m_max_load_factor, m_overflow_elements.key_comp());
- }
-
-public:
- static const size_type DEFAULT_INIT_BUCKETS_SIZE = 0;
- static constexpr float DEFAULT_MAX_LOAD_FACTOR = (NeighborhoodSize <= 30)?0.8f:0.9f;
-
-private:
- static const std::size_t MAX_PROBES_FOR_EMPTY_BUCKET = 12*NeighborhoodSize;
- static constexpr float MIN_LOAD_FACTOR_FOR_REHASH = 0.1f;
-
- /**
- * We can only use the hash on rehash if the size of the hash type is the same as the stored one or
- * if we use a power of two modulo. In the case of the power of two modulo, we just mask
- * the least significant bytes, we just have to check that the truncated_hash_type didn't truncated
- * too much bytes.
- */
- template<class T = size_type, typename std::enable_if<std::is_same<T, truncated_hash_type>::value>::type* = nullptr>
- static bool USE_STORED_HASH_ON_REHASH(size_type /*bucket_count*/) {
- return StoreHash;
- }
-
- template<class T = size_type, typename std::enable_if<!std::is_same<T, truncated_hash_type>::value>::type* = nullptr>
- static bool USE_STORED_HASH_ON_REHASH(size_type bucket_count) {
- (void) bucket_count;
- if(StoreHash && is_power_of_two_policy<GrowthPolicy>::value) {
- tsl_hh_assert(bucket_count > 0);
- return (bucket_count - 1) <= std::numeric_limits<truncated_hash_type>::max();
- }
- else {
- return false;
- }
- }
-
- /**
- * Return an always valid pointer to an static empty hopscotch_bucket.
- */
- hopscotch_bucket* static_empty_bucket_ptr() {
- static hopscotch_bucket empty_bucket;
- return &empty_bucket;
- }
-
-private:
- buckets_container_type m_buckets_data;
- overflow_container_type m_overflow_elements;
-
- /**
- * Points to m_buckets_data.data() if !m_buckets_data.empty() otherwise points to static_empty_bucket_ptr.
- * This variable is useful to avoid the cost of checking if m_buckets_data is empty when trying
- * to find an element.
- *
- * TODO Remove m_buckets_data and only use a pointer+size instead of a pointer+vector to save some space in the hopscotch_hash object.
- */
- hopscotch_bucket* m_buckets;
-
- size_type m_nb_elements;
-
- float m_max_load_factor;
-
- /**
- * Max size of the hash table before a rehash occurs automatically to grow the table.
- */
- size_type m_max_load_threshold_rehash;
-
- /**
- * Min size of the hash table before a rehash can occurs automatically (except if m_max_load_threshold_rehash os reached).
- * If the neighborhood of a bucket is full before the min is reacher, the elements are put into m_overflow_elements.
- */
- size_type m_min_load_threshold_rehash;
-};
-
-} // end namespace detail_hopscotch_hash
-
-
-} // end namespace tsl
-
-#endif
diff --git a/modelling/src/hopscotch_map.h b/modelling/src/hopscotch_map.h
deleted file mode 100644
index f5e38af00d7c39db228a6f5f9168857af491ee40..0000000000000000000000000000000000000000
--- a/modelling/src/hopscotch_map.h
+++ /dev/null
@@ -1,716 +0,0 @@
-//////////////////////////////////////////////////////
-// SOURCE: https://github.com/Tessil/hopscotch-map //
-// COMMIT: 4d0cccd3b41294f843925e495117919a53aa61a0 //
-// WE ASSUME THAT NO UPDATES ARE REQUIRED //
-//////////////////////////////////////////////////////
-
-/**
- * MIT License
- *
- * Copyright (c) 2017 Tessil
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-#ifndef TSL_HOPSCOTCH_MAP_H
-#define TSL_HOPSCOTCH_MAP_H
-
-
-#include <algorithm>
-#include <cstddef>
-#include <functional>
-#include <initializer_list>
-#include <list>
-#include <memory>
-#include <type_traits>
-#include <utility>
-#include "hopscotch_hash.h"
-
-
-namespace tsl {
-
-/**
- * Implementation of a hash map using the hopscotch hashing algorithm.
- *
- * The Key and the value T must be either nothrow move-constructible, copy-constuctible or both.
- *
- * The size of the neighborhood (NeighborhoodSize) must be > 0 and <= 62 if StoreHash is false.
- * When StoreHash is true, 32-bits of the hash will be stored alongside the neighborhood limiting
- * the NeighborhoodSize to <= 30. There is no memory usage difference between
- * 'NeighborhoodSize 62; StoreHash false' and 'NeighborhoodSize 30; StoreHash true'.
- *
- * Storing the hash may improve performance on insert during the rehash process if the hash takes time
- * to compute. It may also improve read performance if the KeyEqual function takes time (or incurs a cache-miss).
- * If used with simple Hash and KeyEqual it may slow things down.
- *
- * StoreHash can only be set if the GrowthPolicy is set to tsl::power_of_two_growth_policy.
- *
- * GrowthPolicy defines how the map grows and consequently how a hash value is mapped to a bucket.
- * By default the map uses tsl::power_of_two_growth_policy. This policy keeps the number of buckets
- * to a power of two and uses a mask to map the hash to a bucket instead of the slow modulo.
- * You may define your own growth policy, check tsl::power_of_two_growth_policy for the interface.
- *
- * If the destructors of Key or T throw an exception, behaviour of the class is undefined.
- *
- * Iterators invalidation:
- * - clear, operator=, reserve, rehash: always invalidate the iterators.
- * - insert, emplace, emplace_hint, operator[]: if there is an effective insert, invalidate the iterators
- * if a displacement is needed to resolve a collision (which mean that most of the time,
- * insert will invalidate the iterators). Or if there is a rehash.
- * - erase: iterator on the erased element is the only one which become invalid.
- */
-template<class Key,
- class T,
- class Hash = std::hash<Key>,
- class KeyEqual = std::equal_to<Key>,
- class Allocator = std::allocator<std::pair<Key, T>>,
- unsigned int NeighborhoodSize = 62,
- bool StoreHash = false,
- class GrowthPolicy = tsl::hh::power_of_two_growth_policy<2>>
-class hopscotch_map {
-private:
- template<typename U>
- using has_is_transparent = tsl::detail_hopscotch_hash::has_is_transparent<U>;
-
- class KeySelect {
- public:
- using key_type = Key;
-
- const key_type& operator()(const std::pair<Key, T>& key_value) const {
- return key_value.first;
- }
-
- key_type& operator()(std::pair<Key, T>& key_value) {
- return key_value.first;
- }
- };
-
- class ValueSelect {
- public:
- using value_type = T;
-
- const value_type& operator()(const std::pair<Key, T>& key_value) const {
- return key_value.second;
- }
-
- value_type& operator()(std::pair<Key, T>& key_value) {
- return key_value.second;
- }
- };
-
-
- using overflow_container_type = std::list<std::pair<Key, T>, Allocator>;
- using ht = detail_hopscotch_hash::hopscotch_hash<std::pair<Key, T>, KeySelect, ValueSelect,
- Hash, KeyEqual,
- Allocator, NeighborhoodSize,
- StoreHash, GrowthPolicy,
- overflow_container_type>;
-
-public:
- using key_type = typename ht::key_type;
- using mapped_type = T;
- using value_type = typename ht::value_type;
- using size_type = typename ht::size_type;
- using difference_type = typename ht::difference_type;
- using hasher = typename ht::hasher;
- using key_equal = typename ht::key_equal;
- using allocator_type = typename ht::allocator_type;
- using reference = typename ht::reference;
- using const_reference = typename ht::const_reference;
- using pointer = typename ht::pointer;
- using const_pointer = typename ht::const_pointer;
- using iterator = typename ht::iterator;
- using const_iterator = typename ht::const_iterator;
-
-
-
- /*
- * Constructors
- */
- hopscotch_map() : hopscotch_map(ht::DEFAULT_INIT_BUCKETS_SIZE) {
- }
-
- explicit hopscotch_map(size_type bucket_count,
- const Hash& hash = Hash(),
- const KeyEqual& equal = KeyEqual(),
- const Allocator& alloc = Allocator()) :
- m_ht(bucket_count, hash, equal, alloc, ht::DEFAULT_MAX_LOAD_FACTOR)
- {
- }
-
- hopscotch_map(size_type bucket_count,
- const Allocator& alloc) : hopscotch_map(bucket_count, Hash(), KeyEqual(), alloc)
- {
- }
-
- hopscotch_map(size_type bucket_count,
- const Hash& hash,
- const Allocator& alloc) : hopscotch_map(bucket_count, hash, KeyEqual(), alloc)
- {
- }
-
- explicit hopscotch_map(const Allocator& alloc) : hopscotch_map(ht::DEFAULT_INIT_BUCKETS_SIZE, alloc) {
- }
-
- template<class InputIt>
- hopscotch_map(InputIt first, InputIt last,
- size_type bucket_count = ht::DEFAULT_INIT_BUCKETS_SIZE,
- const Hash& hash = Hash(),
- const KeyEqual& equal = KeyEqual(),
- const Allocator& alloc = Allocator()) : hopscotch_map(bucket_count, hash, equal, alloc)
- {
- insert(first, last);
- }
-
- template<class InputIt>
- hopscotch_map(InputIt first, InputIt last,
- size_type bucket_count,
- const Allocator& alloc) : hopscotch_map(first, last, bucket_count, Hash(), KeyEqual(), alloc)
- {
- }
-
- template<class InputIt>
- hopscotch_map(InputIt first, InputIt last,
- size_type bucket_count,
- const Hash& hash,
- const Allocator& alloc) : hopscotch_map(first, last, bucket_count, hash, KeyEqual(), alloc)
- {
- }
-
- hopscotch_map(std::initializer_list<value_type> init,
- size_type bucket_count = ht::DEFAULT_INIT_BUCKETS_SIZE,
- const Hash& hash = Hash(),
- const KeyEqual& equal = KeyEqual(),
- const Allocator& alloc = Allocator()) :
- hopscotch_map(init.begin(), init.end(), bucket_count, hash, equal, alloc)
- {
- }
-
- hopscotch_map(std::initializer_list<value_type> init,
- size_type bucket_count,
- const Allocator& alloc) :
- hopscotch_map(init.begin(), init.end(), bucket_count, Hash(), KeyEqual(), alloc)
- {
- }
-
- hopscotch_map(std::initializer_list<value_type> init,
- size_type bucket_count,
- const Hash& hash,
- const Allocator& alloc) :
- hopscotch_map(init.begin(), init.end(), bucket_count, hash, KeyEqual(), alloc)
- {
- }
-
-
- hopscotch_map& operator=(std::initializer_list<value_type> ilist) {
- m_ht.clear();
-
- m_ht.reserve(ilist.size());
- m_ht.insert(ilist.begin(), ilist.end());
-
- return *this;
- }
-
- allocator_type get_allocator() const { return m_ht.get_allocator(); }
-
-
- /*
- * Iterators
- */
- iterator begin() noexcept { return m_ht.begin(); }
- const_iterator begin() const noexcept { return m_ht.begin(); }
- const_iterator cbegin() const noexcept { return m_ht.cbegin(); }
-
- iterator end() noexcept { return m_ht.end(); }
- const_iterator end() const noexcept { return m_ht.end(); }
- const_iterator cend() const noexcept { return m_ht.cend(); }
-
-
- /*
- * Capacity
- */
- bool empty() const noexcept { return m_ht.empty(); }
- size_type size() const noexcept { return m_ht.size(); }
- size_type max_size() const noexcept { return m_ht.max_size(); }
-
- /*
- * Modifiers
- */
- void clear() noexcept { m_ht.clear(); }
-
-
-
-
- std::pair<iterator, bool> insert(const value_type& value) {
- return m_ht.insert(value);
- }
-
- template<class P, typename std::enable_if<std::is_constructible<value_type, P&&>::value>::type* = nullptr>
- std::pair<iterator, bool> insert(P&& value) {
- return m_ht.insert(std::forward<P>(value));
- }
-
- std::pair<iterator, bool> insert(value_type&& value) {
- return m_ht.insert(std::move(value));
- }
-
-
- iterator insert(const_iterator hint, const value_type& value) {
- return m_ht.insert(hint, value);
- }
-
- template<class P, typename std::enable_if<std::is_constructible<value_type, P&&>::value>::type* = nullptr>
- iterator insert(const_iterator hint, P&& value) {
- return m_ht.insert(hint, std::forward<P>(value));
- }
-
- iterator insert(const_iterator hint, value_type&& value) {
- return m_ht.insert(hint, std::move(value));
- }
-
-
- template<class InputIt>
- void insert(InputIt first, InputIt last) {
- m_ht.insert(first, last);
- }
-
- void insert(std::initializer_list<value_type> ilist) {
- m_ht.insert(ilist.begin(), ilist.end());
- }
-
-
-
-
- template<class M>
- std::pair<iterator, bool> insert_or_assign(const key_type& k, M&& obj) {
- return m_ht.insert_or_assign(k, std::forward<M>(obj));
- }
-
- template<class M>
- std::pair<iterator, bool> insert_or_assign(key_type&& k, M&& obj) {
- return m_ht.insert_or_assign(std::move(k), std::forward<M>(obj));
- }
-
- template<class M>
- iterator insert_or_assign(const_iterator hint, const key_type& k, M&& obj) {
- return m_ht.insert_or_assign(hint, k, std::forward<M>(obj));
- }
-
- template<class M>
- iterator insert_or_assign(const_iterator hint, key_type&& k, M&& obj) {
- return m_ht.insert_or_assign(hint, std::move(k), std::forward<M>(obj));
- }
-
-
-
-
- /**
- * Due to the way elements are stored, emplace will need to move or copy the key-value once.
- * The method is equivalent to insert(value_type(std::forward<Args>(args)...));
- *
- * Mainly here for compatibility with the std::unordered_map interface.
- */
- template<class... Args>
- std::pair<iterator, bool> emplace(Args&&... args) {
- return m_ht.emplace(std::forward<Args>(args)...);
- }
-
-
-
-
- /**
- * Due to the way elements are stored, emplace_hint will need to move or copy the key-value once.
- * The method is equivalent to insert(hint, value_type(std::forward<Args>(args)...));
- *
- * Mainly here for compatibility with the std::unordered_map interface.
- */
- template<class... Args>
- iterator emplace_hint(const_iterator hint, Args&&... args) {
- return m_ht.emplace_hint(hint, std::forward<Args>(args)...);
- }
-
-
-
-
- template<class... Args>
- std::pair<iterator, bool> try_emplace(const key_type& k, Args&&... args) {
- return m_ht.try_emplace(k, std::forward<Args>(args)...);
- }
-
- template<class... Args>
- std::pair<iterator, bool> try_emplace(key_type&& k, Args&&... args) {
- return m_ht.try_emplace(std::move(k), std::forward<Args>(args)...);
- }
-
- template<class... Args>
- iterator try_emplace(const_iterator hint, const key_type& k, Args&&... args) {
- return m_ht.try_emplace(hint, k, std::forward<Args>(args)...);
- }
-
- template<class... Args>
- iterator try_emplace(const_iterator hint, key_type&& k, Args&&... args) {
- return m_ht.try_emplace(hint, std::move(k), std::forward<Args>(args)...);
- }
-
-
-
-
- iterator erase(iterator pos) { return m_ht.erase(pos); }
- iterator erase(const_iterator pos) { return m_ht.erase(pos); }
- iterator erase(const_iterator first, const_iterator last) { return m_ht.erase(first, last); }
- size_type erase(const key_type& key) { return m_ht.erase(key); }
-
- /**
- * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same
- * as hash_function()(key). Usefull to speed-up the lookup to the value if you already have the hash.
- */
- size_type erase(const key_type& key, std::size_t precalculated_hash) {
- return m_ht.erase(key, precalculated_hash);
- }
-
- /**
- * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent exists.
- * If so, K must be hashable and comparable to Key.
- */
- template<class K, class KE = KeyEqual, typename std::enable_if<has_is_transparent<KE>::value>::type* = nullptr>
- size_type erase(const K& key) { return m_ht.erase(key); }
-
- /**
- * @copydoc erase(const K& key)
- *
- * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same
- * as hash_function()(key). Usefull to speed-up the lookup to the value if you already have the hash.
- */
- template<class K, class KE = KeyEqual, typename std::enable_if<has_is_transparent<KE>::value>::type* = nullptr>
- size_type erase(const K& key, std::size_t precalculated_hash) {
- return m_ht.erase(key, precalculated_hash);
- }
-
-
-
-
- void swap(hopscotch_map& other) { other.m_ht.swap(m_ht); }
-
- /*
- * Lookup
- */
- T& at(const Key& key) { return m_ht.at(key); }
-
- /**
- * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same
- * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash.
- */
- T& at(const Key& key, std::size_t precalculated_hash) { return m_ht.at(key, precalculated_hash); }
-
-
- const T& at(const Key& key) const { return m_ht.at(key); }
-
- /**
- * @copydoc at(const Key& key, std::size_t precalculated_hash)
- */
- const T& at(const Key& key, std::size_t precalculated_hash) const { return m_ht.at(key, precalculated_hash); }
-
-
- /**
- * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent exists.
- * If so, K must be hashable and comparable to Key.
- */
- template<class K, class KE = KeyEqual, typename std::enable_if<has_is_transparent<KE>::value>::type* = nullptr>
- T& at(const K& key) { return m_ht.at(key); }
-
- /**
- * @copydoc at(const K& key)
- *
- * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same
- * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash.
- */
- template<class K, class KE = KeyEqual, typename std::enable_if<has_is_transparent<KE>::value>::type* = nullptr>
- T& at(const K& key, std::size_t precalculated_hash) { return m_ht.at(key, precalculated_hash); }
-
-
- /**
- * @copydoc at(const K& key)
- */
- template<class K, class KE = KeyEqual, typename std::enable_if<has_is_transparent<KE>::value>::type* = nullptr>
- const T& at(const K& key) const { return m_ht.at(key); }
-
- /**
- * @copydoc at(const K& key, std::size_t precalculated_hash)
- */
- template<class K, class KE = KeyEqual, typename std::enable_if<has_is_transparent<KE>::value>::type* = nullptr>
- const T& at(const K& key, std::size_t precalculated_hash) const { return m_ht.at(key, precalculated_hash); }
-
-
-
-
- T& operator[](const Key& key) { return m_ht[key]; }
- T& operator[](Key&& key) { return m_ht[std::move(key)]; }
-
-
-
-
- size_type count(const Key& key) const { return m_ht.count(key); }
-
- /**
- * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same
- * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash.
- */
- size_type count(const Key& key, std::size_t precalculated_hash) const {
- return m_ht.count(key, precalculated_hash);
- }
-
- /**
- * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent exists.
- * If so, K must be hashable and comparable to Key.
- */
- template<class K, class KE = KeyEqual, typename std::enable_if<has_is_transparent<KE>::value>::type* = nullptr>
- size_type count(const K& key) const { return m_ht.count(key); }
-
- /**
- * @copydoc count(const K& key) const
- *
- * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same
- * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash.
- */
- template<class K, class KE = KeyEqual, typename std::enable_if<has_is_transparent<KE>::value>::type* = nullptr>
- size_type count(const K& key, std::size_t precalculated_hash) const { return m_ht.count(key, precalculated_hash); }
-
-
-
-
- iterator find(const Key& key) { return m_ht.find(key); }
-
- /**
- * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same
- * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash.
- */
- iterator find(const Key& key, std::size_t precalculated_hash) { return m_ht.find(key, precalculated_hash); }
-
- const_iterator find(const Key& key) const { return m_ht.find(key); }
-
- /**
- * @copydoc find(const Key& key, std::size_t precalculated_hash)
- */
- const_iterator find(const Key& key, std::size_t precalculated_hash) const {
- return m_ht.find(key, precalculated_hash);
- }
-
- /**
- * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent exists.
- * If so, K must be hashable and comparable to Key.
- */
- template<class K, class KE = KeyEqual, typename std::enable_if<has_is_transparent<KE>::value>::type* = nullptr>
- iterator find(const K& key) { return m_ht.find(key); }
-
- /**
- * @copydoc find(const K& key)
- *
- * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same
- * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash.
- */
- template<class K, class KE = KeyEqual, typename std::enable_if<has_is_transparent<KE>::value>::type* = nullptr>
- iterator find(const K& key, std::size_t precalculated_hash) { return m_ht.find(key, precalculated_hash); }
-
- /**
- * @copydoc find(const K& key)
- */
- template<class K, class KE = KeyEqual, typename std::enable_if<has_is_transparent<KE>::value>::type* = nullptr>
- const_iterator find(const K& key) const { return m_ht.find(key); }
-
- /**
- * @copydoc find(const K& key)
- *
- * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same
- * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash.
- */
- template<class K, class KE = KeyEqual, typename std::enable_if<has_is_transparent<KE>::value>::type* = nullptr>
- const_iterator find(const K& key, std::size_t precalculated_hash) const {
- return m_ht.find(key, precalculated_hash);
- }
-
-
-
-
- bool contains(const Key& key) const { return m_ht.contains(key); }
-
- /**
- * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same
- * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash.
- */
- bool contains(const Key& key, std::size_t precalculated_hash) const {
- return m_ht.contains(key, precalculated_hash);
- }
-
- /**
- * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent exists.
- * If so, K must be hashable and comparable to Key.
- */
- template<class K, class KE = KeyEqual, typename std::enable_if<has_is_transparent<KE>::value>::type* = nullptr>
- bool contains(const K& key) const { return m_ht.contains(key); }
-
- /**
- * @copydoc contains(const K& key) const
- *
- * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same
- * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash.
- */
- template<class K, class KE = KeyEqual, typename std::enable_if<has_is_transparent<KE>::value>::type* = nullptr>
- bool contains(const K& key, std::size_t precalculated_hash) const {
- return m_ht.contains(key, precalculated_hash);
- }
-
-
-
-
- std::pair<iterator, iterator> equal_range(const Key& key) { return m_ht.equal_range(key); }
-
- /**
- * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same
- * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash.
- */
- std::pair<iterator, iterator> equal_range(const Key& key, std::size_t precalculated_hash) {
- return m_ht.equal_range(key, precalculated_hash);
- }
-
- std::pair<const_iterator, const_iterator> equal_range(const Key& key) const { return m_ht.equal_range(key); }
-
- /**
- * @copydoc equal_range(const Key& key, std::size_t precalculated_hash)
- */
- std::pair<const_iterator, const_iterator> equal_range(const Key& key, std::size_t precalculated_hash) const {
- return m_ht.equal_range(key, precalculated_hash);
- }
-
- /**
- * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent exists.
- * If so, K must be hashable and comparable to Key.
- */
- template<class K, class KE = KeyEqual, typename std::enable_if<has_is_transparent<KE>::value>::type* = nullptr>
- std::pair<iterator, iterator> equal_range(const K& key) { return m_ht.equal_range(key); }
-
-
- /**
- * @copydoc equal_range(const K& key)
- *
- * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same
- * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash.
- */
- template<class K, class KE = KeyEqual, typename std::enable_if<has_is_transparent<KE>::value>::type* = nullptr>
- std::pair<iterator, iterator> equal_range(const K& key, std::size_t precalculated_hash) {
- return m_ht.equal_range(key, precalculated_hash);
- }
-
- /**
- * @copydoc equal_range(const K& key)
- */
- template<class K, class KE = KeyEqual, typename std::enable_if<has_is_transparent<KE>::value>::type* = nullptr>
- std::pair<const_iterator, const_iterator> equal_range(const K& key) const { return m_ht.equal_range(key); }
-
- /**
- * @copydoc equal_range(const K& key, std::size_t precalculated_hash)
- */
- template<class K, class KE = KeyEqual, typename std::enable_if<has_is_transparent<KE>::value>::type* = nullptr>
- std::pair<const_iterator, const_iterator> equal_range(const K& key, std::size_t precalculated_hash) const {
- return m_ht.equal_range(key, precalculated_hash);
- }
-
-
-
-
- /*
- * Bucket interface
- */
- size_type bucket_count() const { return m_ht.bucket_count(); }
- size_type max_bucket_count() const { return m_ht.max_bucket_count(); }
-
-
- /*
- * Hash policy
- */
- float load_factor() const { return m_ht.load_factor(); }
- float max_load_factor() const { return m_ht.max_load_factor(); }
- void max_load_factor(float ml) { m_ht.max_load_factor(ml); }
-
- void rehash(size_type count_) { m_ht.rehash(count_); }
- void reserve(size_type count_) { m_ht.reserve(count_); }
-
-
- /*
- * Observers
- */
- hasher hash_function() const { return m_ht.hash_function(); }
- key_equal key_eq() const { return m_ht.key_eq(); }
-
- /*
- * Other
- */
-
- /**
- * Convert a const_iterator to an iterator.
- */
- iterator mutable_iterator(const_iterator pos) {
- return m_ht.mutable_iterator(pos);
- }
-
- size_type overflow_size() const noexcept { return m_ht.overflow_size(); }
-
- friend bool operator==(const hopscotch_map& lhs, const hopscotch_map& rhs) {
- if(lhs.size() != rhs.size()) {
- return false;
- }
-
- for(const auto& element_lhs : lhs) {
- const auto it_element_rhs = rhs.find(element_lhs.first);
- if(it_element_rhs == rhs.cend() || element_lhs.second != it_element_rhs->second) {
- return false;
- }
- }
-
- return true;
- }
-
- friend bool operator!=(const hopscotch_map& lhs, const hopscotch_map& rhs) {
- return !operator==(lhs, rhs);
- }
-
- friend void swap(hopscotch_map& lhs, hopscotch_map& rhs) {
- lhs.swap(rhs);
- }
-
-
-
-private:
- ht m_ht;
-};
-
-
-/**
- * Same as `tsl::hopscotch_map<Key, T, Hash, KeyEqual, Allocator, NeighborhoodSize, StoreHash, tsl::hh::prime_growth_policy>`.
- */
-template<class Key,
- class T,
- class Hash = std::hash<Key>,
- class KeyEqual = std::equal_to<Key>,
- class Allocator = std::allocator<std::pair<Key, T>>,
- unsigned int NeighborhoodSize = 62,
- bool StoreHash = false>
-using hopscotch_pg_map = hopscotch_map<Key, T, Hash, KeyEqual, Allocator, NeighborhoodSize, StoreHash, tsl::hh::prime_growth_policy>;
-
-} // end namespace tsl
-
-#endif
diff --git a/modelling/src/pocket_finder.cc b/modelling/src/pocket_finder.cc
index f3f1b8f7c25884545604d0c208da114430b3954f..85d68e1ad5dd934a9819247a54cd68202313b052 100644
--- a/modelling/src/pocket_finder.cc
+++ b/modelling/src/pocket_finder.cc
@@ -13,18 +13,15 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-#include <chrono>
-#include <tuple>
-#include <unordered_map>
-#include <limits>
#include <promod3/modelling/pocket_finder.hh>
#include <promod3/core/runtime_profiling.hh>
-#include <promod3/modelling/hopscotch_map.h>
+#include <promod3/modelling/robin_hood.h>
+#include <promod3/core/portable_binary_serializer.hh>
+#include <promod3/core/check_io.hh>
#include <promod3/core/eigen_types.hh>
#include <promod3/core/superpose.hh>
-#include <ost/mol/atom_view.hh>
namespace {
@@ -38,12 +35,12 @@ struct PocketHasherKey {
inline void SetX(int32_t x) { key = (key & (~x_mask)) | ((x+31) << 12); }
inline void SetY(int32_t y) { key = (key & (~y_mask)) | ((y+31) << 6); }
inline void SetZ(int32_t z) { key = (key & (~z_mask)) | (z+31); }
- inline uint32_t GetA() { return (key & a_mask) >> 26; }
- inline uint32_t GetB() { return (key & b_mask) >> 22; }
- inline uint32_t GetC() { return (key & c_mask) >> 18; }
- inline int32_t GetX() { return ((key & x_mask) >> 12) - 31; }
- inline int32_t GetY() { return ((key & y_mask) >> 6) - 31; }
- inline int32_t GetZ() { return (key & z_mask) - 31; }
+ inline uint32_t GetA() const { return (key & a_mask) >> 26; }
+ inline uint32_t GetB() const { return (key & b_mask) >> 22; }
+ inline uint32_t GetC() const { return (key & c_mask) >> 18; }
+ inline int32_t GetX() const { return ((key & x_mask) >> 12) - 31; }
+ inline int32_t GetY() const { return ((key & y_mask) >> 6) - 31; }
+ inline int32_t GetZ() const { return (key & z_mask) - 31; }
bool operator==(const PocketHasherKey& other) const {
return key == other.key;
@@ -54,42 +51,68 @@ struct PocketHasherKey {
}
// data layout: 00aaaabbbbccccxxxxxxyyyyyyzzzzzz
- // a,b,c can represent values in [0,15}
+ // a,b,c can represent values in [0,15]
// x,y,z can represent values in [-31,31]
// THERE ARE NO CHECKS FOR OVERFLOWS!
- static const uint32_t a_mask = 1006632960;
- static const uint32_t b_mask = 62914560;
- static const uint32_t c_mask = 3932160;
- static const uint32_t x_mask = 258048;
- static const uint32_t y_mask = 4032;
- static const uint32_t z_mask = 63;
+ static const uint32_t a_mask = 0x3C000000;
+ static const uint32_t b_mask = 0x03C00000;
+ static const uint32_t c_mask = 0x003C0000;
+ static const uint32_t x_mask = 0x0003F000;
+ static const uint32_t y_mask = 0x00000FC0;
+ static const uint32_t z_mask = 0x0000003F;
+
uint32_t key;
};
-
+
struct PocketHasherKeyHasher {
std::size_t operator()(const PocketHasherKey& k) const {
- std::hash<uint32_t> h;
- return h(k.key);
+ static robin_hood::hash<uint32_t> hasher;
+ return hasher(k.key);
}
};
+
+
+struct PocketHasherValueItem {
+
+ PocketHasherValueItem(): data(0) { }
+
+ inline void SetQueryIdx(uint32_t idx) { data = (data & (~q_mask)) | (idx << 21); }
+ inline void SetTriangleIdx(uint32_t idx) { data = (data & (~t_mask)) | idx; }
+ inline uint32_t GetQueryIdx() const { return (data & q_mask) >> 21; }
+ inline uint32_t GetTriangleIdx() const { return data & t_mask; }
+
+ // data layout: qqqqqqqqqqqttttttttttttttttttttt
+ // query idx (q) can represent values in [0, 2047]
+ // triangle idx (t) can represent values in [0, 2097151]
+ // THERE ARE NO CHECKS FOR OVERFLOWS
+ static const uint32_t q_mask = 0xFFE00000;
+ static const uint32_t t_mask = 0x001FFFFF;
+ uint32_t data;
+};
+
+typedef std::vector<PocketHasherValueItem> PocketHasherValue;
+typedef robin_hood::unordered_map<PocketHasherKey, PocketHasherValue,
+ PocketHasherKeyHasher> PocketHasherMap;
-typedef std::vector<std::pair<uint32_t, uint32_t> > PocketHasherValue;
-typedef tsl::hopscotch_map<PocketHasherKey, PocketHasherValue,
- PocketHasherKeyHasher> PocketHasherMap;
-void Transform(const promod3::core::EMat3X& pos, int p1, int p2, int p3,
- promod3::core::EMat3X& transformed_pos) {
+void BaseTransform(const promod3::core::EMat3X& pos, int p1, int p2, int p3,
+ promod3::core::EMat3X& transformed_pos) {
+
+ // translate to new origin
+ transformed_pos = pos.colwise() - pos.col(p1);
+
+ // define new coordinate system
promod3::core::EMat3 base;
- base.col(0) = pos.col(p2) - pos.col(p1);
- base.col(1) = pos.col(p3) - pos.col(p1);
- base.col(2) = base.col(0).cross(base.col(1));
- base.col(0).normalize();
- base.col(1).normalize();
- base.col(2).normalize();
- transformed_pos = base.inverse() * pos;
+ base.col(0) = transformed_pos.col(p2);
+ base.col(2) = base.col(0).cross(transformed_pos.col(p3));
+ base.col(1) = base.col(0).cross(base.col(2));
+ base.colwise().normalize();
+
+ // change base
+ transformed_pos = base.inverse() * transformed_pos;
}
@@ -112,9 +135,180 @@ void SetupEigenMatrices(const geom::Vec3List& positions,
}
}
}
-} // anon ns
+bool RefineInitialHit(const promod3::modelling::PocketQuery& query,
+ const promod3::core::EMat3X& target_pos, Real dist_thresh,
+ int query_idx, int query_triangle_idx, int target_p1,
+ int target_p2, int target_p3, geom::Mat4& mat,
+ std::vector<std::pair<int, int> >& alignment) {
+
+ // the query is only available as geom::Vec3List. This makes sense from a
+ // usability perspective, but we transfer the thing to an Eigen matrix
+ // for processing here...
+ int query_n = query.GetQuerySize(query_idx);
+ const geom::Vec3List& query_vec3_pos = query.GetPositions(query_idx);
+ promod3::core::EMat3X query_pos = promod3::core::EMat3X::Zero(3, query_n);
+ for(int i = 0; i < query_n; ++i) {
+ promod3::core::EMatFillCol(query_pos, i, query_vec3_pos[i]);
+ }
+
+ // we superpose iteratively, so we need to keep track of several
+ // transformations
+ std::vector<geom::Mat4> transformations;
+
+ // the initial alignment is defined by the triangles in query and target
+ int temp = query_triangle_idx;
+ int query_p1 = static_cast<int>(temp/(query_n*query_n));
+ temp -= query_p1*query_n*query_n;
+ int query_p2 = static_cast<int>(temp/query_n);
+ temp -= query_p2*query_n;
+ int query_p3 = temp;
+
+ alignment.clear();
+ alignment.push_back(std::make_pair(query_p1, target_p1));
+ alignment.push_back(std::make_pair(query_p2, target_p2));
+ alignment.push_back(std::make_pair(query_p3, target_p3));
+
+ // to save some square roots, we're only dealing with squared distances below
+ Real squared_dist_thresh = dist_thresh * dist_thresh;
+
+ for(int iteration = 0; iteration < 5; ++iteration) {
+
+ if(alignment.size() < 3) {
+ return false;
+ }
+
+ promod3::core::EMatX3 m1 = promod3::core::EMatX3::Zero(alignment.size(),3);
+ promod3::core::EMatX3 m2 = promod3::core::EMatX3::Zero(alignment.size(),3);
+
+ for(uint i = 0; i < alignment.size(); ++i) {
+ m1.row(i) = query_pos.col(alignment[i].first).transpose();
+ m2.row(i) = target_pos.col(alignment[i].second).transpose();
+ }
+
+ geom::Mat4 t = promod3::core::MinRMSDSuperposition(m1, m2);
+
+ // apply the transform to the query positions
+ promod3::core::EMat3 rot;
+ rot(0,0) = t(0,0); rot(0,1) = t(0,1); rot(0,2) = t(0,2);
+ rot(1,0) = t(1,0); rot(1,1) = t(1,1); rot(1,2) = t(1,2);
+ rot(2,0) = t(2,0); rot(2,1) = t(2,1); rot(2,2) = t(2,2);
+
+ promod3::core::EVec3 trans;
+ trans(0,0) = t(0,3);
+ trans(1,0) = t(1,3);
+ trans(2,0) = t(2,3);
+
+ query_pos = rot * query_pos;
+ query_pos = query_pos.colwise() + trans;
+
+ std::vector<std::pair<int,int> > new_alignment;
+
+ for(int i = 0; i < query_pos.cols(); ++i) {
+ promod3::core::ERVecX squared_distances =
+ (target_pos.colwise() - query_pos.col(i)).colwise().squaredNorm();
+ Real min = squared_distances.minCoeff();
+ if(min <= squared_dist_thresh) {
+ int min_idx = -1;
+ for(int j = 0; j < squared_distances.cols(); ++j) {
+ if(squared_distances(0,j) == min) {
+ min_idx = j;
+ break;
+ }
+ }
+ new_alignment.push_back(std::make_pair(i, min_idx));
+ }
+ }
+
+ transformations.push_back(t);
+ if(new_alignment == alignment) {
+ break;
+ }
+ alignment = new_alignment;
+ }
+
+ // chain together the final transformation matrix
+ int t_idx = transformations.size() - 1;
+ mat = transformations[t_idx];
+ while(t_idx > 0) {
+ mat = mat * transformations[--t_idx];
+ }
+
+ return true;
+}
+
+
+void ProcessAccumulator(const promod3::modelling::PocketQuery& query,
+ const promod3::core::EMat3X& target_pos,
+ Real coverage_thresh, Real dist_thresh,
+ int target_p1, int target_p2, int target_p3,
+ promod3::core::EMatXX& accumulator,
+ std::vector<promod3::modelling::PocketMatch>& results) {
+
+ promod3::core::ERVecX w = accumulator.colwise().maxCoeff();
+ for(int query_idx = 0; query_idx < accumulator.cols(); ++query_idx) {
+ if(w(0,query_idx) / (query.GetQuerySize(query_idx)-3) >= coverage_thresh) {
+ int query_size = query.GetQuerySize(query_idx);
+ int query_n_triangles = query_size*query_size*query_size;
+ for(int query_triangle_idx = 0;
+ query_triangle_idx < query_n_triangles; ++query_triangle_idx) {
+ if(accumulator(query_triangle_idx, query_idx) / (query_size-3) >=
+ coverage_thresh) {
+ geom::Mat4 mat;
+ std::vector<std::pair<int,int> > aln;
+ if(RefineInitialHit(query, target_pos, 1.0, query_idx,
+ query_triangle_idx, target_p1,
+ target_p2, target_p3, mat, aln)) {
+
+ // only add the result if its unique
+ bool already_there = false;
+ for(uint res_idx = 0; res_idx < results.size(); ++res_idx) {
+ // we do not check for the transformation matrix
+ // if the alignment is the same, the matrix will also be the same
+ if(results[res_idx].query_idx == query_idx &&
+ results[res_idx].aln == aln) {
+ already_there = true;
+ break;
+ }
+ }
+ if(!already_there) {
+ results.push_back(promod3::modelling::PocketMatch(query_idx,
+ mat, aln));
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // done, let's prepare the accumulator for another round of counting
+ accumulator.setZero();
+}
+
+
+template<typename T>
+void WriteVec(std::ofstream& stream, const std::vector<T>& vec) {
+ uint32_t size = vec.size();
+ stream.write(reinterpret_cast<char*>(&size), sizeof(uint32_t));
+ if(size > 0) {
+ stream.write(reinterpret_cast<const char*>(&vec[0]), size*sizeof(T));
+ }
+}
+
+
+template<typename T>
+void ReadVec(std::ifstream& stream, std::vector<T>& vec) {
+ uint32_t size;
+ stream.read(reinterpret_cast<char*>(&size), sizeof(uint32_t));
+ if(size > 0) {
+ vec.resize(size);
+ stream.read(reinterpret_cast<char*>(&vec[0]), size*sizeof(T));
+ }
+}
+
+} // anon ns
+
namespace promod3 { namespace modelling {
@@ -128,9 +322,22 @@ struct PocketQueryData {
};
+PocketQuery::PocketQuery() { }
+
+
PocketQuery::PocketQuery(const geom::Vec3List& positions,
const String& identifier) {
+ if(positions.size() < 4) {
+ throw promod3::Error("Require at least 4 positions to construct "
+ "PocketQuery");
+ }
+
+ if(positions.size() > 128) {
+ throw promod3::Error("PocketQuery is limited to a maximum of 128 "
+ "positions");
+ }
+
data_ = std::shared_ptr<PocketQueryData>(new PocketQueryData);
data_->positions.push_back(positions);
data_->identifiers.push_back(identifier);
@@ -148,7 +355,6 @@ PocketQuery::PocketQuery(const geom::Vec3List& positions,
int n = eigen_positions.cols();
for(int p1 = 0; p1 < n; ++p1) {
- std::cerr<<p1<<std::endl;
for(int p2 = 0; p2 < n; ++p2) {
if(p2 == p1) {
continue;
@@ -172,7 +378,7 @@ PocketQuery::PocketQuery(const geom::Vec3List& positions,
// transform all positions into the vector basis defined by the
// triangle with edges at p1, p2, p3 and add the stuff to the hash
// map
- Transform(eigen_positions, p1, p2, p3, transformed_pos);
+ BaseTransform(eigen_positions, p1, p2, p3, transformed_pos);
for(int i = 0; i < n; ++i) {
if(i != p1 && i != p2 && i != p3) {
d = static_cast<int32_t>(transformed_pos(0,i));
@@ -190,9 +396,10 @@ PocketQuery::PocketQuery(const geom::Vec3List& positions,
data_->map[key] = PocketHasherValue();
it = data_->map.find(key);
}
- it.value().push_back(std::make_pair(triangle_idx, query_idx));
- // for std::unordered_map you have to do:
- //it->second.push_back(std::make_pair(triangle_idx, query_idx));
+ PocketHasherValueItem item;
+ item.SetQueryIdx(query_idx);
+ item.SetTriangleIdx(triangle_idx);
+ it->second.push_back(item);
}
}
}
@@ -213,6 +420,11 @@ PocketQuery::PocketQuery(const std::vector<PocketQuery>& queries) {
// iterate over all queries and update data_ along the way
for(auto query_it = queries.begin(); query_it != queries.end(); ++query_it) {
+ if(data_->n_queries + query_it->data_->n_queries > 2048) {
+ throw promod3::Error("Can at most combine 2048 PocketQueries to one "
+ "single query");
+ }
+
// update the map in data_
for(auto other_map_it = query_it->data_->map.begin();
other_map_it != query_it->data_->map.end(); ++other_map_it) {
@@ -221,14 +433,13 @@ PocketQuery::PocketQuery(const std::vector<PocketQuery>& queries) {
map[other_map_it->first] = PocketHasherValue();
map_it = map.find(other_map_it->first);
}
- for(auto value_it = other_map_it->second.begin();
- value_it != other_map_it->second.end(); ++value_it) {
+ for(auto item_it = other_map_it->second.begin();
+ item_it != other_map_it->second.end(); ++item_it) {
// we have to update the second element of the value pairs (query index)
- std::pair<int,int> new_value =
- std::make_pair(value_it->first, value_it->second + data_->n_queries);
- map_it.value().push_back(new_value);
- // for std::unordered_map you have to do:
- //map_it->second.push_back(new_value);
+ PocketHasherValueItem new_item;
+ new_item.SetQueryIdx(item_it->GetQueryIdx() + data_->n_queries);
+ new_item.SetTriangleIdx(item_it->GetTriangleIdx());
+ map_it->second.push_back(new_item);
}
}
@@ -245,6 +456,123 @@ PocketQuery::PocketQuery(const std::vector<PocketQuery>& queries) {
}
+PocketQuery PocketQuery::Load(const String& filename) {
+
+ std::ifstream in_stream_(filename.c_str(), std::ios::binary);
+ if (!in_stream_) {
+ std::stringstream ss;
+ ss << "The file '" << filename << "' does not exist.";
+ throw promod3::Error(ss.str());
+ }
+
+ core::PortableBinaryDataSource in_stream(in_stream_);
+ core::CheckMagicNumber(in_stream);
+ uint32_t version = core::GetVersionNumber(in_stream_);
+ if(version != 1) {
+ std::stringstream ss;
+ ss << "Unsupported file version '" << version << "' in '" << filename;
+ throw promod3::Error(ss.str());
+ }
+ core::CheckTypeSize<uint32_t>(in_stream);
+ core::CheckTypeSize<float>(in_stream);
+
+ PocketQuery query;
+ query.data_ = std::shared_ptr<PocketQueryData>(new PocketQueryData);
+ std::vector<uint32_t> query_sizes;
+ std::vector<float> position_vec;
+ std::vector<uint32_t> keys;
+ std::vector<uint32_t> n_items;
+ std::vector<uint32_t> items;
+
+ in_stream & query.data_->identifiers;
+ ReadVec(in_stream.Stream(), query_sizes);
+ ReadVec(in_stream.Stream(), position_vec);
+ ReadVec(in_stream.Stream(), keys);
+ ReadVec(in_stream.Stream(), n_items);
+ ReadVec(in_stream.Stream(), items);
+
+ query.data_->n_queries = query.data_->identifiers.size();
+ query.data_->n_max = 0;
+ int current_pos = 0;
+ for(int i = 0; i < query.data_->n_queries; ++i) {
+ geom::Vec3List positions;
+ for(uint j = 0; j < query_sizes[i]; ++j) {
+ positions.push_back(geom::Vec3(position_vec[current_pos],
+ position_vec[current_pos+1],
+ position_vec[current_pos+2]));
+ current_pos += 3;
+ }
+ query.data_->positions.push_back(positions);
+ query.data_->n_max = std::max(query.data_->n_max,
+ static_cast<int>(query_sizes[i]));
+ }
+
+ current_pos = 0;
+ for(uint i = 0; i < keys.size(); ++i) {
+ PocketHasherKey key;
+ key.key = keys[i];
+ PocketHasherValue value;
+ for(uint j = 0; j < n_items[i]; ++j) {
+ PocketHasherValueItem item;
+ item.data = items[current_pos++];
+ value.push_back(item);
+ }
+ query.data_->map[key] = value;
+ }
+
+ return query;
+}
+
+
+void PocketQuery::Save(const String& filename) const {
+
+ std::ofstream out_stream_(filename.c_str(), std::ios::binary);
+ if (!out_stream_) {
+ std::stringstream ss;
+ ss << "The file '" << filename << "' cannot be opened.";
+ throw promod3::Error(ss.str());
+ }
+
+ std::vector<uint32_t> query_sizes;
+ std::vector<float> position_vec;
+ std::vector<uint32_t> keys;
+ std::vector<uint32_t> n_items;
+ std::vector<uint32_t> items;
+
+ for(uint i = 0; i < data_->positions.size(); ++i) {
+ int n = data_->positions[i].size();
+ query_sizes.push_back(n);
+ for(int j = 0; j < n; ++j) {
+ position_vec.push_back(data_->positions[i][j][0]);
+ position_vec.push_back(data_->positions[i][j][1]);
+ position_vec.push_back(data_->positions[i][j][2]);
+ }
+ }
+
+ for(PocketHasherMap::const_iterator it = data_->map.begin();
+ it != data_->map.end(); ++it) {
+ keys.push_back(it->first.key);
+ n_items.push_back(it->second.size());
+ for(uint i = 0; i < n_items.back(); ++i) {
+ items.push_back(it->second[i].data);
+ }
+ }
+
+ core::PortableBinaryDataSink out_stream(out_stream_);
+ core::WriteMagicNumber(out_stream);
+ core::WriteVersionNumber(out_stream, 1);
+ core::WriteTypeSize<uint32_t>(out_stream);
+ core::WriteTypeSize<float>(out_stream);
+
+ out_stream & data_->identifiers;
+ WriteVec(out_stream.Stream(), query_sizes);
+ WriteVec(out_stream.Stream(), position_vec);
+ WriteVec(out_stream.Stream(), keys);
+ WriteVec(out_stream.Stream(), n_items);
+ WriteVec(out_stream.Stream(), items);
+}
+
+
const geom::Vec3List& PocketQuery::GetPositions(uint idx) const{
return data_->positions[idx];
}
@@ -260,8 +588,17 @@ size_t PocketQuery::GetN() const {
}
-geom::Mat4 FindTransform(const PocketQuery& query,
- const geom::Vec3List& positions) {
+size_t PocketQuery::GetQuerySize(uint idx) const {
+ return data_->positions[idx].size();
+}
+
+
+std::vector<PocketMatch> FindPockets(const PocketQuery& query,
+ const geom::Vec3List& positions,
+ Real coverage_thresh,
+ Real distance_thresh) {
+
+ std::vector<PocketMatch> results;
promod3::core::EMat3X eigen_positions;
promod3::core::EMatXX eigen_distances;
@@ -275,25 +612,17 @@ geom::Mat4 FindTransform(const PocketQuery& query,
// fetch hash map and setup accumulator
const PocketHasherMap& map = query.data_->map;
- int n_max = query.data_->n_max;
- int n_queries = query.data_->n_queries;
promod3::core::EMatXX accumulator =
- promod3::core::EMatXX::Zero(n_max*n_max*n_max, n_queries);
+ promod3::core::EMatXX::Zero(std::pow(query.data_->n_max, 3),
+ query.data_->n_queries);
for(int p1 = 0; p1 < n_target; ++p1) {
- std::cerr<<p1<<std::endl;
- for(int p2 = 0; p2 < n_target; ++p2) {
- if(p2 == p1) {
- continue;
- }
+ for(int p2 = p1+1; p2 < n_target; ++p2) {
a = static_cast<uint32_t>(eigen_distances(p1,p2));
if(a > 12) {
continue;
}
- for(int p3 = 0; p3 < n_target; ++p3) {
- if(p3 == p1 || p3 == p2) {
- continue;
- }
+ for(int p3 = p2+1; p3 < n_target; ++p3) {
b = static_cast<uint32_t>(eigen_distances(p1,p3));
c = static_cast<uint32_t>(eigen_distances(p2,p3));
if(b > 12 || c > 12) {
@@ -306,7 +635,7 @@ geom::Mat4 FindTransform(const PocketQuery& query,
// triangle with edges at p1, p2, p3
// search for occurences of that triangle in combination with the
// transformed positions in the hash map
- Transform(eigen_positions, p1, p2, p3, transformed_pos);
+ BaseTransform(eigen_positions, p1, p2, p3, transformed_pos);
for(int i = 0; i < n_target; ++i) {
if(i != p1 && i != p2 && i != p3) {
d = static_cast<int32_t>(transformed_pos(0,i));
@@ -320,37 +649,21 @@ geom::Mat4 FindTransform(const PocketQuery& query,
if(it != map.end()) {
for(PocketHasherValue::const_iterator v_it = it->second.begin();
v_it != it->second.end(); ++v_it) {
- accumulator(v_it->first, v_it->second) += 1.0;
+ accumulator(v_it->GetTriangleIdx(), v_it->GetQueryIdx()) += 1.0;
}
}
}
}
}
-
- // search for high vote numbers in accumulator
- Eigen::Matrix<Real,1,1> w = accumulator.colwise().maxCoeff();
- for(int i = 0; i < accumulator.cols(); ++i) {
- if(w(0,i) > 10) {
- //TODO do something awesome with the found hit
- std::cerr<<"asdfsadfasdfasdf"<<std::endl;
- }
- }
- accumulator.setZero();
+ // modifies results based on counts in the accumulator and
+ // wipes the accumulator for another round of counting
+ ProcessAccumulator(query, eigen_positions, coverage_thresh, 1.0,
+ p1, p2, p3, accumulator, results);
}
}
}
- return geom::Mat4();
-}
-
-
-void PocketFinder(const PocketQuery& query,
- const geom::Vec3List& target_positions) {
-
-
- geom::Mat4 t = FindTransform(query, target_positions);
-
- std::cerr<<t<<std::endl;
+ return results;
}
}} // ns
diff --git a/modelling/src/pocket_finder.hh b/modelling/src/pocket_finder.hh
index 5da95d2c96a8263495f36d0963f8769c3248680e..f9884466d6fdefa0f54d262b800bc76bf0b64d73 100644
--- a/modelling/src/pocket_finder.hh
+++ b/modelling/src/pocket_finder.hh
@@ -32,17 +32,41 @@ struct PocketQuery{
PocketQuery(const std::vector<PocketQuery>& queries);
+ static PocketQuery Load(const String& filename);
+
+ void Save(const String& filename) const;
+
const geom::Vec3List& GetPositions(uint idx) const;
const std::vector<String>& GetIdentifiers() const;
size_t GetN() const;
+ size_t GetQuerySize(uint idx) const;
+
std::shared_ptr<PocketQueryData> data_;
+
+private:
+
+ PocketQuery();
};
-void PocketFinder(const PocketQuery& query,
- const geom::Vec3List& target_positions);
+
+struct PocketMatch{
+
+ PocketMatch(int q_idx, const geom::Mat4& m,
+ const std::vector<std::pair<int,int> >& a): query_idx(q_idx),
+ mat(m), aln(a) { }
+ int query_idx;
+ geom::Mat4 mat;
+ std::vector<std::pair<int,int> > aln;
+};
+
+
+std::vector<PocketMatch> FindPockets(const PocketQuery& query,
+ const geom::Vec3List& positions,
+ Real coverage_thresh = 0.5,
+ Real distance_thresh = 1.0);
}} // ns
diff --git a/modelling/src/robin_hood.h b/modelling/src/robin_hood.h
new file mode 100644
index 0000000000000000000000000000000000000000..fe02f6b7b1e613d45654cc1f175741835c4f3fc9
--- /dev/null
+++ b/modelling/src/robin_hood.h
@@ -0,0 +1,2063 @@
+/////////////////////////////////////////////////////////////////
+// SOURCE: https://github.com/martinus/robin-hood-hashing.git //
+// COMMIT: c9d72bdf02c4289903544325b272eb9f4adb7b83 //
+// WE ASSUME THAT NO UPDATES ARE REQUIRED //
+/////////////////////////////////////////////////////////////////
+
+
+// ______ _____ ______ _________
+// ______________ ___ /_ ___(_)_______ ___ /_ ______ ______ ______ /
+// __ ___/_ __ \__ __ \__ / __ __ \ __ __ \_ __ \_ __ \_ __ /
+// _ / / /_/ /_ /_/ /_ / _ / / / _ / / // /_/ // /_/ // /_/ /
+// /_/ \____/ /_.___/ /_/ /_/ /_/ ________/_/ /_/ \____/ \____/ \__,_/
+// _/_____/
+//
+// Fast & memory efficient hashtable based on robin hood hashing for C++11/14/17/20
+// version 3.4.1
+// https://github.com/martinus/robin-hood-hashing
+//
+// Licensed under the MIT License <http://opensource.org/licenses/MIT>.
+// SPDX-License-Identifier: MIT
+// Copyright (c) 2018-2019 Martin Ankerl <http://martin.ankerl.com>
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+
+#ifndef ROBIN_HOOD_H_INCLUDED
+#define ROBIN_HOOD_H_INCLUDED
+
+// see https://semver.org/
+#define ROBIN_HOOD_VERSION_MAJOR 3 // for incompatible API changes
+#define ROBIN_HOOD_VERSION_MINOR 4 // for adding functionality in a backwards-compatible manner
+#define ROBIN_HOOD_VERSION_PATCH 1 // for backwards-compatible bug fixes
+
+#include <algorithm>
+#include <cstdlib>
+#include <cstring>
+#include <functional>
+#include <stdexcept>
+#include <string>
+#include <type_traits>
+#include <utility>
+
+// #define ROBIN_HOOD_LOG_ENABLED
+#ifdef ROBIN_HOOD_LOG_ENABLED
+# include <iostream>
+# define ROBIN_HOOD_LOG(x) std::cout << __FUNCTION__ << "@" << __LINE__ << ": " << x << std::endl
+#else
+# define ROBIN_HOOD_LOG(x)
+#endif
+
+// #define ROBIN_HOOD_TRACE_ENABLED
+#ifdef ROBIN_HOOD_TRACE_ENABLED
+# include <iostream>
+# define ROBIN_HOOD_TRACE(x) \
+ std::cout << __FUNCTION__ << "@" << __LINE__ << ": " << x << std::endl
+#else
+# define ROBIN_HOOD_TRACE(x)
+#endif
+
+// all non-argument macros should use this facility. See
+// https://www.fluentcpp.com/2019/05/28/better-macros-better-flags/
+#define ROBIN_HOOD(x) ROBIN_HOOD_PRIVATE_DEFINITION_##x()
+
+// mark unused members with this macro
+#define ROBIN_HOOD_UNUSED(identifier)
+
+// bitness
+#if SIZE_MAX == UINT32_MAX
+# define ROBIN_HOOD_PRIVATE_DEFINITION_BITNESS() 32
+#elif SIZE_MAX == UINT64_MAX
+# define ROBIN_HOOD_PRIVATE_DEFINITION_BITNESS() 64
+#else
+# error Unsupported bitness
+#endif
+
+// endianess
+#ifdef _WIN32
+# define ROBIN_HOOD_PRIVATE_DEFINITION_LITTLE_ENDIAN() 1
+# define ROBIN_HOOD_PRIVATE_DEFINITION_BIG_ENDIAN() 0
+#else
+# define ROBIN_HOOD_PRIVATE_DEFINITION_LITTLE_ENDIAN() \
+ (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)
+# define ROBIN_HOOD_PRIVATE_DEFINITION_BIG_ENDIAN() (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
+#endif
+
+// inline
+#ifdef _WIN32
+# define ROBIN_HOOD_PRIVATE_DEFINITION_NOINLINE() __declspec(noinline)
+#else
+# define ROBIN_HOOD_PRIVATE_DEFINITION_NOINLINE() __attribute__((noinline))
+#endif
+
+// exceptions
+#if !defined(__cpp_exceptions) && !defined(__EXCEPTIONS) && !defined(_CPPUNWIND)
+# define ROBIN_HOOD_PRIVATE_DEFINITION_HAS_EXCEPTIONS() 0
+#else
+# define ROBIN_HOOD_PRIVATE_DEFINITION_HAS_EXCEPTIONS() 1
+#endif
+
+// count leading/trailing bits
+#ifdef _WIN32
+# if ROBIN_HOOD(BITNESS) == 32
+# define ROBIN_HOOD_PRIVATE_DEFINITION_BITSCANFORWARD() _BitScanForward
+# else
+# define ROBIN_HOOD_PRIVATE_DEFINITION_BITSCANFORWARD() _BitScanForward64
+# endif
+# include <intrin.h>
+# pragma intrinsic(ROBIN_HOOD(BITSCANFORWARD))
+# define ROBIN_HOOD_COUNT_TRAILING_ZEROES(x) \
+ [](size_t mask) noexcept->int { \
+ unsigned long index; \
+ return ROBIN_HOOD(BITSCANFORWARD)(&index, mask) ? static_cast<int>(index) \
+ : ROBIN_HOOD(BITNESS); \
+ } \
+ (x)
+#else
+# if ROBIN_HOOD(BITNESS) == 32
+# define ROBIN_HOOD_PRIVATE_DEFINITION_CTZ() __builtin_ctzl
+# define ROBIN_HOOD_PRIVATE_DEFINITION_CLZ() __builtin_clzl
+# else
+# define ROBIN_HOOD_PRIVATE_DEFINITION_CTZ() __builtin_ctzll
+# define ROBIN_HOOD_PRIVATE_DEFINITION_CLZ() __builtin_clzll
+# endif
+# define ROBIN_HOOD_COUNT_LEADING_ZEROES(x) ((x) ? ROBIN_HOOD(CLZ)(x) : ROBIN_HOOD(BITNESS))
+# define ROBIN_HOOD_COUNT_TRAILING_ZEROES(x) ((x) ? ROBIN_HOOD(CTZ)(x) : ROBIN_HOOD(BITNESS))
+#endif
+
+// fallthrough
+#ifndef __has_cpp_attribute // For backwards compatibility
+# define __has_cpp_attribute(x) 0
+#endif
+#if __has_cpp_attribute(clang::fallthrough)
+# define ROBIN_HOOD_PRIVATE_DEFINITION_FALLTHROUGH() [[clang::fallthrough]]
+#elif __has_cpp_attribute(gnu::fallthrough)
+# define ROBIN_HOOD_PRIVATE_DEFINITION_FALLTHROUGH() [[gnu::fallthrough]]
+#else
+# define ROBIN_HOOD_PRIVATE_DEFINITION_FALLTHROUGH()
+#endif
+
+// likely/unlikely
+#if defined(_WIN32)
+# define ROBIN_HOOD_LIKELY(condition) condition
+# define ROBIN_HOOD_UNLIKELY(condition) condition
+#else
+# define ROBIN_HOOD_LIKELY(condition) __builtin_expect(condition, 1)
+# define ROBIN_HOOD_UNLIKELY(condition) __builtin_expect(condition, 0)
+#endif
+
+// workaround missing "is_trivially_copyable" in g++ < 5.0
+// See https://stackoverflow.com/a/31798726/48181
+#if defined(__GNUC__) && __GNUC__ < 5
+# define ROBIN_HOOD_IS_TRIVIALLY_COPYABLE(...) __has_trivial_copy(__VA_ARGS__)
+#else
+# define ROBIN_HOOD_IS_TRIVIALLY_COPYABLE(...) std::is_trivially_copyable<__VA_ARGS__>::value
+#endif
+
+// helpers for C++ versions, see https://gcc.gnu.org/onlinedocs/cpp/Standard-Predefined-Macros.html
+#define ROBIN_HOOD_PRIVATE_DEFINITION_CXX() __cplusplus
+#define ROBIN_HOOD_PRIVATE_DEFINITION_CXX98() 199711L
+#define ROBIN_HOOD_PRIVATE_DEFINITION_CXX11() 201103L
+#define ROBIN_HOOD_PRIVATE_DEFINITION_CXX14() 201402L
+#define ROBIN_HOOD_PRIVATE_DEFINITION_CXX17() 201703L
+
+#if ROBIN_HOOD(CXX) >= ROBIN_HOOD(CXX17)
+# define ROBIN_HOOD_PRIVATE_DEFINITION_NODISCARD() [[nodiscard]]
+#else
+# define ROBIN_HOOD_PRIVATE_DEFINITION_NODISCARD()
+#endif
+
+namespace robin_hood {
+
+#if ROBIN_HOOD(CXX) >= ROBIN_HOOD(CXX14)
+# define ROBIN_HOOD_STD std
+#else
+
+// c++11 compatibility layer
+namespace ROBIN_HOOD_STD {
+template <class T>
+struct alignment_of
+ : std::integral_constant<std::size_t, alignof(typename std::remove_all_extents<T>::type)> {};
+
+template <class T, T... Ints>
+class integer_sequence {
+public:
+ using value_type = T;
+ static_assert(std::is_integral<value_type>::value, "not integral type");
+ static constexpr std::size_t size() noexcept {
+ return sizeof...(Ints);
+ }
+};
+template <std::size_t... Inds>
+using index_sequence = integer_sequence<std::size_t, Inds...>;
+
+namespace detail_ {
+template <class T, T Begin, T End, bool>
+struct IntSeqImpl {
+ using TValue = T;
+ static_assert(std::is_integral<TValue>::value, "not integral type");
+ static_assert(Begin >= 0 && Begin < End, "unexpected argument (Begin<0 || Begin<=End)");
+
+ template <class, class>
+ struct IntSeqCombiner;
+
+ template <TValue... Inds0, TValue... Inds1>
+ struct IntSeqCombiner<integer_sequence<TValue, Inds0...>, integer_sequence<TValue, Inds1...>> {
+ using TResult = integer_sequence<TValue, Inds0..., Inds1...>;
+ };
+
+ using TResult =
+ typename IntSeqCombiner<typename IntSeqImpl<TValue, Begin, Begin + (End - Begin) / 2,
+ (End - Begin) / 2 == 1>::TResult,
+ typename IntSeqImpl<TValue, Begin + (End - Begin) / 2, End,
+ (End - Begin + 1) / 2 == 1>::TResult>::TResult;
+};
+
+template <class T, T Begin>
+struct IntSeqImpl<T, Begin, Begin, false> {
+ using TValue = T;
+ static_assert(std::is_integral<TValue>::value, "not integral type");
+ static_assert(Begin >= 0, "unexpected argument (Begin<0)");
+ using TResult = integer_sequence<TValue>;
+};
+
+template <class T, T Begin, T End>
+struct IntSeqImpl<T, Begin, End, true> {
+ using TValue = T;
+ static_assert(std::is_integral<TValue>::value, "not integral type");
+ static_assert(Begin >= 0, "unexpected argument (Begin<0)");
+ using TResult = integer_sequence<TValue, Begin>;
+};
+} // namespace detail_
+
+template <class T, T N>
+using make_integer_sequence = typename detail_::IntSeqImpl<T, 0, N, (N - 0) == 1>::TResult;
+
+template <std::size_t N>
+using make_index_sequence = make_integer_sequence<std::size_t, N>;
+
+template <class... T>
+using index_sequence_for = make_index_sequence<sizeof...(T)>;
+
+} // namespace ROBIN_HOOD_STD
+
+#endif
+
+namespace detail {
+
+// umul
+#if defined(__SIZEOF_INT128__)
+# define ROBIN_HOOD_PRIVATE_DEFINITION_HAS_UMUL128() 1
+# if defined(__GNUC__) || defined(__clang__)
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wpedantic"
+using uint128_t = unsigned __int128;
+# pragma GCC diagnostic pop
+# endif
+inline uint64_t umul128(uint64_t a, uint64_t b, uint64_t* high) noexcept {
+ auto result = static_cast<uint128_t>(a) * static_cast<uint128_t>(b);
+ *high = static_cast<uint64_t>(result >> 64U);
+ return static_cast<uint64_t>(result);
+}
+#elif (defined(_WIN32) && ROBIN_HOOD(BITNESS) == 64)
+# define ROBIN_HOOD_PRIVATE_DEFINITION_HAS_UMUL128() 1
+# include <intrin.h> // for __umulh
+# pragma intrinsic(__umulh)
+# ifndef _M_ARM64
+# pragma intrinsic(_umul128)
+# endif
+inline uint64_t umul128(uint64_t a, uint64_t b, uint64_t* high) noexcept {
+# ifdef _M_ARM64
+ *high = __umulh(a, b);
+ return ((uint64_t)(a)) * (b);
+# else
+ return _umul128(a, b, high);
+# endif
+}
+#else
+# define ROBIN_HOOD_PRIVATE_DEFINITION_HAS_UMUL128() 0
+#endif
+
+// This cast gets rid of warnings like "cast from 'uint8_t*' {aka 'unsigned char*'} to
+// 'uint64_t*' {aka 'long unsigned int*'} increases required alignment of target type". Use with
+// care!
+template <typename T>
+inline T reinterpret_cast_no_cast_align_warning(void* ptr) noexcept {
+ return reinterpret_cast<T>(ptr);
+}
+
+template <typename T>
+inline T reinterpret_cast_no_cast_align_warning(void const* ptr) noexcept {
+ return reinterpret_cast<T>(ptr);
+}
+
+// make sure this is not inlined as it is slow and dramatically enlarges code, thus making other
+// inlinings more difficult. Throws are also generally the slow path.
+template <typename E, typename... Args>
+ROBIN_HOOD(NOINLINE)
+#if ROBIN_HOOD(HAS_EXCEPTIONS)
+void doThrow(Args&&... args) {
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay)
+ throw E(std::forward<Args>(args)...);
+}
+#else
+void doThrow(Args&&... ROBIN_HOOD_UNUSED(args) /*unused*/) {
+ abort();
+}
+#endif
+
+template <typename E, typename T, typename... Args>
+T* assertNotNull(T* t, Args&&... args) {
+ if (ROBIN_HOOD_UNLIKELY(nullptr == t)) {
+ doThrow<E>(std::forward<Args>(args)...);
+ }
+ return t;
+}
+
+template <typename T>
+inline T unaligned_load(void const* ptr) noexcept {
+ // using memcpy so we don't get into unaligned load problems.
+ // compiler should optimize this very well anyways.
+ T t;
+ std::memcpy(&t, ptr, sizeof(T));
+ return t;
+}
+
+// Allocates bulks of memory for objects of type T. This deallocates the memory in the destructor,
+// and keeps a linked list of the allocated memory around. Overhead per allocation is the size of a
+// pointer.
+template <typename T, size_t MinNumAllocs = 4, size_t MaxNumAllocs = 256>
+class BulkPoolAllocator {
+public:
+ BulkPoolAllocator() noexcept = default;
+
+ // does not copy anything, just creates a new allocator.
+ BulkPoolAllocator(const BulkPoolAllocator& ROBIN_HOOD_UNUSED(o) /*unused*/) noexcept
+ : mHead(nullptr)
+ , mListForFree(nullptr) {}
+
+ BulkPoolAllocator(BulkPoolAllocator&& o) noexcept
+ : mHead(o.mHead)
+ , mListForFree(o.mListForFree) {
+ o.mListForFree = nullptr;
+ o.mHead = nullptr;
+ }
+
+ BulkPoolAllocator& operator=(BulkPoolAllocator&& o) noexcept {
+ reset();
+ mHead = o.mHead;
+ mListForFree = o.mListForFree;
+ o.mListForFree = nullptr;
+ o.mHead = nullptr;
+ return *this;
+ }
+
+ BulkPoolAllocator&
+ operator=(const BulkPoolAllocator& ROBIN_HOOD_UNUSED(o) /*unused*/) noexcept {
+ // does not do anything
+ return *this;
+ }
+
+ ~BulkPoolAllocator() noexcept {
+ reset();
+ }
+
+ // Deallocates all allocated memory.
+ void reset() noexcept {
+ while (mListForFree) {
+ T* tmp = *mListForFree;
+ free(mListForFree);
+ mListForFree = reinterpret_cast_no_cast_align_warning<T**>(tmp);
+ }
+ mHead = nullptr;
+ }
+
+ // allocates, but does NOT initialize. Use in-place new constructor, e.g.
+ // T* obj = pool.allocate();
+ // ::new (static_cast<void*>(obj)) T();
+ T* allocate() {
+ T* tmp = mHead;
+ if (!tmp) {
+ tmp = performAllocation();
+ }
+
+ mHead = *reinterpret_cast_no_cast_align_warning<T**>(tmp);
+ return tmp;
+ }
+
+ // does not actually deallocate but puts it in store.
+ // make sure you have already called the destructor! e.g. with
+ // obj->~T();
+ // pool.deallocate(obj);
+ void deallocate(T* obj) noexcept {
+ *reinterpret_cast_no_cast_align_warning<T**>(obj) = mHead;
+ mHead = obj;
+ }
+
+ // Adds an already allocated block of memory to the allocator. This allocator is from now on
+ // responsible for freeing the data (with free()). If the provided data is not large enough to
+ // make use of, it is immediately freed. Otherwise it is reused and freed in the destructor.
+ void addOrFree(void* ptr, const size_t numBytes) noexcept {
+ // calculate number of available elements in ptr
+ if (numBytes < ALIGNMENT + ALIGNED_SIZE) {
+ // not enough data for at least one element. Free and return.
+ free(ptr);
+ } else {
+ add(ptr, numBytes);
+ }
+ }
+
+ void swap(BulkPoolAllocator<T, MinNumAllocs, MaxNumAllocs>& other) noexcept {
+ using std::swap;
+ swap(mHead, other.mHead);
+ swap(mListForFree, other.mListForFree);
+ }
+
+private:
+ // iterates the list of allocated memory to calculate how many to alloc next.
+ // Recalculating this each time saves us a size_t member.
+ // This ignores the fact that memory blocks might have been added manually with addOrFree. In
+ // practice, this should not matter much.
+ ROBIN_HOOD(NODISCARD) size_t calcNumElementsToAlloc() const noexcept {
+ auto tmp = mListForFree;
+ size_t numAllocs = MinNumAllocs;
+
+ while (numAllocs * 2 <= MaxNumAllocs && tmp) {
+ auto x = reinterpret_cast<T***>(tmp);
+ tmp = *x;
+ numAllocs *= 2;
+ }
+
+ return numAllocs;
+ }
+
+ // WARNING: Underflow if numBytes < ALIGNMENT! This is guarded in addOrFree().
+ void add(void* ptr, const size_t numBytes) noexcept {
+ const size_t numElements = (numBytes - ALIGNMENT) / ALIGNED_SIZE;
+
+ auto data = reinterpret_cast<T**>(ptr);
+
+ // link free list
+ auto x = reinterpret_cast<T***>(data);
+ *x = mListForFree;
+ mListForFree = data;
+
+ // create linked list for newly allocated data
+ auto const headT =
+ reinterpret_cast_no_cast_align_warning<T*>(reinterpret_cast<char*>(ptr) + ALIGNMENT);
+
+ auto const head = reinterpret_cast<char*>(headT);
+
+ // Visual Studio compiler automatically unrolls this loop, which is pretty cool
+ for (size_t i = 0; i < numElements; ++i) {
+ *reinterpret_cast_no_cast_align_warning<char**>(head + i * ALIGNED_SIZE) =
+ head + (i + 1) * ALIGNED_SIZE;
+ }
+
+ // last one points to 0
+ *reinterpret_cast_no_cast_align_warning<T**>(head + (numElements - 1) * ALIGNED_SIZE) =
+ mHead;
+ mHead = headT;
+ }
+
+ // Called when no memory is available (mHead == 0).
+ // Don't inline this slow path.
+ ROBIN_HOOD(NOINLINE) T* performAllocation() {
+ size_t const numElementsToAlloc = calcNumElementsToAlloc();
+
+ // alloc new memory: [prev |T, T, ... T]
+ // std::cout << (sizeof(T*) + ALIGNED_SIZE * numElementsToAlloc) << " bytes" << std::endl;
+ size_t const bytes = ALIGNMENT + ALIGNED_SIZE * numElementsToAlloc;
+ add(assertNotNull<std::bad_alloc>(malloc(bytes)), bytes);
+ return mHead;
+ }
+
+ // enforce byte alignment of the T's
+#if ROBIN_HOOD(CXX) >= ROBIN_HOOD(CXX14)
+ static constexpr size_t ALIGNMENT =
+ (std::max)(std::alignment_of<T>::value, std::alignment_of<T*>::value);
+#else
+ static const size_t ALIGNMENT =
+ (ROBIN_HOOD_STD::alignment_of<T>::value > ROBIN_HOOD_STD::alignment_of<T*>::value)
+ ? ROBIN_HOOD_STD::alignment_of<T>::value
+ : +ROBIN_HOOD_STD::alignment_of<T*>::value; // the + is for walkarround
+#endif
+
+ static constexpr size_t ALIGNED_SIZE = ((sizeof(T) - 1) / ALIGNMENT + 1) * ALIGNMENT;
+
+ static_assert(MinNumAllocs >= 1, "MinNumAllocs");
+ static_assert(MaxNumAllocs >= MinNumAllocs, "MaxNumAllocs");
+ static_assert(ALIGNED_SIZE >= sizeof(T*), "ALIGNED_SIZE");
+ static_assert(0 == (ALIGNED_SIZE % sizeof(T*)), "ALIGNED_SIZE mod");
+ static_assert(ALIGNMENT >= sizeof(T*), "ALIGNMENT");
+
+ T* mHead{nullptr};
+ T** mListForFree{nullptr};
+};
+
+template <typename T, size_t MinSize, size_t MaxSize, bool IsFlatMap>
+struct NodeAllocator;
+
+// dummy allocator that does nothing
+template <typename T, size_t MinSize, size_t MaxSize>
+struct NodeAllocator<T, MinSize, MaxSize, true> {
+
+ // we are not using the data, so just free it.
+ void addOrFree(void* ptr, size_t ROBIN_HOOD_UNUSED(numBytes) /*unused*/) noexcept {
+ free(ptr);
+ }
+};
+
+template <typename T, size_t MinSize, size_t MaxSize>
+struct NodeAllocator<T, MinSize, MaxSize, false> : public BulkPoolAllocator<T, MinSize, MaxSize> {};
+
+// dummy hash, unsed as mixer when robin_hood::hash is already used
+template <typename T>
+struct identity_hash {
+ constexpr size_t operator()(T const& obj) const noexcept {
+ return static_cast<size_t>(obj);
+ }
+};
+
+// c++14 doesn't have is_nothrow_swappable, and clang++ 6.0.1 doesn't like it either, so I'm making
+// my own here.
+namespace swappable {
+using std::swap;
+template <typename T>
+struct nothrow {
+ static const bool value = noexcept(swap(std::declval<T&>(), std::declval<T&>()));
+};
+
+} // namespace swappable
+
+} // namespace detail
+
+struct is_transparent_tag {};
+
+// A custom pair implementation is used in the map because std::pair is not is_trivially_copyable,
+// which means it would not be allowed to be used in std::memcpy. This struct is copyable, which is
+// also tested.
+template <typename T1, typename T2>
+struct pair {
+ using first_type = T1;
+ using second_type = T2;
+
+ template <typename U1 = T1, typename U2 = T2,
+ typename = typename std::enable_if<std::is_default_constructible<U1>::value &&
+ std::is_default_constructible<U2>::value>::type>
+ constexpr pair() noexcept(noexcept(U1()) && noexcept(U2()))
+ : first()
+ , second() {}
+
+ // pair constructors are explicit so we don't accidentally call this ctor when we don't have to.
+ explicit constexpr pair(std::pair<T1, T2> const& o) noexcept(
+ noexcept(T1(std::declval<T1 const&>())) && noexcept(T2(std::declval<T2 const&>())))
+ : first(o.first)
+ , second(o.second) {}
+
+ // pair constructors are explicit so we don't accidentally call this ctor when we don't have to.
+ explicit constexpr pair(std::pair<T1, T2>&& o) noexcept(
+ noexcept(T1(std::move(std::declval<T1&&>()))) &&
+ noexcept(T2(std::move(std::declval<T2&&>()))))
+ : first(std::move(o.first))
+ , second(std::move(o.second)) {}
+
+ constexpr pair(T1&& a, T2&& b) noexcept(noexcept(T1(std::move(std::declval<T1&&>()))) &&
+ noexcept(T2(std::move(std::declval<T2&&>()))))
+ : first(std::move(a))
+ , second(std::move(b)) {}
+
+ template <typename U1, typename U2>
+ constexpr pair(U1&& a, U2&& b) noexcept(noexcept(T1(std::forward<U1>(std::declval<U1&&>()))) &&
+ noexcept(T2(std::forward<U2>(std::declval<U2&&>()))))
+ : first(std::forward<U1>(a))
+ , second(std::forward<U2>(b)) {}
+
+ template <typename... U1, typename... U2>
+ constexpr pair(
+ std::piecewise_construct_t /*unused*/, std::tuple<U1...> a,
+ std::tuple<U2...> b) noexcept(noexcept(pair(std::declval<std::tuple<U1...>&>(),
+ std::declval<std::tuple<U2...>&>(),
+ ROBIN_HOOD_STD::index_sequence_for<U1...>(),
+ ROBIN_HOOD_STD::index_sequence_for<U2...>())))
+ : pair(a, b, ROBIN_HOOD_STD::index_sequence_for<U1...>(),
+ ROBIN_HOOD_STD::index_sequence_for<U2...>()) {}
+
+ // constructor called from the std::piecewise_construct_t ctor
+ template <typename... U1, size_t... I1, typename... U2, size_t... I2>
+ pair(std::tuple<U1...>& a, std::tuple<U2...>& b,
+ ROBIN_HOOD_STD::index_sequence<I1...> /*unused*/,
+ ROBIN_HOOD_STD::index_sequence<
+ I2...> /*unused*/) noexcept(noexcept(T1(std::
+ forward<U1>(std::get<I1>(
+ std::declval<
+ std::tuple<U1...>&>()))...)) &&
+ noexcept(T2(std::forward<U2>(
+ std::get<I2>(std::declval<std::tuple<U2...>&>()))...)))
+ : first(std::forward<U1>(std::get<I1>(a))...)
+ , second(std::forward<U2>(std::get<I2>(b))...) {
+ // make visual studio compiler happy about warning about unused a & b.
+ // Visual studio's pair implementation disables warning 4100.
+ (void)a;
+ (void)b;
+ }
+
+ ROBIN_HOOD(NODISCARD) first_type& getFirst() noexcept {
+ return first;
+ }
+ ROBIN_HOOD(NODISCARD) first_type const& getFirst() const noexcept {
+ return first;
+ }
+ ROBIN_HOOD(NODISCARD) second_type& getSecond() noexcept {
+ return second;
+ }
+ ROBIN_HOOD(NODISCARD) second_type const& getSecond() const noexcept {
+ return second;
+ }
+
+ void swap(pair<T1, T2>& o) noexcept((detail::swappable::nothrow<T1>::value) &&
+ (detail::swappable::nothrow<T2>::value)) {
+ using std::swap;
+ swap(first, o.first);
+ swap(second, o.second);
+ }
+
+ T1 first; // NOLINT(misc-non-private-member-variables-in-classes)
+ T2 second; // NOLINT(misc-non-private-member-variables-in-classes)
+};
+
+template <typename A, typename B>
+void swap(pair<A, B>& a, pair<A, B>& b) noexcept(
+ noexcept(std::declval<pair<A, B>&>().swap(std::declval<pair<A, B>&>()))) {
+ a.swap(b);
+}
+
+// Hash an arbitrary amount of bytes. This is basically Murmur2 hash without caring about big
+// endianness. TODO(martinus) add a fallback for very large strings?
+static size_t hash_bytes(void const* ptr, size_t const len) noexcept {
+ static constexpr uint64_t m = UINT64_C(0xc6a4a7935bd1e995);
+ static constexpr uint64_t seed = UINT64_C(0xe17a1465);
+ static constexpr unsigned int r = 47;
+
+ auto const data64 = static_cast<uint64_t const*>(ptr);
+ uint64_t h = seed ^ (len * m);
+
+ size_t const n_blocks = len / 8;
+ for (size_t i = 0; i < n_blocks; ++i) {
+ auto k = detail::unaligned_load<uint64_t>(data64 + i);
+
+ k *= m;
+ k ^= k >> r;
+ k *= m;
+
+ h ^= k;
+ h *= m;
+ }
+
+ auto const data8 = reinterpret_cast<uint8_t const*>(data64 + n_blocks);
+ switch (len & 7U) {
+ case 7:
+ h ^= static_cast<uint64_t>(data8[6]) << 48U;
+ ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH
+ case 6:
+ h ^= static_cast<uint64_t>(data8[5]) << 40U;
+ ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH
+ case 5:
+ h ^= static_cast<uint64_t>(data8[4]) << 32U;
+ ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH
+ case 4:
+ h ^= static_cast<uint64_t>(data8[3]) << 24U;
+ ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH
+ case 3:
+ h ^= static_cast<uint64_t>(data8[2]) << 16U;
+ ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH
+ case 2:
+ h ^= static_cast<uint64_t>(data8[1]) << 8U;
+ ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH
+ case 1:
+ h ^= static_cast<uint64_t>(data8[0]);
+ h *= m;
+ ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH
+ default:
+ break;
+ }
+
+ h ^= h >> r;
+ h *= m;
+ h ^= h >> r;
+ return static_cast<size_t>(h);
+}
+
+inline size_t hash_int(uint64_t obj) noexcept {
+#if ROBIN_HOOD(HAS_UMUL128)
+ // 167079903232 masksum, 120428523 ops best: 0xde5fb9d2630458e9
+ static constexpr uint64_t k = UINT64_C(0xde5fb9d2630458e9);
+ uint64_t h;
+ uint64_t l = detail::umul128(obj, k, &h);
+ return h + l;
+#elif ROBIN_HOOD(BITNESS) == 32
+ uint64_t const r = obj * UINT64_C(0xca4bcaa75ec3f625);
+ auto h = static_cast<uint32_t>(r >> 32U);
+ auto l = static_cast<uint32_t>(r);
+ return h + l;
+#else
+ // murmurhash 3 finalizer
+ uint64_t h = obj;
+ h ^= h >> 33;
+ h *= 0xff51afd7ed558ccd;
+ h ^= h >> 33;
+ h *= 0xc4ceb9fe1a85ec53;
+ h ^= h >> 33;
+ return static_cast<size_t>(h);
+#endif
+}
+
+// A thin wrapper around std::hash, performing an additional simple mixing step of the result.
+template <typename T>
+struct hash : public std::hash<T> {
+ size_t operator()(T const& obj) const
+ noexcept(noexcept(std::declval<std::hash<T>>().operator()(std::declval<T const&>()))) {
+ // call base hash
+ auto result = std::hash<T>::operator()(obj);
+ // return mixed of that, to be save against identity has
+ return hash_int(static_cast<uint64_t>(result));
+ }
+};
+
+template <>
+struct hash<std::string> {
+ size_t operator()(std::string const& str) const noexcept {
+ return hash_bytes(str.data(), str.size());
+ }
+};
+
+template <class T>
+struct hash<T*> {
+ size_t operator()(T* ptr) const noexcept {
+ return hash_int(reinterpret_cast<size_t>(ptr));
+ }
+};
+
+#define ROBIN_HOOD_HASH_INT(T) \
+ template <> \
+ struct hash<T> { \
+ size_t operator()(T obj) const noexcept { \
+ return hash_int(static_cast<uint64_t>(obj)); \
+ } \
+ }
+
+#if defined(__GNUC__) && !defined(__clang__)
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wuseless-cast"
+#endif
+// see https://en.cppreference.com/w/cpp/utility/hash
+ROBIN_HOOD_HASH_INT(bool);
+ROBIN_HOOD_HASH_INT(char);
+ROBIN_HOOD_HASH_INT(signed char);
+ROBIN_HOOD_HASH_INT(unsigned char);
+ROBIN_HOOD_HASH_INT(char16_t);
+ROBIN_HOOD_HASH_INT(char32_t);
+ROBIN_HOOD_HASH_INT(wchar_t);
+ROBIN_HOOD_HASH_INT(short);
+ROBIN_HOOD_HASH_INT(unsigned short);
+ROBIN_HOOD_HASH_INT(int);
+ROBIN_HOOD_HASH_INT(unsigned int);
+ROBIN_HOOD_HASH_INT(long);
+ROBIN_HOOD_HASH_INT(long long);
+ROBIN_HOOD_HASH_INT(unsigned long);
+ROBIN_HOOD_HASH_INT(unsigned long long);
+#if defined(__GNUC__) && !defined(__clang__)
+# pragma GCC diagnostic pop
+#endif
+namespace detail {
+
+// using wrapper classes for hash and key_equal prevents the diamond problem when the same type is
+// used. see https://stackoverflow.com/a/28771920/48181
+template <typename T>
+struct WrapHash : public T {
+ WrapHash() = default;
+ explicit WrapHash(T const& o) noexcept(noexcept(T(std::declval<T const&>())))
+ : T(o) {}
+};
+
+template <typename T>
+struct WrapKeyEqual : public T {
+ WrapKeyEqual() = default;
+ explicit WrapKeyEqual(T const& o) noexcept(noexcept(T(std::declval<T const&>())))
+ : T(o) {}
+};
+
+// A highly optimized hashmap implementation, using the Robin Hood algorithm.
+//
+// In most cases, this map should be usable as a drop-in replacement for std::unordered_map, but be
+// about 2x faster in most cases and require much less allocations.
+//
+// This implementation uses the following memory layout:
+//
+// [Node, Node, ... Node | info, info, ... infoSentinel ]
+//
+// * Node: either a DataNode that directly has the std::pair<key, val> as member,
+// or a DataNode with a pointer to std::pair<key,val>. Which DataNode representation to use
+// depends on how fast the swap() operation is. Heuristically, this is automatically choosen based
+// on sizeof(). there are always 2^n Nodes.
+//
+// * info: Each Node in the map has a corresponding info byte, so there are 2^n info bytes.
+// Each byte is initialized to 0, meaning the corresponding Node is empty. Set to 1 means the
+// corresponding node contains data. Set to 2 means the corresponding Node is filled, but it
+// actually belongs to the previous position and was pushed out because that place is already
+// taken.
+//
+// * infoSentinel: Sentinel byte set to 1, so that iterator's ++ can stop at end() without the need
+// for a idx
+// variable.
+//
+// According to STL, order of templates has effect on throughput. That's why I've moved the boolean
+// to the front.
+// https://www.reddit.com/r/cpp/comments/ahp6iu/compile_time_binary_size_reductions_and_cs_future/eeguck4/
+template <bool IsFlatMap, size_t MaxLoadFactor100, typename Key, typename T, typename Hash,
+ typename KeyEqual>
+class unordered_map
+ : public WrapHash<Hash>,
+ public WrapKeyEqual<KeyEqual>,
+ detail::NodeAllocator<
+ robin_hood::pair<typename std::conditional<IsFlatMap, Key, Key const>::type, T>, 4, 16384,
+ IsFlatMap> {
+public:
+ using key_type = Key;
+ using mapped_type = T;
+ using value_type =
+ robin_hood::pair<typename std::conditional<IsFlatMap, Key, Key const>::type, T>;
+ using size_type = size_t;
+ using hasher = Hash;
+ using key_equal = KeyEqual;
+ using Self =
+ unordered_map<IsFlatMap, MaxLoadFactor100, key_type, mapped_type, hasher, key_equal>;
+ static constexpr bool is_flat_map = IsFlatMap;
+
+private:
+ static_assert(MaxLoadFactor100 > 10 && MaxLoadFactor100 < 100,
+ "MaxLoadFactor100 needs to be >10 && < 100");
+
+ using WHash = WrapHash<Hash>;
+ using WKeyEqual = WrapKeyEqual<KeyEqual>;
+
+ // configuration defaults
+
+ // make sure we have 8 elements, needed to quickly rehash mInfo
+ static constexpr size_t InitialNumElements = sizeof(uint64_t);
+ static constexpr uint32_t InitialInfoNumBits = 5;
+ static constexpr uint8_t InitialInfoInc = 1U << InitialInfoNumBits;
+ static constexpr uint8_t InitialInfoHashShift = sizeof(size_t) * 8 - InitialInfoNumBits;
+ using DataPool = detail::NodeAllocator<value_type, 4, 16384, IsFlatMap>;
+
+ // type needs to be wider than uint8_t.
+ using InfoType = uint32_t;
+
+ // DataNode ////////////////////////////////////////////////////////
+
+ // Primary template for the data node. We have special implementations for small and big
+ // objects. For large objects it is assumed that swap() is fairly slow, so we allocate these on
+ // the heap so swap merely swaps a pointer.
+ template <typename M, bool>
+ class DataNode {};
+
+ // Small: just allocate on the stack.
+ template <typename M>
+ class DataNode<M, true> final {
+ public:
+ template <typename... Args>
+ explicit DataNode(M& ROBIN_HOOD_UNUSED(map) /*unused*/, Args&&... args) noexcept(
+ noexcept(value_type(std::forward<Args>(args)...)))
+ : mData(std::forward<Args>(args)...) {}
+
+ DataNode(M& ROBIN_HOOD_UNUSED(map) /*unused*/, DataNode<M, true>&& n) noexcept(
+ std::is_nothrow_move_constructible<value_type>::value)
+ : mData(std::move(n.mData)) {}
+
+ // doesn't do anything
+ void destroy(M& ROBIN_HOOD_UNUSED(map) /*unused*/) noexcept {}
+ void destroyDoNotDeallocate() noexcept {}
+
+ value_type const* operator->() const noexcept {
+ return &mData;
+ }
+ value_type* operator->() noexcept {
+ return &mData;
+ }
+
+ const value_type& operator*() const noexcept {
+ return mData;
+ }
+
+ value_type& operator*() noexcept {
+ return mData;
+ }
+
+ ROBIN_HOOD(NODISCARD) typename value_type::first_type& getFirst() noexcept {
+ return mData.first;
+ }
+
+ ROBIN_HOOD(NODISCARD) typename value_type::first_type const& getFirst() const noexcept {
+ return mData.first;
+ }
+
+ ROBIN_HOOD(NODISCARD) typename value_type::second_type& getSecond() noexcept {
+ return mData.second;
+ }
+
+ ROBIN_HOOD(NODISCARD) typename value_type::second_type const& getSecond() const noexcept {
+ return mData.second;
+ }
+
+ void swap(DataNode<M, true>& o) noexcept(
+ noexcept(std::declval<value_type>().swap(std::declval<value_type>()))) {
+ mData.swap(o.mData);
+ }
+
+ private:
+ value_type mData;
+ };
+
+ // big object: allocate on heap.
+ template <typename M>
+ class DataNode<M, false> {
+ public:
+ template <typename... Args>
+ explicit DataNode(M& map, Args&&... args)
+ : mData(map.allocate()) {
+ ::new (static_cast<void*>(mData)) value_type(std::forward<Args>(args)...);
+ }
+
+ DataNode(M& ROBIN_HOOD_UNUSED(map) /*unused*/, DataNode<M, false>&& n) noexcept
+ : mData(std::move(n.mData)) {}
+
+ void destroy(M& map) noexcept {
+ // don't deallocate, just put it into list of datapool.
+ mData->~value_type();
+ map.deallocate(mData);
+ }
+
+ void destroyDoNotDeallocate() noexcept {
+ mData->~value_type();
+ }
+
+ value_type const* operator->() const noexcept {
+ return mData;
+ }
+
+ value_type* operator->() noexcept {
+ return mData;
+ }
+
+ const value_type& operator*() const {
+ return *mData;
+ }
+
+ value_type& operator*() {
+ return *mData;
+ }
+
+ ROBIN_HOOD(NODISCARD) typename value_type::first_type& getFirst() {
+ return mData->first;
+ }
+
+ ROBIN_HOOD(NODISCARD) typename value_type::first_type const& getFirst() const {
+ return mData->first;
+ }
+
+ ROBIN_HOOD(NODISCARD) typename value_type::second_type& getSecond() {
+ return mData->second;
+ }
+
+ ROBIN_HOOD(NODISCARD) typename value_type::second_type const& getSecond() const {
+ return mData->second;
+ }
+
+ void swap(DataNode<M, false>& o) noexcept {
+ using std::swap;
+ swap(mData, o.mData);
+ }
+
+ private:
+ value_type* mData;
+ };
+
+ using Node = DataNode<Self, IsFlatMap>;
+
+ // Cloner //////////////////////////////////////////////////////////
+
+ template <typename M, bool UseMemcpy>
+ struct Cloner;
+
+ // fast path: Just copy data, without allocating anything.
+ template <typename M>
+ struct Cloner<M, true> {
+ void operator()(M const& source, M& target) const {
+ // std::memcpy(target.mKeyVals, source.mKeyVals,
+ // target.calcNumBytesTotal(target.mMask + 1));
+ auto src = reinterpret_cast<char const*>(source.mKeyVals);
+ auto tgt = reinterpret_cast<char*>(target.mKeyVals);
+ std::copy(src, src + target.calcNumBytesTotal(target.mMask + 1), tgt);
+ }
+ };
+
+ template <typename M>
+ struct Cloner<M, false> {
+ void operator()(M const& s, M& t) const {
+ std::copy(s.mInfo, s.mInfo + t.calcNumBytesInfo(t.mMask + 1), t.mInfo);
+
+ for (size_t i = 0; i < t.mMask + 1; ++i) {
+ if (t.mInfo[i]) {
+ ::new (static_cast<void*>(t.mKeyVals + i)) Node(t, *s.mKeyVals[i]);
+ }
+ }
+ }
+ };
+
+ // Destroyer ///////////////////////////////////////////////////////
+
+ template <typename M, bool IsFlatMapAndTrivial>
+ struct Destroyer {};
+
+ template <typename M>
+ struct Destroyer<M, true> {
+ void nodes(M& m) const noexcept {
+ m.mNumElements = 0;
+ }
+
+ void nodesDoNotDeallocate(M& m) const noexcept {
+ m.mNumElements = 0;
+ }
+ };
+
+ template <typename M>
+ struct Destroyer<M, false> {
+ void nodes(M& m) const noexcept {
+ m.mNumElements = 0;
+ // clear also resets mInfo to 0, that's sometimes not necessary.
+ for (size_t idx = 0; idx <= m.mMask; ++idx) {
+ if (0 != m.mInfo[idx]) {
+ Node& n = m.mKeyVals[idx];
+ n.destroy(m);
+ n.~Node();
+ }
+ }
+ }
+
+ void nodesDoNotDeallocate(M& m) const noexcept {
+ m.mNumElements = 0;
+ // clear also resets mInfo to 0, that's sometimes not necessary.
+ for (size_t idx = 0; idx <= m.mMask; ++idx) {
+ if (0 != m.mInfo[idx]) {
+ Node& n = m.mKeyVals[idx];
+ n.destroyDoNotDeallocate();
+ n.~Node();
+ }
+ }
+ }
+ };
+
+ // Iter ////////////////////////////////////////////////////////////
+
+ struct fast_forward_tag {};
+
+ // generic iterator for both const_iterator and iterator.
+ template <bool IsConst>
+ // NOLINTNEXTLINE(hicpp-special-member-functions,cppcoreguidelines-special-member-functions)
+ class Iter {
+ private:
+ using NodePtr = typename std::conditional<IsConst, Node const*, Node*>::type;
+
+ public:
+ using difference_type = std::ptrdiff_t;
+ using value_type = typename Self::value_type;
+ using reference = typename std::conditional<IsConst, value_type const&, value_type&>::type;
+ using pointer = typename std::conditional<IsConst, value_type const*, value_type*>::type;
+ using iterator_category = std::forward_iterator_tag;
+
+ // default constructed iterator can be compared to itself, but WON'T return true when
+ // compared to end().
+ Iter() = default;
+
+ // Rule of zero: nothing specified. The conversion constructor is only enabled for iterator
+ // to const_iterator, so it doesn't accidentally work as a copy ctor.
+
+ // Conversion constructor from iterator to const_iterator.
+ template <bool OtherIsConst,
+ typename = typename std::enable_if<IsConst && !OtherIsConst>::type>
+ // NOLINTNEXTLINE(hicpp-explicit-conversions)
+ Iter(Iter<OtherIsConst> const& other) noexcept
+ : mKeyVals(other.mKeyVals)
+ , mInfo(other.mInfo) {}
+
+ Iter(NodePtr valPtr, uint8_t const* infoPtr) noexcept
+ : mKeyVals(valPtr)
+ , mInfo(infoPtr) {}
+
+ Iter(NodePtr valPtr, uint8_t const* infoPtr,
+ fast_forward_tag ROBIN_HOOD_UNUSED(tag) /*unused*/) noexcept
+ : mKeyVals(valPtr)
+ , mInfo(infoPtr) {
+ fastForward();
+ }
+
+ template <bool OtherIsConst,
+ typename = typename std::enable_if<IsConst && !OtherIsConst>::type>
+ Iter& operator=(Iter<OtherIsConst> const& other) noexcept {
+ mKeyVals = other.mKeyVals;
+ mInfo = other.mInfo;
+ return *this;
+ }
+
+ // prefix increment. Undefined behavior if we are at end()!
+ Iter& operator++() noexcept {
+ mInfo++;
+ mKeyVals++;
+ fastForward();
+ return *this;
+ }
+
+ reference operator*() const {
+ return **mKeyVals;
+ }
+
+ pointer operator->() const {
+ return &**mKeyVals;
+ }
+
+ template <bool O>
+ bool operator==(Iter<O> const& o) const noexcept {
+ return mKeyVals == o.mKeyVals;
+ }
+
+ template <bool O>
+ bool operator!=(Iter<O> const& o) const noexcept {
+ return mKeyVals != o.mKeyVals;
+ }
+
+ private:
+ // fast forward to the next non-free info byte
+ void fastForward() noexcept {
+ int inc;
+ do {
+ auto const n = detail::unaligned_load<size_t>(mInfo);
+#if ROBIN_HOOD(LITTLE_ENDIAN)
+ inc = ROBIN_HOOD_COUNT_TRAILING_ZEROES(n) / 8;
+#else
+ inc = ROBIN_HOOD_COUNT_LEADING_ZEROES(n) / 8;
+#endif
+ mInfo += inc;
+ mKeyVals += inc;
+ } while (inc == static_cast<int>(sizeof(size_t)));
+ }
+
+ friend class unordered_map<IsFlatMap, MaxLoadFactor100, key_type, mapped_type, hasher,
+ key_equal>;
+ NodePtr mKeyVals{nullptr};
+ uint8_t const* mInfo{nullptr};
+ };
+
+ ////////////////////////////////////////////////////////////////////
+
+ // highly performance relevant code.
+ // Lower bits are used for indexing into the array (2^n size)
+ // The upper 1-5 bits need to be a reasonable good hash, to save comparisons.
+ template <typename HashKey>
+ void keyToIdx(HashKey&& key, size_t* idx, InfoType* info) const {
+ // for a user-specified hash that is *not* robin_hood::hash, apply robin_hood::hash as an
+ // additional mixing step. This serves as a bad hash prevention, if the given data is badly
+ // mixed.
+ using Mix =
+ typename std::conditional<std::is_same<::robin_hood::hash<key_type>, hasher>::value,
+ ::robin_hood::detail::identity_hash<size_t>,
+ ::robin_hood::hash<size_t>>::type;
+ *idx = Mix{}(WHash::operator()(key));
+
+ *info = mInfoInc + static_cast<InfoType>(*idx >> mInfoHashShift);
+ *idx &= mMask;
+ }
+
+ // forwards the index by one, wrapping around at the end
+ void next(InfoType* info, size_t* idx) const noexcept {
+ *idx = (*idx + 1) & mMask;
+ *info += mInfoInc;
+ }
+
+ void nextWhileLess(InfoType* info, size_t* idx) const noexcept {
+ // unrolling this by hand did not bring any speedups.
+ while (*info < mInfo[*idx]) {
+ next(info, idx);
+ }
+ }
+
+ // Shift everything up by one element. Tries to move stuff around.
+ // True if some shifting has occured (entry under idx is a constructed object)
+ // Fals if no shift has occured (entry under idx is unconstructed memory)
+ void
+ shiftUp(size_t idx,
+ size_t const insertion_idx) noexcept(std::is_nothrow_move_assignable<Node>::value) {
+ while (idx != insertion_idx) {
+ size_t prev_idx = (idx - 1) & mMask;
+ if (mInfo[idx]) {
+ mKeyVals[idx] = std::move(mKeyVals[prev_idx]);
+ } else {
+ ::new (static_cast<void*>(mKeyVals + idx)) Node(std::move(mKeyVals[prev_idx]));
+ }
+ mInfo[idx] = static_cast<uint8_t>(mInfo[prev_idx] + mInfoInc);
+ if (ROBIN_HOOD_UNLIKELY(mInfo[idx] + mInfoInc > 0xFF)) {
+ mMaxNumElementsAllowed = 0;
+ }
+ idx = prev_idx;
+ }
+ }
+
+ void shiftDown(size_t idx) noexcept(std::is_nothrow_move_assignable<Node>::value) {
+ // until we find one that is either empty or has zero offset.
+ // TODO(martinus) we don't need to move everything, just the last one for the same bucket.
+ mKeyVals[idx].destroy(*this);
+
+ // until we find one that is either empty or has zero offset.
+ size_t nextIdx = (idx + 1) & mMask;
+ while (mInfo[nextIdx] >= 2 * mInfoInc) {
+ mInfo[idx] = static_cast<uint8_t>(mInfo[nextIdx] - mInfoInc);
+ mKeyVals[idx] = std::move(mKeyVals[nextIdx]);
+ idx = nextIdx;
+ nextIdx = (idx + 1) & mMask;
+ }
+
+ mInfo[idx] = 0;
+ // don't destroy, we've moved it
+ // mKeyVals[idx].destroy(*this);
+ mKeyVals[idx].~Node();
+ }
+
+ // copy of find(), except that it returns iterator instead of const_iterator.
+ template <typename Other>
+ ROBIN_HOOD(NODISCARD)
+ size_t findIdx(Other const& key) const {
+ size_t idx;
+ InfoType info;
+ keyToIdx(key, &idx, &info);
+
+ do {
+ // unrolling this twice gives a bit of a speedup. More unrolling did not help.
+ if (info == mInfo[idx] && WKeyEqual::operator()(key, mKeyVals[idx].getFirst())) {
+ return idx;
+ }
+ next(&info, &idx);
+ if (info == mInfo[idx] && WKeyEqual::operator()(key, mKeyVals[idx].getFirst())) {
+ return idx;
+ }
+ next(&info, &idx);
+ } while (info <= mInfo[idx]);
+
+ // nothing found!
+ return mMask == 0 ? 0 : mMask + 1;
+ }
+
+ void cloneData(const unordered_map& o) {
+ Cloner<unordered_map, IsFlatMap && ROBIN_HOOD_IS_TRIVIALLY_COPYABLE(Node)>()(o, *this);
+ }
+
+ // inserts a keyval that is guaranteed to be new, e.g. when the hashmap is resized.
+ // @return index where the element was created
+ size_t insert_move(Node&& keyval) {
+ // we don't retry, fail if overflowing
+ // don't need to check max num elements
+ if (0 == mMaxNumElementsAllowed && !try_increase_info()) {
+ throwOverflowError();
+ }
+
+ size_t idx;
+ InfoType info;
+ keyToIdx(keyval.getFirst(), &idx, &info);
+
+ // skip forward. Use <= because we are certain that the element is not there.
+ while (info <= mInfo[idx]) {
+ idx = (idx + 1) & mMask;
+ info += mInfoInc;
+ }
+
+ // key not found, so we are now exactly where we want to insert it.
+ auto const insertion_idx = idx;
+ auto const insertion_info = static_cast<uint8_t>(info);
+ if (ROBIN_HOOD_UNLIKELY(insertion_info + mInfoInc > 0xFF)) {
+ mMaxNumElementsAllowed = 0;
+ }
+
+ // find an empty spot
+ while (0 != mInfo[idx]) {
+ next(&info, &idx);
+ }
+
+ auto& l = mKeyVals[insertion_idx];
+ if (idx == insertion_idx) {
+ ::new (static_cast<void*>(&l)) Node(std::move(keyval));
+ } else {
+ shiftUp(idx, insertion_idx);
+ l = std::move(keyval);
+ }
+
+ // put at empty spot
+ mInfo[insertion_idx] = insertion_info;
+
+ ++mNumElements;
+ return insertion_idx;
+ }
+
+public:
+ using iterator = Iter<false>;
+ using const_iterator = Iter<true>;
+
+ // Creates an empty hash map. Nothing is allocated yet, this happens at the first insert. This
+ // tremendously speeds up ctor & dtor of a map that never receives an element. The penalty is
+ // payed at the first insert, and not before. Lookup of this empty map works because everybody
+ // points to DummyInfoByte::b. parameter bucket_count is dictated by the standard, but we can
+ // ignore it.
+ explicit unordered_map(size_t ROBIN_HOOD_UNUSED(bucket_count) /*unused*/ = 0,
+ const Hash& h = Hash{},
+ const KeyEqual& equal = KeyEqual{}) noexcept(noexcept(Hash(h)) &&
+ noexcept(KeyEqual(equal)))
+ : WHash(h)
+ , WKeyEqual(equal) {
+ ROBIN_HOOD_TRACE(this);
+ }
+
+ template <typename Iter>
+ unordered_map(Iter first, Iter last, size_t ROBIN_HOOD_UNUSED(bucket_count) /*unused*/ = 0,
+ const Hash& h = Hash{}, const KeyEqual& equal = KeyEqual{})
+ : WHash(h)
+ , WKeyEqual(equal) {
+ ROBIN_HOOD_TRACE(this);
+ insert(first, last);
+ }
+
+ unordered_map(std::initializer_list<value_type> initlist,
+ size_t ROBIN_HOOD_UNUSED(bucket_count) /*unused*/ = 0, const Hash& h = Hash{},
+ const KeyEqual& equal = KeyEqual{})
+ : WHash(h)
+ , WKeyEqual(equal) {
+ ROBIN_HOOD_TRACE(this);
+ insert(initlist.begin(), initlist.end());
+ }
+
+ unordered_map(unordered_map&& o) noexcept
+ : WHash(std::move(static_cast<WHash&>(o)))
+ , WKeyEqual(std::move(static_cast<WKeyEqual&>(o)))
+ , DataPool(std::move(static_cast<DataPool&>(o))) {
+ ROBIN_HOOD_TRACE(this);
+ if (o.mMask) {
+ mKeyVals = std::move(o.mKeyVals);
+ mInfo = std::move(o.mInfo);
+ mNumElements = std::move(o.mNumElements);
+ mMask = std::move(o.mMask);
+ mMaxNumElementsAllowed = std::move(o.mMaxNumElementsAllowed);
+ mInfoInc = std::move(o.mInfoInc);
+ mInfoHashShift = std::move(o.mInfoHashShift);
+ // set other's mask to 0 so its destructor won't do anything
+ o.init();
+ }
+ }
+
+ unordered_map& operator=(unordered_map&& o) noexcept {
+ ROBIN_HOOD_TRACE(this);
+ if (&o != this) {
+ if (o.mMask) {
+ // only move stuff if the other map actually has some data
+ destroy();
+ mKeyVals = std::move(o.mKeyVals);
+ mInfo = std::move(o.mInfo);
+ mNumElements = std::move(o.mNumElements);
+ mMask = std::move(o.mMask);
+ mMaxNumElementsAllowed = std::move(o.mMaxNumElementsAllowed);
+ mInfoInc = std::move(o.mInfoInc);
+ mInfoHashShift = std::move(o.mInfoHashShift);
+ WHash::operator=(std::move(static_cast<WHash&>(o)));
+ WKeyEqual::operator=(std::move(static_cast<WKeyEqual&>(o)));
+ DataPool::operator=(std::move(static_cast<DataPool&>(o)));
+
+ o.init();
+
+ } else {
+ // nothing in the other map => just clear us.
+ clear();
+ }
+ }
+ return *this;
+ }
+
+ unordered_map(const unordered_map& o)
+ : WHash(static_cast<const WHash&>(o))
+ , WKeyEqual(static_cast<const WKeyEqual&>(o))
+ , DataPool(static_cast<const DataPool&>(o)) {
+ ROBIN_HOOD_TRACE(this);
+ if (!o.empty()) {
+ // not empty: create an exact copy. it is also possible to just iterate through all
+ // elements and insert them, but copying is probably faster.
+
+ mKeyVals = static_cast<Node*>(
+ detail::assertNotNull<std::bad_alloc>(malloc(calcNumBytesTotal(o.mMask + 1))));
+ // no need for calloc because clonData does memcpy
+ mInfo = reinterpret_cast<uint8_t*>(mKeyVals + o.mMask + 1);
+ mNumElements = o.mNumElements;
+ mMask = o.mMask;
+ mMaxNumElementsAllowed = o.mMaxNumElementsAllowed;
+ mInfoInc = o.mInfoInc;
+ mInfoHashShift = o.mInfoHashShift;
+ cloneData(o);
+ }
+ }
+
+ // Creates a copy of the given map. Copy constructor of each entry is used.
+ unordered_map& operator=(unordered_map const& o) {
+ ROBIN_HOOD_TRACE(this);
+ if (&o == this) {
+ // prevent assigning of itself
+ return *this;
+ }
+
+ // we keep using the old allocator and not assign the new one, because we want to keep the
+ // memory available. when it is the same size.
+ if (o.empty()) {
+ if (0 == mMask) {
+ // nothing to do, we are empty too
+ return *this;
+ }
+
+ // not empty: destroy what we have there
+ // clear also resets mInfo to 0, that's sometimes not necessary.
+ destroy();
+ init();
+ WHash::operator=(static_cast<const WHash&>(o));
+ WKeyEqual::operator=(static_cast<const WKeyEqual&>(o));
+ DataPool::operator=(static_cast<DataPool const&>(o));
+
+ return *this;
+ }
+
+ // clean up old stuff
+ Destroyer<Self, IsFlatMap && std::is_trivially_destructible<Node>::value>{}.nodes(*this);
+
+ if (mMask != o.mMask) {
+ // no luck: we don't have the same array size allocated, so we need to realloc.
+ if (0 != mMask) {
+ // only deallocate if we actually have data!
+ free(mKeyVals);
+ }
+
+ mKeyVals = static_cast<Node*>(
+ detail::assertNotNull<std::bad_alloc>(malloc(calcNumBytesTotal(o.mMask + 1))));
+
+ // no need for calloc here because cloneData performs a memcpy.
+ mInfo = reinterpret_cast<uint8_t*>(mKeyVals + o.mMask + 1);
+ // sentinel is set in cloneData
+ }
+ WHash::operator=(static_cast<const WHash&>(o));
+ WKeyEqual::operator=(static_cast<const WKeyEqual&>(o));
+ DataPool::operator=(static_cast<DataPool const&>(o));
+ mNumElements = o.mNumElements;
+ mMask = o.mMask;
+ mMaxNumElementsAllowed = o.mMaxNumElementsAllowed;
+ mInfoInc = o.mInfoInc;
+ mInfoHashShift = o.mInfoHashShift;
+ cloneData(o);
+
+ return *this;
+ }
+
+ // Swaps everything between the two maps.
+ void swap(unordered_map& o) {
+ ROBIN_HOOD_TRACE(this);
+ using std::swap;
+ swap(o, *this);
+ }
+
+ // Clears all data, without resizing.
+ void clear() {
+ ROBIN_HOOD_TRACE(this);
+ if (empty()) {
+ // don't do anything! also important because we don't want to write to DummyInfoByte::b,
+ // even though we would just write 0 to it.
+ return;
+ }
+
+ Destroyer<Self, IsFlatMap && std::is_trivially_destructible<Node>::value>{}.nodes(*this);
+
+ // clear everything except the sentinel
+ // std::memset(mInfo, 0, sizeof(uint8_t) * (mMask + 1));
+ uint8_t const z = 0;
+ std::fill(mInfo, mInfo + (sizeof(uint8_t) * (mMask + 1)), z);
+
+ mInfoInc = InitialInfoInc;
+ mInfoHashShift = InitialInfoHashShift;
+ }
+
+ // Destroys the map and all it's contents.
+ ~unordered_map() {
+ ROBIN_HOOD_TRACE(this);
+ destroy();
+ }
+
+ // Checks if both maps contain the same entries. Order is irrelevant.
+ bool operator==(const unordered_map& other) const {
+ ROBIN_HOOD_TRACE(this);
+ if (other.size() != size()) {
+ return false;
+ }
+ for (auto const& otherEntry : other) {
+ auto const myIt = find(otherEntry.first);
+ if (myIt == end() || !(myIt->second == otherEntry.second)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ bool operator!=(const unordered_map& other) const {
+ ROBIN_HOOD_TRACE(this);
+ return !operator==(other);
+ }
+
+ mapped_type& operator[](const key_type& key) {
+ ROBIN_HOOD_TRACE(this);
+ return doCreateByKey(key);
+ }
+
+ mapped_type& operator[](key_type&& key) {
+ ROBIN_HOOD_TRACE(this);
+ return doCreateByKey(std::move(key));
+ }
+
+ template <typename Iter>
+ void insert(Iter first, Iter last) {
+ for (; first != last; ++first) {
+ // value_type ctor needed because this might be called with std::pair's
+ insert(value_type(*first));
+ }
+ }
+
+ template <typename... Args>
+ std::pair<iterator, bool> emplace(Args&&... args) {
+ ROBIN_HOOD_TRACE(this);
+ Node n{*this, std::forward<Args>(args)...};
+ auto r = doInsert(std::move(n));
+ if (!r.second) {
+ // insertion not possible: destroy node
+ // NOLINTNEXTLINE(bugprone-use-after-move)
+ n.destroy(*this);
+ }
+ return r;
+ }
+
+ std::pair<iterator, bool> insert(const value_type& keyval) {
+ ROBIN_HOOD_TRACE(this);
+ return doInsert(keyval);
+ }
+
+ std::pair<iterator, bool> insert(value_type&& keyval) {
+ return doInsert(std::move(keyval));
+ }
+
+ // Returns 1 if key is found, 0 otherwise.
+ size_t count(const key_type& key) const { // NOLINT(modernize-use-nodiscard)
+ ROBIN_HOOD_TRACE(this);
+ auto kv = mKeyVals + findIdx(key);
+ if (kv != reinterpret_cast_no_cast_align_warning<Node*>(mInfo)) {
+ return 1;
+ }
+ return 0;
+ }
+
+ // Returns a reference to the value found for key.
+ // Throws std::out_of_range if element cannot be found
+ mapped_type& at(key_type const& key) {
+ ROBIN_HOOD_TRACE(this);
+ auto kv = mKeyVals + findIdx(key);
+ if (kv == reinterpret_cast_no_cast_align_warning<Node*>(mInfo)) {
+ doThrow<std::out_of_range>("key not found");
+ }
+ return kv->getSecond();
+ }
+
+ // Returns a reference to the value found for key.
+ // Throws std::out_of_range if element cannot be found
+ mapped_type const& at(key_type const& key) const { // NOLINT(modernize-use-nodiscard)
+ ROBIN_HOOD_TRACE(this);
+ auto kv = mKeyVals + findIdx(key);
+ if (kv == reinterpret_cast_no_cast_align_warning<Node*>(mInfo)) {
+ doThrow<std::out_of_range>("key not found");
+ }
+ return kv->getSecond();
+ }
+
+ const_iterator find(const key_type& key) const { // NOLINT(modernize-use-nodiscard)
+ ROBIN_HOOD_TRACE(this);
+ const size_t idx = findIdx(key);
+ return const_iterator{mKeyVals + idx, mInfo + idx};
+ }
+
+ template <typename OtherKey>
+ const_iterator find(const OtherKey& key, is_transparent_tag /*unused*/) const {
+ ROBIN_HOOD_TRACE(this);
+ const size_t idx = findIdx(key);
+ return const_iterator{mKeyVals + idx, mInfo + idx};
+ }
+
+ iterator find(const key_type& key) {
+ ROBIN_HOOD_TRACE(this);
+ const size_t idx = findIdx(key);
+ return iterator{mKeyVals + idx, mInfo + idx};
+ }
+
+ template <typename OtherKey>
+ iterator find(const OtherKey& key, is_transparent_tag /*unused*/) {
+ ROBIN_HOOD_TRACE(this);
+ const size_t idx = findIdx(key);
+ return iterator{mKeyVals + idx, mInfo + idx};
+ }
+
+ iterator begin() {
+ ROBIN_HOOD_TRACE(this);
+ if (empty()) {
+ return end();
+ }
+ return iterator(mKeyVals, mInfo, fast_forward_tag{});
+ }
+ const_iterator begin() const { // NOLINT(modernize-use-nodiscard)
+ ROBIN_HOOD_TRACE(this);
+ return cbegin();
+ }
+ const_iterator cbegin() const { // NOLINT(modernize-use-nodiscard)
+ ROBIN_HOOD_TRACE(this);
+ if (empty()) {
+ return cend();
+ }
+ return const_iterator(mKeyVals, mInfo, fast_forward_tag{});
+ }
+
+ iterator end() {
+ ROBIN_HOOD_TRACE(this);
+ // no need to supply valid info pointer: end() must not be dereferenced, and only node
+ // pointer is compared.
+ return iterator{reinterpret_cast_no_cast_align_warning<Node*>(mInfo), nullptr};
+ }
+ const_iterator end() const { // NOLINT(modernize-use-nodiscard)
+ ROBIN_HOOD_TRACE(this);
+ return cend();
+ }
+ const_iterator cend() const { // NOLINT(modernize-use-nodiscard)
+ ROBIN_HOOD_TRACE(this);
+ return const_iterator{reinterpret_cast_no_cast_align_warning<Node*>(mInfo), nullptr};
+ }
+
+ iterator erase(const_iterator pos) {
+ ROBIN_HOOD_TRACE(this);
+ // its safe to perform const cast here
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast)
+ return erase(iterator{const_cast<Node*>(pos.mKeyVals), const_cast<uint8_t*>(pos.mInfo)});
+ }
+
+ // Erases element at pos, returns iterator to the next element.
+ iterator erase(iterator pos) {
+ ROBIN_HOOD_TRACE(this);
+ // we assume that pos always points to a valid entry, and not end().
+ auto const idx = static_cast<size_t>(pos.mKeyVals - mKeyVals);
+
+ shiftDown(idx);
+ --mNumElements;
+
+ if (*pos.mInfo) {
+ // we've backward shifted, return this again
+ return pos;
+ }
+
+ // no backward shift, return next element
+ return ++pos;
+ }
+
+ size_t erase(const key_type& key) {
+ ROBIN_HOOD_TRACE(this);
+ size_t idx;
+ InfoType info;
+ keyToIdx(key, &idx, &info);
+
+ // check while info matches with the source idx
+ do {
+ if (info == mInfo[idx] && WKeyEqual::operator()(key, mKeyVals[idx].getFirst())) {
+ shiftDown(idx);
+ --mNumElements;
+ return 1;
+ }
+ next(&info, &idx);
+ } while (info <= mInfo[idx]);
+
+ // nothing found to delete
+ return 0;
+ }
+
+ // reserves space for the specified number of elements. Makes sure the old data fits.
+ // exactly the same as reserve(c).
+ void rehash(size_t c) {
+ reserve(c);
+ }
+
+ // reserves space for the specified number of elements. Makes sure the old data fits.
+ // Exactly the same as resize(c). Use resize(0) to shrink to fit.
+ void reserve(size_t c) {
+ ROBIN_HOOD_TRACE(this);
+ auto const minElementsAllowed = (std::max)(c, mNumElements);
+ auto newSize = InitialNumElements;
+ while (calcMaxNumElementsAllowed(newSize) < minElementsAllowed && newSize != 0) {
+ newSize *= 2;
+ }
+ if (ROBIN_HOOD_UNLIKELY(newSize == 0)) {
+ throwOverflowError();
+ }
+
+ rehashPowerOfTwo(newSize);
+ }
+
+ size_type size() const noexcept { // NOLINT(modernize-use-nodiscard)
+ ROBIN_HOOD_TRACE(this);
+ return mNumElements;
+ }
+
+ size_type max_size() const noexcept { // NOLINT(modernize-use-nodiscard)
+ ROBIN_HOOD_TRACE(this);
+ return static_cast<size_type>(-1);
+ }
+
+ ROBIN_HOOD(NODISCARD) bool empty() const noexcept {
+ ROBIN_HOOD_TRACE(this);
+ return 0 == mNumElements;
+ }
+
+ float max_load_factor() const noexcept { // NOLINT(modernize-use-nodiscard)
+ ROBIN_HOOD_TRACE(this);
+ return MaxLoadFactor100 / 100.0F;
+ }
+
+ // Average number of elements per bucket. Since we allow only 1 per bucket
+ float load_factor() const noexcept { // NOLINT(modernize-use-nodiscard)
+ ROBIN_HOOD_TRACE(this);
+ return static_cast<float>(size()) / static_cast<float>(mMask + 1);
+ }
+
+ ROBIN_HOOD(NODISCARD) size_t mask() const noexcept {
+ ROBIN_HOOD_TRACE(this);
+ return mMask;
+ }
+
+ ROBIN_HOOD(NODISCARD) size_t calcMaxNumElementsAllowed(size_t maxElements) const noexcept {
+ if (ROBIN_HOOD_LIKELY(maxElements <= (std::numeric_limits<size_t>::max)() / 100)) {
+ return maxElements * MaxLoadFactor100 / 100;
+ }
+
+ // we might be a bit inprecise, but since maxElements is quite large that doesn't matter
+ return (maxElements / 100) * MaxLoadFactor100;
+ }
+
+ ROBIN_HOOD(NODISCARD) size_t calcNumBytesInfo(size_t numElements) const {
+ return numElements + sizeof(uint64_t);
+ }
+
+ // calculation ony allowed for 2^n values
+ ROBIN_HOOD(NODISCARD) size_t calcNumBytesTotal(size_t numElements) const {
+#if ROBIN_HOOD(BITNESS) == 64
+ return numElements * sizeof(Node) + calcNumBytesInfo(numElements);
+#else
+ // make sure we're doing 64bit operations, so we are at least safe against 32bit overflows.
+ auto const ne = static_cast<uint64_t>(numElements);
+ auto const s = static_cast<uint64_t>(sizeof(Node));
+ auto const infos = static_cast<uint64_t>(calcNumBytesInfo(numElements));
+
+ auto const total64 = ne * s + infos;
+ auto const total = static_cast<size_t>(total64);
+
+ if (ROBIN_HOOD_UNLIKELY(static_cast<uint64_t>(total) != total64)) {
+ throwOverflowError();
+ }
+ return total;
+#endif
+ }
+
+private:
+ // reserves space for at least the specified number of elements.
+ // only works if numBuckets if power of two
+ void rehashPowerOfTwo(size_t numBuckets) {
+ ROBIN_HOOD_TRACE(this);
+
+ Node* const oldKeyVals = mKeyVals;
+ uint8_t const* const oldInfo = mInfo;
+
+ const size_t oldMaxElements = mMask + 1;
+
+ // resize operation: move stuff
+ init_data(numBuckets);
+ if (oldMaxElements > 1) {
+ for (size_t i = 0; i < oldMaxElements; ++i) {
+ if (oldInfo[i] != 0) {
+ insert_move(std::move(oldKeyVals[i]));
+ // destroy the node but DON'T destroy the data.
+ oldKeyVals[i].~Node();
+ }
+ }
+
+ // don't destroy old data: put it into the pool instead
+ DataPool::addOrFree(oldKeyVals, calcNumBytesTotal(oldMaxElements));
+ }
+ }
+
+ ROBIN_HOOD(NOINLINE) void throwOverflowError() const {
+#if ROBIN_HOOD(HAS_EXCEPTIONS)
+ throw std::overflow_error("robin_hood::map overflow");
+#else
+ abort();
+#endif
+ }
+
+ void init_data(size_t max_elements) {
+ mNumElements = 0;
+ mMask = max_elements - 1;
+ mMaxNumElementsAllowed = calcMaxNumElementsAllowed(max_elements);
+
+ // calloc also zeroes everything
+ mKeyVals = reinterpret_cast<Node*>(
+ detail::assertNotNull<std::bad_alloc>(calloc(1, calcNumBytesTotal(max_elements))));
+ mInfo = reinterpret_cast<uint8_t*>(mKeyVals + max_elements);
+
+ // set sentinel
+ mInfo[max_elements] = 1;
+
+ mInfoInc = InitialInfoInc;
+ mInfoHashShift = InitialInfoHashShift;
+ }
+
+ template <typename Arg>
+ mapped_type& doCreateByKey(Arg&& key) {
+ while (true) {
+ size_t idx;
+ InfoType info;
+ keyToIdx(key, &idx, &info);
+ nextWhileLess(&info, &idx);
+
+ // while we potentially have a match. Can't do a do-while here because when mInfo is 0
+ // we don't want to skip forward
+ while (info == mInfo[idx]) {
+ if (WKeyEqual::operator()(key, mKeyVals[idx].getFirst())) {
+ // key already exists, do not insert.
+ return mKeyVals[idx].getSecond();
+ }
+ next(&info, &idx);
+ }
+
+ // unlikely that this evaluates to true
+ if (ROBIN_HOOD_UNLIKELY(mNumElements >= mMaxNumElementsAllowed)) {
+ increase_size();
+ continue;
+ }
+
+ // key not found, so we are now exactly where we want to insert it.
+ auto const insertion_idx = idx;
+ auto const insertion_info = info;
+ if (ROBIN_HOOD_UNLIKELY(insertion_info + mInfoInc > 0xFF)) {
+ mMaxNumElementsAllowed = 0;
+ }
+
+ // find an empty spot
+ while (0 != mInfo[idx]) {
+ next(&info, &idx);
+ }
+
+ auto& l = mKeyVals[insertion_idx];
+ if (idx == insertion_idx) {
+ // put at empty spot. This forwards all arguments into the node where the object is
+ // constructed exactly where it is needed.
+ ::new (static_cast<void*>(&l))
+ Node(*this, std::piecewise_construct,
+ std::forward_as_tuple(std::forward<Arg>(key)), std::forward_as_tuple());
+ } else {
+ shiftUp(idx, insertion_idx);
+ l = Node(*this, std::piecewise_construct,
+ std::forward_as_tuple(std::forward<Arg>(key)), std::forward_as_tuple());
+ }
+
+ // mKeyVals[idx].getFirst() = std::move(key);
+ mInfo[insertion_idx] = static_cast<uint8_t>(insertion_info);
+
+ ++mNumElements;
+ return mKeyVals[insertion_idx].getSecond();
+ }
+ }
+
+ // This is exactly the same code as operator[], except for the return values
+ template <typename Arg>
+ std::pair<iterator, bool> doInsert(Arg&& keyval) {
+ while (true) {
+ size_t idx;
+ InfoType info;
+ keyToIdx(keyval.getFirst(), &idx, &info);
+ nextWhileLess(&info, &idx);
+
+ // while we potentially have a match
+ while (info == mInfo[idx]) {
+ if (WKeyEqual::operator()(keyval.getFirst(), mKeyVals[idx].getFirst())) {
+ // key already exists, do NOT insert.
+ // see http://en.cppreference.com/w/cpp/container/unordered_map/insert
+ return std::make_pair<iterator, bool>(iterator(mKeyVals + idx, mInfo + idx),
+ false);
+ }
+ next(&info, &idx);
+ }
+
+ // unlikely that this evaluates to true
+ if (ROBIN_HOOD_UNLIKELY(mNumElements >= mMaxNumElementsAllowed)) {
+ increase_size();
+ continue;
+ }
+
+ // key not found, so we are now exactly where we want to insert it.
+ auto const insertion_idx = idx;
+ auto const insertion_info = info;
+ if (ROBIN_HOOD_UNLIKELY(insertion_info + mInfoInc > 0xFF)) {
+ mMaxNumElementsAllowed = 0;
+ }
+
+ // find an empty spot
+ while (0 != mInfo[idx]) {
+ next(&info, &idx);
+ }
+
+ auto& l = mKeyVals[insertion_idx];
+ if (idx == insertion_idx) {
+ ::new (static_cast<void*>(&l)) Node(*this, std::forward<Arg>(keyval));
+ } else {
+ shiftUp(idx, insertion_idx);
+ l = Node(*this, std::forward<Arg>(keyval));
+ }
+
+ // put at empty spot
+ mInfo[insertion_idx] = static_cast<uint8_t>(insertion_info);
+
+ ++mNumElements;
+ return std::make_pair(iterator(mKeyVals + insertion_idx, mInfo + insertion_idx), true);
+ }
+ }
+
+ bool try_increase_info() {
+ ROBIN_HOOD_LOG("mInfoInc=" << mInfoInc << ", numElements=" << mNumElements
+ << ", maxNumElementsAllowed="
+ << calcMaxNumElementsAllowed(mMask + 1));
+ if (mInfoInc <= 2) {
+ // need to be > 2 so that shift works (otherwise undefined behavior!)
+ return false;
+ }
+ // we got space left, try to make info smaller
+ mInfoInc = static_cast<uint8_t>(mInfoInc >> 1U);
+
+ // remove one bit of the hash, leaving more space for the distance info.
+ // This is extremely fast because we can operate on 8 bytes at once.
+ ++mInfoHashShift;
+ auto const data = reinterpret_cast_no_cast_align_warning<uint64_t*>(mInfo);
+ auto const numEntries = (mMask + 1) / 8;
+
+ for (size_t i = 0; i < numEntries; ++i) {
+ data[i] = (data[i] >> 1U) & UINT64_C(0x7f7f7f7f7f7f7f7f);
+ }
+ mMaxNumElementsAllowed = calcMaxNumElementsAllowed(mMask + 1);
+ return true;
+ }
+
+ void increase_size() {
+ // nothing allocated yet? just allocate InitialNumElements
+ if (0 == mMask) {
+ init_data(InitialNumElements);
+ return;
+ }
+
+ auto const maxNumElementsAllowed = calcMaxNumElementsAllowed(mMask + 1);
+ if (mNumElements < maxNumElementsAllowed && try_increase_info()) {
+ return;
+ }
+
+ ROBIN_HOOD_LOG("mNumElements=" << mNumElements << ", maxNumElementsAllowed="
+ << maxNumElementsAllowed << ", load="
+ << (static_cast<double>(mNumElements) * 100.0 /
+ (static_cast<double>(mMask) + 1)));
+ // it seems we have a really bad hash function! don't try to resize again
+ if (mNumElements * 2 < calcMaxNumElementsAllowed(mMask + 1)) {
+ throwOverflowError();
+ }
+
+ rehashPowerOfTwo((mMask + 1) * 2);
+ }
+
+ void destroy() {
+ if (0 == mMask) {
+ // don't deallocate!
+ return;
+ }
+
+ Destroyer<Self, IsFlatMap && std::is_trivially_destructible<Node>::value>{}
+ .nodesDoNotDeallocate(*this);
+ free(mKeyVals);
+ }
+
+ void init() noexcept {
+ mKeyVals = reinterpret_cast<Node*>(&mMask);
+ mInfo = reinterpret_cast<uint8_t*>(&mMask);
+ mNumElements = 0;
+ mMask = 0;
+ mMaxNumElementsAllowed = 0;
+ mInfoInc = InitialInfoInc;
+ mInfoHashShift = InitialInfoHashShift;
+ }
+
+ // members are sorted so no padding occurs
+ Node* mKeyVals = reinterpret_cast<Node*>(&mMask); // 8 byte 8
+ uint8_t* mInfo = reinterpret_cast<uint8_t*>(&mMask); // 8 byte 16
+ size_t mNumElements = 0; // 8 byte 24
+ size_t mMask = 0; // 8 byte 32
+ size_t mMaxNumElementsAllowed = 0; // 8 byte 40
+ InfoType mInfoInc = InitialInfoInc; // 4 byte 44
+ InfoType mInfoHashShift = InitialInfoHashShift; // 4 byte 48
+ // 16 byte 56 if NodeAllocator
+};
+
+} // namespace detail
+
+template <typename Key, typename T, typename Hash = hash<Key>,
+ typename KeyEqual = std::equal_to<Key>, size_t MaxLoadFactor100 = 80>
+using unordered_flat_map = detail::unordered_map<true, MaxLoadFactor100, Key, T, Hash, KeyEqual>;
+
+template <typename Key, typename T, typename Hash = hash<Key>,
+ typename KeyEqual = std::equal_to<Key>, size_t MaxLoadFactor100 = 80>
+using unordered_node_map = detail::unordered_map<false, MaxLoadFactor100, Key, T, Hash, KeyEqual>;
+
+template <typename Key, typename T, typename Hash = hash<Key>,
+ typename KeyEqual = std::equal_to<Key>, size_t MaxLoadFactor100 = 80>
+using unordered_map =
+ detail::unordered_map<sizeof(robin_hood::pair<Key, T>) <= sizeof(size_t) * 6 &&
+ std::is_nothrow_move_constructible<robin_hood::pair<Key, T>>::value &&
+ std::is_nothrow_move_assignable<robin_hood::pair<Key, T>>::value,
+ MaxLoadFactor100, Key, T, Hash, KeyEqual>;
+
+} // namespace robin_hood
+
+#endif