301 lines
8.7 KiB
C++
301 lines
8.7 KiB
C++
// =====================================================================================================================
|
|
// Copyright 2024 Medusa Slockbower
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
// =====================================================================================================================
|
|
|
|
#ifndef HASH_TABLE_H
|
|
#define HASH_TABLE_H
|
|
|
|
#include <memory>
|
|
#include <type_traits>
|
|
|
|
#include "math.h"
|
|
#include "optional.h"
|
|
|
|
namespace open_cpp_utils
|
|
{
|
|
|
|
template<typename T, class Hash = std::hash<T>, class Alloc = std::allocator<T>>
|
|
class hash_table
|
|
{
|
|
// Typedefs ============================================================================================================
|
|
|
|
public:
|
|
friend class iterator;
|
|
|
|
private:
|
|
struct _Node;
|
|
|
|
public:
|
|
using value_type = T;
|
|
using pointer = T*;
|
|
using const_pointer = const T*;
|
|
using reference = T&;
|
|
using const_reference = const T&;
|
|
|
|
using hash_type = Hash;
|
|
using allocator_type = typename std::allocator_traits<Alloc>::template rebind_alloc<_Node>;
|
|
|
|
using size_type = size_t;
|
|
using iterator_type = iterator;
|
|
|
|
private:
|
|
using table_type = _Node*;
|
|
using node = int64_t;
|
|
static constexpr node nullnode = std::integral_constant<node, -1>{};
|
|
|
|
|
|
// Structs =============================================================================================================
|
|
|
|
private:
|
|
struct _Node
|
|
{
|
|
optional<T> value;
|
|
int psl;
|
|
|
|
_Node() : value(), psl(0) { }
|
|
};
|
|
|
|
|
|
// Functions ===========================================================================================================
|
|
|
|
public:
|
|
|
|
// Constructors & Destructor -------------------------------------------------------------------------------------------
|
|
|
|
hash_table() : table_(nullptr), capacity_(0), size_(0), load_factor_(0.8), hash_(), alloc_() { }
|
|
hash_table(const hash_table&);
|
|
hash_table(hash_table&&) = default; // Default Move Constructor should suffice
|
|
~hash_table() { clear(); }
|
|
|
|
// Modifiers -----------------------------------------------------------------------------------------------------------
|
|
|
|
void clear();
|
|
void insert(const_reference x);
|
|
void erase(const_reference x);
|
|
bool contains(const_reference x);
|
|
iterator find(const_reference x) const { node res = _find(x); return iterator(this, res == nullnode ? capacity_ : res); }
|
|
|
|
// Modifiers -----------------------------------------------------------------------------------------------------------
|
|
|
|
[[nodiscard]] size_type capacity() const { return capacity_; }
|
|
[[nodiscard]] size_type size() const { return size_; }
|
|
[[nodiscard]] bool empty() const { return size_ == 0; }
|
|
[[nodiscard]] double occupancy() const { return size_ / static_cast<double>(capacity_); }
|
|
|
|
// Helpers -------------------------------------------------------------------------------------------------------------
|
|
|
|
private:
|
|
void _increase_capacity();
|
|
|
|
[[nodiscard]] node _hash(const_reference v) const;
|
|
[[nodiscard]] node _find(const_reference x) const;
|
|
[[nodiscard]] node _next(node n) const { return (n + 1) % capacity_; }
|
|
[[nodiscard]] node _prev(node n) const { return (n - 1 + capacity_) % capacity_; }
|
|
|
|
static size_type _next_prime(size_type x);
|
|
static size_type _prev_prime(size_type x);
|
|
|
|
|
|
// Iterators ===========================================================================================================
|
|
|
|
class iterator
|
|
{
|
|
public:
|
|
iterator(hash_table* table, int idx) : table_(table), idx_(idx) { _next_index(); }
|
|
iterator(const iterator&) = default;
|
|
iterator(iterator&&) = default;
|
|
~iterator() = default;
|
|
|
|
iterator& operator++() { ++idx_; _next_index(); return *this; }
|
|
iterator operator++(int) { iterator ret = *this; ++idx_; _next_index(); return ret; }
|
|
|
|
bool operator==(const iterator& o) const = default;
|
|
bool operator!=(const iterator& o) const = default;
|
|
|
|
reference operator*() const { return table_->table_[idx_].value; }
|
|
pointer operator->() const { return &table_->table_[idx_].value; }
|
|
|
|
private:
|
|
void _next_index() { while(idx_ < table_->capacity_ && table_[idx_].value()) ++idx_; }
|
|
|
|
hash_table* table_;
|
|
int idx_;
|
|
};
|
|
|
|
iterator begin() { return iterator(this, 0); }
|
|
iterator end() { return iterator(this, capacity_); }
|
|
|
|
|
|
// Variables ===========================================================================================================
|
|
|
|
private:
|
|
table_type table_;
|
|
size_type capacity_, size_;
|
|
double load_factor_;
|
|
hash_type hash_;
|
|
allocator_type alloc_;
|
|
};
|
|
|
|
template<typename T, class Hash, class Alloc>
|
|
hash_table<T, Hash, Alloc>::hash_table(const hash_table& other)
|
|
: table_(nullptr)
|
|
, capacity_(other.capacity_)
|
|
, size_(other.size_)
|
|
, load_factor_(0.8)
|
|
{
|
|
|
|
}
|
|
|
|
template<typename T, class Hash, class Alloc>
|
|
void hash_table<T, Hash, Alloc>::clear()
|
|
{
|
|
alloc_.deallocate(table_, capacity_);
|
|
capacity_ = size_ = 0;
|
|
}
|
|
|
|
template<typename T, class Hash, class Alloc>
|
|
void hash_table<T, Hash, Alloc>::insert(const_reference x)
|
|
{
|
|
if(occupancy() > load_factor_ || capacity_ == 0) _increase_capacity();
|
|
|
|
node idx = _hash(x);
|
|
int psl = 0;
|
|
T value = x;
|
|
|
|
while(table_[idx].value())
|
|
{
|
|
_Node& node = table_[idx];
|
|
|
|
if(node.value == x) return;
|
|
if(psl > node.psl) { std::swap(psl, node.psl); std::swap(value, node.value); }
|
|
|
|
idx = _next(idx);
|
|
++psl;
|
|
}
|
|
|
|
table_[idx].value = value;
|
|
table_[idx].psl = psl;
|
|
++size_;
|
|
}
|
|
|
|
template<typename T, class Hash, class Alloc>
|
|
void hash_table<T, Hash, Alloc>::erase(const_reference x)
|
|
{
|
|
node idx = _find(x);
|
|
if(idx == nullnode) return;
|
|
|
|
table_[idx].value.reset();
|
|
--size_;
|
|
|
|
node prev = idx; idx = _next(idx);
|
|
while(table_[idx].value() && table_[idx].psl > 0)
|
|
{
|
|
_Node &a = table_[prev], &b = table_[idx];
|
|
std::swap(a, b);
|
|
--a.psl; prev = idx; idx = _next(idx);
|
|
}
|
|
}
|
|
|
|
template<typename T, class Hash, class Alloc>
|
|
bool hash_table<T, Hash, Alloc>::contains(const_reference x)
|
|
{
|
|
return _find(x) != nullnode;
|
|
}
|
|
|
|
template<typename T, class Hash, class Alloc>
|
|
void hash_table<T, Hash, Alloc>::_increase_capacity()
|
|
{
|
|
table_type old = table_;
|
|
size_type old_capacity = capacity_;
|
|
capacity_ = _next_prime(capacity_);
|
|
table_ = alloc_.allocate(capacity_);
|
|
memset(table_, 0, capacity_ * sizeof(_Node));
|
|
size_ = 0;
|
|
|
|
for(node i = 0; i < old_capacity; ++i)
|
|
{
|
|
if(old[i].value()) insert(old[i].value);
|
|
}
|
|
|
|
alloc_.deallocate(old, old_capacity);
|
|
}
|
|
|
|
template<typename T, class Hash, class Alloc>
|
|
typename hash_table<T, Hash, Alloc>::node hash_table<T, Hash, Alloc>::_hash(const_reference v) const
|
|
{
|
|
node x = hash_(v);
|
|
|
|
x ^= x >> 33U;
|
|
x *= UINT64_C(0xff51afd7ed558ccd);
|
|
x ^= x >> 33U;
|
|
x *= UINT64_C(0xc4ceb9fe1a85ec53);
|
|
x ^= x >> 33U;
|
|
|
|
return x;
|
|
}
|
|
|
|
template<typename T, class Hash, class Alloc>
|
|
typename hash_table<T, Hash, Alloc>::node hash_table<T, Hash, Alloc>::_find(const_reference x) const
|
|
{
|
|
if(capacity_ == 0) return nullnode;
|
|
node idx = _hash(x);
|
|
int psl = 0;
|
|
|
|
while(table_[idx].value())
|
|
{
|
|
_Node& node = table_[idx];
|
|
|
|
if(node.psl > psl) return nullnode;
|
|
if(node.value == x) return idx;
|
|
|
|
idx = _next(idx); ++psl;
|
|
}
|
|
|
|
return nullnode;
|
|
}
|
|
|
|
template<typename T, class Hash, class Alloc>
|
|
typename hash_table<T, Hash, Alloc>::size_type hash_table<T, Hash, Alloc>::_next_prime(size_type x)
|
|
{
|
|
size_type n = (x + 1) / 6;
|
|
n *= 2;
|
|
|
|
while(true)
|
|
{
|
|
x = (n * 6) - 1;
|
|
if(!is_prime(x)) x = (n * 6) + 1;
|
|
if(!is_prime(x)) { ++n; continue; }
|
|
return std::max(x, 7ull);
|
|
}
|
|
}
|
|
|
|
template<typename T, class Hash, class Alloc>
|
|
typename hash_table<T, Hash, Alloc>::size_type hash_table<T, Hash, Alloc>::_prev_prime(size_type x)
|
|
{
|
|
size_type n = (x + 1) / 6;
|
|
n *= 2;
|
|
|
|
while(true)
|
|
{
|
|
x = (n * 6) - 1;
|
|
if(!is_prime(x)) x = (n * 6) + 1;
|
|
if(!is_prime(x)) { --n; continue; }
|
|
return std::max(x, 7ull);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
#endif //HASH_TABLE_H
|