open-cpp-utils 0.0.1
Loading...
Searching...
No Matches
hash_table.h
1// =====================================================================================================================
2// open-cpp-utils, an open-source cpp library with data structures that extend the STL.
3// Copyright (C) 2024 Medusa Slockbower
4//
5// This program is free software: you can redistribute it and/or modify
6// it under the terms of the GNU General Public License as published by
7// the Free Software Foundation, either version 3 of the License, or
8// (at your option) any later version.
9//
10// This program is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13// GNU General Public License for more details.
14//
15// You should have received a copy of the GNU General Public License
16// along with this program. If not, see <https://www.gnu.org/licenses/>.
17// =====================================================================================================================
18
19#ifndef OPEN_CPP_UTILS_HASH_TABLE_H
20#define OPEN_CPP_UTILS_HASH_TABLE_H
21
22#include <memory>
23#include <type_traits>
24#include <cstdarg>
25#include <utility>
26
27#include "math.h"
28#include "optional.h"
29
30namespace open_cpp_utils
31{
32
33template<typename T, class Hash = std::hash<T>, class Alloc = std::allocator<T>>
35{
36// Typedefs ============================================================================================================
37
38public:
39 friend class iterator;
40
41private:
42 struct _Node;
43
44public:
45 using value_type = T;
46 using pointer = T*;
47 using const_pointer = const T*;
48 using reference = T&;
49 using const_reference = const T&;
50
51 using hash_type = Hash;
52 using allocator_type = typename std::allocator_traits<Alloc>::template rebind_alloc<_Node>;
53
54 using size_type = size_t;
55 using iterator_type = class iterator;
56
57private:
58 using table_type = _Node*;
59 using node = int64_t;
60 static constexpr node nullnode = std::integral_constant<node, -1>{};
61
62
63// Structs =============================================================================================================
64
65private:
66 struct _Node
67 {
68 optional<T> value;
69 int psl;
70
71 _Node() : value(), psl(0) { }
72 _Node(const_reference v) : value(v), psl(0) { }
73 _Node(const_reference v, int psl) : value(v), psl(psl) { }
74
75 bool operator==(const _Node& b) const
76 {
77 return value == b.value && psl == b.psl;
78 }
79 };
80
81
82 // Iterators ===========================================================================================================
83
84public:
86 {
87 public:
88 iterator(hash_table* table, size_type idx) : table_(table), idx_(idx) { _next_index(); }
89 iterator(const iterator& b) : table_(b.table_), idx_(b.idx_) { }
90 iterator(iterator&& b) noexcept : table_(b.table_), idx_(b.idx_) { }
91 ~iterator() = default;
92
93 iterator& operator=(const iterator& b) { if(&b == this) return *this; table_ = b.table_; idx_ = b.idx_; return *this; }
94 iterator& operator=(iterator&& b) noexcept { if(&b == this) return *this; table_ = b.table_; idx_ = b.idx_; return *this; };
95
96 iterator& operator++() { ++idx_; _next_index(); return *this; }
97 iterator operator++(int) { iterator ret = *this; ++idx_; _next_index(); return ret; }
98
99 bool operator==(const iterator& o) const = default;
100 bool operator!=(const iterator& o) const = default;
101
102 reference operator*() { return table_->table_[idx_].value; }
103 pointer operator->() { return &*table_->table_[idx_].value; }
104
105 const_reference operator*() const { return table_->table_[idx_].value; }
106 const_pointer operator->() const { return &*table_->table_[idx_].value; }
107
108 private:
109 void _next_index()
110 {
111 while(idx_ < table_->capacity_ && not table_->table_[idx_].value())
112 ++idx_;
113 }
114
115 hash_table* table_;
116 size_type idx_;
117 };
118
120 {
121 public:
122 const_iterator(const hash_table* table, node idx) : table_(table), idx_(idx) { _next_index(); }
123 const_iterator(const const_iterator& b) : table_(b.table_), idx_(b.idx_) { }
124 const_iterator(const_iterator&& b) noexcept : table_(b.table_), idx_(b.idx_) { }
125 ~const_iterator() = default;
126
127 const_iterator& operator=(const const_iterator& b) { if(&b == this) return *this; table_ = b.table_; idx_ = b.idx_; return *this; }
128 const_iterator& operator=(const_iterator&& b) noexcept { if(&b == this) return *this; table_ = b.table_; idx_ = b.idx_; return *this; };
129
130 const_iterator& operator++() { ++idx_; _next_index(); return *this; }
131 const_iterator operator++(int) { const_iterator ret = *this; ++idx_; _next_index(); return ret; }
132
133 bool operator==(const const_iterator& o) const = default;
134 bool operator!=(const const_iterator& o) const = default;
135
136 reference operator*() const { return table_->table_[idx_].value; }
137 pointer operator->() const { return &*table_->table_[idx_].value; }
138
139 private:
140 void _next_index() { while(idx_ < table_->capacity_ && not table_->table_[idx_].value()) ++idx_; }
141
142 const hash_table* table_;
143 node idx_;
144 };
145
146 iterator begin() { return iterator(this, 0); }
147 iterator end() { return iterator(this, capacity_); }
148
149 const_iterator begin() const { return const_iterator(this, 0); }
150 const_iterator end() const { return const_iterator(this, capacity_); }
151
152
153// Functions ===========================================================================================================
154
155public:
156
157// Constructors & Destructor -------------------------------------------------------------------------------------------
158
159 hash_table() : table_(nullptr), capacity_(0), size_(0), load_factor_(0.8), hash_(), alloc_() { }
160 hash_table(std::initializer_list<value_type> data);
161 hash_table(const hash_table&);
162 hash_table(hash_table&&) = default; // Default Move Constructor should suffice
163 ~hash_table() { clear(); }
164
165// Modifiers -----------------------------------------------------------------------------------------------------------
166
167 void reserve(size_t size);
168 void clear();
169 void insert(const_reference x);
170 void erase(const_reference x);
171 bool contains(const_reference x);
172
173 iterator find(const_reference x)
174 {
175 node res = _find(x);
176 if(res == nullnode) res = capacity_;
177 return iterator(this, res);
178 }
179
180 const_iterator find(const_reference x) const
181 {
182 node res = _find(x);
183 if(res == nullnode) res = capacity_;
184 return const_iterator(this, res);
185 }
186
187// Size ----------------------------------------------------------------------------------------------------------------
188
189 [[nodiscard]] size_type capacity() const { return capacity_; }
190 [[nodiscard]] size_type size() const { return size_; }
191 [[nodiscard]] bool empty() const { return size_ == 0; }
192 [[nodiscard]] double occupancy() const { return size_ / static_cast<double>(capacity_); }
193
194// Helpers -------------------------------------------------------------------------------------------------------------
195
196private:
197 void _increase_capacity();
198
199 [[nodiscard]] node _hash(const_reference v) const;
200 [[nodiscard]] node _find(const_reference x) const;
201 [[nodiscard]] node _next(node n) const { return (n + 1) % capacity_; }
202 [[nodiscard]] node _prev(node n) const { return (n - 1 + capacity_) % capacity_; }
203
204 static size_type _next_prime(size_type x);
205 static size_type _prev_prime(size_type x);
206
207
208// Variables ===========================================================================================================
209
210private:
211 table_type table_;
212 size_type capacity_, size_;
213 double load_factor_;
214 hash_type hash_;
215 allocator_type alloc_;
216};
217
218template<typename T, class Hash, class Alloc>
219hash_table<T, Hash, Alloc>::hash_table(std::initializer_list<value_type> data)
220 : hash_table()
221{
222 reserve(data.size());
223 for(value_type val : data) { insert(val); }
224}
225
226template<typename T, class Hash, class Alloc>
227hash_table<T, Hash, Alloc>::hash_table(const hash_table& other)
228 : table_(nullptr)
229 , capacity_(other.capacity_)
230 , size_(other.size_)
231 , load_factor_(0.8)
232{
233
234}
235
236template<typename T, class Hash, class Alloc>
237void hash_table<T, Hash, Alloc>::reserve(size_t size)
238{
239 table_type old = table_;
240 size_type old_capacity = capacity_;
241 capacity_ = _next_prime(size);
242 table_ = alloc_.allocate(capacity_);
243 memset(table_, 0, capacity_ * sizeof(_Node));
244 size_ = 0;
245
246 for(size_type i = 0; i < old_capacity; ++i)
247 {
248 if(old[i].value()) insert(old[i].value);
249 }
250
251 alloc_.deallocate(old, old_capacity);
252}
253
254template<typename T, class Hash, class Alloc>
255void hash_table<T, Hash, Alloc>::clear()
256{
257 alloc_.deallocate(table_, capacity_);
258 capacity_ = size_ = 0;
259 table_ = nullptr;
260}
261
262template<typename T, class Hash, class Alloc>
263void hash_table<T, Hash, Alloc>::insert(const_reference x)
264{
265 if(occupancy() > load_factor_ || capacity_ == 0) _increase_capacity();
266
267 node idx = _hash(x);
268 int psl = 0;
269 T val = x;
270
271 while(table_[idx].value())
272 {
273 _Node& node = table_[idx];
274
275 if(*node.value == x) return;
276 if(psl > node.psl)
277 {
278 // std::swap(psl, node.psl); std::swap(value, node.value);
279 int temp_psl = psl; psl = node.psl; node.psl = temp_psl;
280 T temp_val = std::move(node.value); node.value = std::move(val); val = std::move(temp_val);
281 }
282
283 idx = _next(idx);
284 ++psl;
285 }
286
287 std::construct_at(table_ + idx, val, psl);
288 ++size_;
289}
290
291template<typename T, class Hash, class Alloc>
292void hash_table<T, Hash, Alloc>::erase(const_reference x)
293{
294 node idx = _find(x);
295 if(idx == nullnode) return;
296
297 table_[idx].value.reset();
298 --size_;
299
300 node prev = idx; idx = _next(idx);
301 while(table_[idx].value() && table_[idx].psl > 0)
302 {
303 _Node &a = table_[prev], &b = table_[idx];
304 std::swap(a, b);
305 --a.psl; prev = idx; idx = _next(idx);
306 }
307}
308
309template<typename T, class Hash, class Alloc>
310bool hash_table<T, Hash, Alloc>::contains(const_reference x)
311{
312 return _find(x) != nullnode;
313}
314
315template<typename T, class Hash, class Alloc>
316void hash_table<T, Hash, Alloc>::_increase_capacity()
317{
318 table_type old = table_;
319 size_type old_capacity = capacity_;
320 alloc_.deallocate(table_, capacity_);
321 capacity_ = _next_prime(capacity_);
322 table_ = alloc_.allocate(capacity_);
323 memset(table_, 0, capacity_ * sizeof(_Node));
324 size_ = 0;
325
326 for(size_type i = 0; i < old_capacity; ++i)
327 {
328 if(old[i].value()) insert(old[i].value);
329 }
330
331 alloc_.deallocate(old, old_capacity);
332}
333
334template<typename T, class Hash, class Alloc>
335typename hash_table<T, Hash, Alloc>::node hash_table<T, Hash, Alloc>::_hash(const_reference v) const
336{
337 node x = static_cast<node>(hash_(v));
338
339 x ^= x >> 33U;
340 x *= UINT64_C(0xff51afd7ed558ccd);
341 x ^= x >> 33U;
342 x *= UINT64_C(0xc4ceb9fe1a85ec53);
343 x ^= x >> 33U;
344
345 return x % capacity_;
346}
347
348template<typename T, class Hash, class Alloc>
349typename hash_table<T, Hash, Alloc>::node hash_table<T, Hash, Alloc>::_find(const_reference x) const
350{
351 if(capacity_ == 0) return nullnode;
352 node idx = _hash(x);
353 int psl = 0;
354
355 while(table_[idx].value())
356 {
357 _Node& node = table_[idx];
358
359 if(node.psl > psl) return nullnode;
360 if(*node.value == x) return idx;
361
362 idx = _next(idx); ++psl;
363 }
364
365 return nullnode;
366}
367
368template<typename T, class Hash, class Alloc>
369typename hash_table<T, Hash, Alloc>::size_type hash_table<T, Hash, Alloc>::_next_prime(size_type x)
370{
371 size_type n = (x + 1) / 6;
372 n *= 2;
373
374 while(true)
375 {
376 x = (n * 6) - 1;
377 if(!is_prime(x)) x = (n * 6) + 1;
378 if(!is_prime(x)) { ++n; continue; }
379 return std::max(x, 7ull);
380 }
381}
382
383template<typename T, class Hash, class Alloc>
384typename hash_table<T, Hash, Alloc>::size_type hash_table<T, Hash, Alloc>::_prev_prime(size_type x)
385{
386 size_type n = (x + 1) / 6;
387 n *= 2;
388
389 while(true)
390 {
391 x = (n * 6) - 1;
392 if(!is_prime(x)) x = (n * 6) + 1;
393 if(!is_prime(x)) { --n; continue; }
394 return std::max(x, 7ull);
395 }
396}
397
398}
399
400#endif // OPEN_CPP_UTILS_HASH_TABLE_H
Definition hash_table.h:86
Definition hash_table.h:35
Definition optional.h:28