/** This file is a part of rexy's general purpose library Copyright (C) 2020 rexy712 This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef REXY_CX_HASHMAP_HPP #define REXY_CX_HASHMAP_HPP #include "vector.hpp" #include "array.hpp" #include "../algorithm.hpp" #include "../hash.hpp" #include //CHAR_BIT #include //size_t, ptrdiff_t #include //pair #include //decay #include namespace rexy::cx{ template struct element{ using key_type = Key; using value_type = Value; Key key; Value value; }; template element(Key,Value) -> element; template> class hashmap { public: using key_type = Key; using mapped_type = Value; using value_type = element; using size_type = size_t; using difference_type = ptrdiff_t; using hasher = Hash; using reference = mapped_type&; using const_reference = const mapped_type&; using pointer = mapped_type*; using const_pointer = const mapped_type*; static constexpr size_type single_bucket_bit = size_type{1} << ((sizeof(size_type)*CHAR_BIT) - 1); static constexpr size_type max_size = N; static_assert((max_size & single_bucket_bit) == 0); private: array m_values; //perfect hash table array m_g; //'salt' values for indexing into the perfect hash table array m_key_hashes; //full hash values for keys to verify good index values public: constexpr hashmap(const value_type(&elements)[N]) noexcept(std::is_nothrow_default_constructible::value && std::is_nothrow_copy_constructible::value && std::is_nothrow_move_assignable::value && std::is_nothrow_invocable::value); //no key checks. give a correct key or get a random answer :) template>> constexpr reference operator[](U&& u)noexcept; template>> constexpr const_reference operator[](U&& u)const noexcept; template>> constexpr bool is_valid(U&& u)const noexcept; }; template constexpr hashmap::hashmap(const value_type(&elements)[N]) noexcept(std::is_nothrow_default_constructible::value && std::is_nothrow_copy_constructible::value && std::is_nothrow_move_assignable::value && std::is_nothrow_invocable::value) { array,N> buckets; size_type current_bucket = 0; //place all keys into buckets for(auto& element : elements){ buckets[Hash{}(element.key, 0) % max_size].push_back(element); } //sort the buckets based on size, largest first quicksort(buckets.begin(), buckets.end(), [](auto&& left, auto&& right) -> bool{ return left.size() > right.size(); }); //for each bucket, try different values of 'd' to try to find a hash that doesn't collide for(current_bucket = 0;current_bucket < buckets.size();++current_bucket){ auto& bucket = buckets[current_bucket]; //only handle buckets containing collisions if(bucket.size() <= 1) break; const auto hashval = Hash{}(bucket[0].key, 0); array pass_slots_used; vector pass_slots; size_type d = 1; for(size_type i = 0;i < bucket.size();){ size_type slot = Hash{}(bucket[i].key, d) % max_size; if(pass_slots_used[slot] || m_key_hashes[slot] != 0){ //slot already in use, try another value for 'd' ++d; i = 0; pass_slots_used.fill(false); pass_slots.clear(); }else{ //slot is good to go pass_slots_used[slot] = true; pass_slots.push_back(slot); ++i; } } //store the successful value of 'd' at index of the first hash for this bucket m_g[hashval % max_size] = d; //take the value from the temporary bucket into the permanent slot for(size_type i = 0;i < bucket.size();++i){ m_values[pass_slots[i]] = std::move(bucket[i].value); m_key_hashes[pass_slots[i]] = hashval; } } //Handle remaining single value buckets size_type next_free_slot = 0; for(;current_bucket < buckets.size();++current_bucket){ auto& bucket = buckets[current_bucket]; if(bucket.size() == 0) break; const auto hashval = Hash{}(bucket[0].key, 0); for(;m_key_hashes[next_free_slot] != 0;++next_free_slot); m_g[Hash{}(bucket[0].key, 0) % max_size] = (next_free_slot | single_bucket_bit); m_values[next_free_slot] = std::move(bucket[0].value); m_key_hashes[next_free_slot] = hashval; } } //no key checks. give a correct key or get a random answer :) template template constexpr auto hashmap::operator[](U&& key)noexcept -> reference{ auto d = m_g[UHash{}(std::forward(key), 0) % max_size]; if(d & single_bucket_bit) return m_values[d & ~single_bucket_bit]; return m_values[UHash{}(std::forward(key), d) % max_size]; } template template constexpr auto hashmap::operator[](U&& key)const noexcept -> const_reference{ auto d = m_g[UHash{}(std::forward(key), 0) % max_size]; if(d & single_bucket_bit) return m_values[d & ~single_bucket_bit]; return m_values[UHash{}(std::forward(key), d) % max_size]; } template template constexpr bool hashmap::is_valid(U&& key)const noexcept{ const auto hashval = UHash{}(std::forward(key), 0); const auto d = m_g[hashval % max_size]; if(d & single_bucket_bit){ return m_key_hashes[d & ~single_bucket_bit] == hashval; } return m_key_hashes[UHash{}(std::forward(key), d) % max_size] == hashval; } template> constexpr auto make_hashmap(const typename hashmap::value_type(&list)[N]){ return hashmap(list); } } #ifdef REXY_STRING_BASE_HPP #include "../string_hash.hpp" #endif #endif