149 lines
4.3 KiB
C++
149 lines
4.3 KiB
C++
/**
|
|
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 Affero 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 Affero General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#ifndef REXY_CX_HASHMAP_HPP
|
|
#define REXY_CX_HASHMAP_HPP
|
|
|
|
#include "vector.hpp"
|
|
#include "array.hpp"
|
|
#include "algorithm.hpp"
|
|
#include "hash.hpp"
|
|
|
|
#include <climits> //CHAR_BIT
|
|
#include <initializer_list>
|
|
|
|
namespace rexy::cx{
|
|
|
|
template<class Key, class Value>
|
|
struct element
|
|
{
|
|
Key key;
|
|
Value value;
|
|
};
|
|
|
|
template<class Key, class Value, size_t N, class Hash = hash<Key>>
|
|
class hashmap
|
|
{
|
|
public:
|
|
static constexpr size_t single_bucket_bit = size_t{1} << ((sizeof(size_t)*CHAR_BIT) - 1);
|
|
static constexpr size_t max_size = N;
|
|
|
|
static_assert((max_size & single_bucket_bit) == 0);
|
|
|
|
using key_type = Key;
|
|
using value_type = Value;
|
|
using hash_type = Hash;
|
|
|
|
private:
|
|
array<Value,N> m_values; //perfect hash table
|
|
array<size_t,N> m_g; //'salt' values for indexing into the perfect hash table
|
|
|
|
public:
|
|
constexpr hashmap(const element<Key,Value>(&elements)[N]){
|
|
array<vector<element<Key,Value>,N>,N> buckets;
|
|
array<bool,N> slots_used;
|
|
size_t current_bucket = 0;
|
|
|
|
//place all keys into buckets
|
|
for(auto& element : elements){
|
|
buckets[Hash{}(element.key) % 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;
|
|
|
|
array<bool,N> pass_slots_used;
|
|
vector<size_t,N> pass_slots;
|
|
size_t d = 1;
|
|
|
|
for(size_t i = 0;i < bucket.size();){
|
|
size_t slot = Hash{}(bucket[i].key, d) % max_size;
|
|
if(pass_slots_used[slot] || slots_used[slot]){
|
|
//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[Hash{}(bucket[0].key) % max_size] = d;
|
|
|
|
//take the value from the temporary bucket into the permanent slot
|
|
for(size_t i = 0;i < bucket.size();++i){
|
|
m_values[pass_slots[i]] = std::move(bucket[i].value);
|
|
slots_used[pass_slots[i]] = true;
|
|
}
|
|
}
|
|
|
|
//Handle remaining single value buckets
|
|
size_t next_free_slot = 0;
|
|
|
|
for(;current_bucket < buckets.size();++current_bucket){
|
|
auto& bucket = buckets[current_bucket];
|
|
if(bucket.size() == 0)
|
|
break;
|
|
for(;slots_used[next_free_slot];++next_free_slot);
|
|
|
|
m_g[Hash{}(bucket[0].key) % max_size] = (next_free_slot | single_bucket_bit);
|
|
m_values[next_free_slot] = std::move(bucket[0].value);
|
|
slots_used[next_free_slot] = true;
|
|
}
|
|
}
|
|
|
|
//no key checks. give a correct key or get a random answer :)
|
|
constexpr value_type& operator[](const Key& key){
|
|
size_t d = m_g[Hash{}(key) % max_size];
|
|
if(d & single_bucket_bit)
|
|
return m_values[d & ~single_bucket_bit];
|
|
return m_values[Hash{}(key, d) % max_size];
|
|
}
|
|
constexpr const value_type& operator[](const Key& key)const{
|
|
size_t d = m_g[Hash{}(key) % max_size];
|
|
if(d & single_bucket_bit)
|
|
return m_values[d & ~single_bucket_bit];
|
|
return m_values[Hash{}(key, d) % max_size];
|
|
}
|
|
};
|
|
|
|
template<class Key, class Value, size_t N, class Hash = hash<Key>>
|
|
constexpr auto make_hashmap(const element<Key,Value>(&list)[N]){
|
|
return hashmap<Key,Value,N,Hash>(list);
|
|
}
|
|
}
|
|
|
|
#ifdef REXY_STRING_BASE_HPP
|
|
#include "string_hash.hpp"
|
|
#endif
|
|
|
|
#endif
|