source: mainline/uspace/lib/cpp/include/__bits/adt/hash_table.hpp@ b57ba05

Last change on this file since b57ba05 was b57ba05, checked in by Jiří Zárevúcky <zarevucky.jiri@…>, 3 years ago

Update headers in C++ files

  • Property mode set to 100644
File size: 17.2 KB
Line 
1/*
2 * SPDX-FileCopyrightText: 2018 Jaroslav Jindrak
3 *
4 * SPDX-License-Identifier: BSD-3-Clause
5 */
6
7#ifndef LIBCPP_BITS_ADT_HASH_TABLE
8#define LIBCPP_BITS_ADT_HASH_TABLE
9
10#include <__bits/adt/list_node.hpp>
11#include <__bits/adt/key_extractors.hpp>
12#include <__bits/adt/hash_table_iterators.hpp>
13#include <__bits/adt/hash_table_policies.hpp>
14#include <cstdlib>
15#include <iterator>
16#include <limits>
17#include <memory>
18#include <tuple>
19#include <utility>
20
21namespace std::aux
22{
23 /**
24 * To save code, we're going to implement one hash table
25 * for both unordered_map and unordered_set. To do this,
26 * we create one inner hash table that is oblivious to its
27 * held type (and uses a key extractor to get the key from it)
28 * and two proxies that either use plain Key type or a pair
29 * of a key and a value.
30 * Additionally, we will use policies to represent both single
31 * and multi variants of these containers at once.
32 * Note: I am aware the naming is very unimaginative here,
33 * not my strong side :)
34 */
35
36 template<
37 class Value, class Key, class KeyExtractor,
38 class Hasher, class KeyEq,
39 class Alloc, class Size,
40 class Iterator, class ConstIterator,
41 class LocalIterator, class ConstLocalIterator,
42 class Policy
43 >
44 class hash_table
45 {
46 public:
47 using value_type = Value;
48 using key_type = Key;
49 using size_type = Size;
50 using allocator_type = Alloc;
51 using key_equal = KeyEq;
52 using hasher = Hasher;
53 using key_extract = KeyExtractor;
54
55 using iterator = Iterator;
56 using const_iterator = ConstIterator;
57 using local_iterator = LocalIterator;
58 using const_local_iterator = ConstLocalIterator;
59
60 using node_type = list_node<value_type>;
61
62 using place_type = tuple<
63 hash_table_bucket<value_type, size_type>*,
64 list_node<value_type>*, size_type
65 >;
66
67 hash_table(size_type buckets, float max_load_factor = 1.f)
68 : table_{new hash_table_bucket<value_type, size_type>[buckets]()},
69 bucket_count_{buckets}, size_{}, hasher_{}, key_eq_{},
70 key_extractor_{}, max_load_factor_{max_load_factor}
71 { /* DUMMY BODY */ }
72
73 hash_table(size_type buckets, const hasher& hf, const key_equal& eql,
74 float max_load_factor = 1.f)
75 : table_{new hash_table_bucket<value_type, size_type>[buckets]()},
76 bucket_count_{buckets}, size_{}, hasher_{hf}, key_eq_{eql},
77 key_extractor_{}, max_load_factor_{max_load_factor}
78 { /* DUMMY BODY */ }
79
80 hash_table(const hash_table& other)
81 : hash_table{other.bucket_count_, other.hasher_, other.key_eq_,
82 other.max_load_factor_}
83 {
84 for (const auto& x: other)
85 insert(x);
86 }
87
88 hash_table(hash_table&& other)
89 : table_{other.table_}, bucket_count_{other.bucket_count_},
90 size_{other.size_}, hasher_{move(other.hasher_)},
91 key_eq_{move(other.key_eq_)}, key_extractor_{move(other.key_extractor_)},
92 max_load_factor_{other.max_load_factor_}
93 {
94 other.table_ = nullptr;
95 other.bucket_count_ = size_type{};
96 other.size_ = size_type{};
97 other.max_load_factor_ = 1.f;
98 }
99
100 hash_table& operator=(const hash_table& other)
101 {
102 hash_table tmp{other};
103 tmp.swap(*this);
104
105 return *this;
106 }
107
108 hash_table& operator=(hash_table&& other)
109 {
110 hash_table tmp{move(other)};
111 tmp.swap(*this);
112
113 return *this;
114 }
115
116 bool empty() const noexcept
117 {
118 return size_ == 0;
119 }
120
121 size_type size() const noexcept
122 {
123 return size_;
124 }
125
126 size_type max_size(allocator_type& alloc)
127 {
128 return allocator_traits<allocator_type>::max_size(alloc);
129 }
130
131 iterator begin() noexcept
132 {
133 auto idx = first_filled_bucket_();
134 return iterator{
135 table_, idx, bucket_count_,
136 table_[idx].head
137 };
138 }
139
140 const_iterator begin() const noexcept
141 {
142 return cbegin();
143 }
144
145 iterator end() noexcept
146 {
147 return iterator{};
148 }
149
150 const_iterator end() const noexcept
151 {
152 return cend();
153 }
154
155 const_iterator cbegin() const noexcept
156 {
157 auto idx = first_filled_bucket_();
158 return const_iterator{
159 table_, idx, bucket_count_,
160 table_[idx].head
161 };
162 }
163
164 const_iterator cend() const noexcept
165 {
166 return const_iterator{};
167 }
168
169 template<class... Args>
170 auto emplace(Args&&... args)
171 {
172 return Policy::emplace(*this, forward<Args>(args)...);
173 }
174
175 auto insert(const value_type& val)
176 {
177 return Policy::insert(*this, val);
178 }
179
180 auto insert(value_type&& val)
181 {
182 return Policy::insert(*this, forward<value_type>(val));
183 }
184
185 size_type erase(const key_type& key)
186 {
187 return Policy::erase(*this, key);
188 }
189
190 iterator erase(const_iterator it)
191 {
192 if (it == cend())
193 return end();
194
195 auto node = it.node();
196 auto idx = it.idx();
197
198 /**
199 * Note: This way we will continue on the next bucket
200 * if this is the last element in its bucket.
201 */
202 iterator res{table_, idx, size_, node};
203 ++res;
204
205 if (table_[idx].head == node)
206 {
207 if (node->next != node)
208 table_[idx].head = node->next;
209 else
210 table_[idx].head = nullptr;
211 }
212 --size_;
213
214 node->unlink();
215 delete node;
216
217 if (empty())
218 return end();
219 else
220 return res;
221 }
222
223 void clear() noexcept
224 {
225 for (size_type i = 0; i < bucket_count_; ++i)
226 table_[i].clear();
227 size_ = size_type{};
228 }
229
230 void swap(hash_table& other)
231 noexcept(allocator_traits<allocator_type>::is_always_equal::value &&
232 noexcept(swap(declval<Hasher&>(), declval<Hasher&>())) &&
233 noexcept(swap(declval<KeyEq&>(), declval<KeyEq&>())))
234 {
235 std::swap(table_, other.table_);
236 std::swap(bucket_count_, other.bucket_count_);
237 std::swap(size_, other.size_);
238 std::swap(hasher_, other.hasher_);
239 std::swap(key_eq_, other.key_eq_);
240 std::swap(max_load_factor_, other.max_load_factor_);
241 }
242
243 hasher hash_function() const
244 {
245 return hasher_;
246 }
247
248 key_equal key_eq() const
249 {
250 return key_eq_;
251 }
252
253 iterator find(const key_type& key)
254 {
255 auto idx = get_bucket_idx_(key);
256 auto head = table_[idx].head;
257
258 if (!head)
259 return end();
260
261 auto current = head;
262 do
263 {
264 if (key_eq_(key, key_extractor_(current->value)))
265 return iterator{table_, idx, size_, current};
266 current = current->next;
267 }
268 while (current && current != head);
269
270 return end();
271 }
272
273 const_iterator find(const key_type& key) const
274 {
275 auto idx = get_bucket_idx_(key);
276 auto head = table_[idx].head;
277
278 if (!head)
279 return end();
280
281 auto current = head;
282 do
283 {
284 if (key_eq_(key, key_extractor_(current->value)))
285 return iterator{table_, idx, size_, current};
286 current = current->next;
287 }
288 while (current != head);
289
290 return end();
291 }
292
293 size_type count(const key_type& key) const
294 {
295 return Policy::count(*this, key);
296 }
297
298 pair<iterator, iterator> equal_range(const key_type& key)
299 {
300 return Policy::equal_range(*this, key);
301 }
302
303 pair<const_iterator, const_iterator> equal_range(const key_type& key) const
304 {
305 return Policy::equal_range_const(*this, key);
306 }
307
308 size_type bucket_count() const noexcept
309 {
310 return bucket_count_;
311 }
312
313 size_type max_bucket_count() const noexcept
314 {
315 return numeric_limits<size_type>::max() /
316 sizeof(hash_table_bucket<value_type, size_type>);
317 }
318
319 size_type bucket_size(size_type n) const
320 {
321 return table_[n].size();
322 }
323
324 size_type bucket(const key_type& key) const
325 {
326 return get_bucket_idx_(key);
327 }
328
329 local_iterator begin(size_type n)
330 {
331 return local_iterator{table_[n].head, table_[n].head};
332 }
333
334 const_local_iterator begin(size_type n) const
335 {
336 return cbegin(n);
337 }
338
339 local_iterator end(size_type n)
340 {
341 return local_iterator{};
342 }
343
344 const_local_iterator end(size_type n) const
345 {
346 return cend(n);
347 }
348
349 const_local_iterator cbegin(size_type n) const
350 {
351 return const_local_iterator{table_[n].head, table_[n].head};
352 }
353
354 const_local_iterator cend(size_type n) const
355 {
356 return const_local_iterator{};
357 }
358
359 float load_factor() const noexcept
360 {
361 return size_ / static_cast<float>(bucket_count_);
362 }
363
364 float max_load_factor() const noexcept
365 {
366 return max_load_factor_;
367 }
368
369 void max_load_factor(float factor)
370 {
371 if (factor > 0.f)
372 max_load_factor_ = factor;
373
374 rehash_if_needed();
375 }
376
377 void rehash(size_type count)
378 {
379 if (count < size_ / max_load_factor_)
380 count = size_ / max_load_factor_;
381
382 /**
383 * Note: If an exception is thrown, there
384 * is no effect. Since this is the only
385 * place where an exception (no mem) can
386 * be thrown and no changes to this have been
387 * made, we're ok.
388 */
389 hash_table new_table{count, max_load_factor_};
390
391 for (std::size_t i = 0; i < bucket_count_; ++i)
392 {
393 auto head = table_[i].head;
394 if (!head)
395 continue;
396
397 auto current = head;
398
399 do
400 {
401 auto next = current->next;
402
403 current->next = current;
404 current->prev = current;
405
406 auto where = Policy::find_insertion_spot(
407 new_table, key_extractor_(current->value)
408 );
409
410 /**
411 * Note: We're rehashing, so we know each
412 * key can be inserted.
413 */
414 auto new_bucket = get<0>(where);
415 auto new_successor = get<1>(where);
416
417 if (new_successor)
418 new_successor->append(current);
419 else
420 new_bucket->append(current);
421
422 current = next;
423 } while (current != head);
424
425 table_[i].head = nullptr;
426 }
427
428 new_table.size_ = size_;
429 swap(new_table);
430
431 delete[] new_table.table_;
432 new_table.table_ = nullptr;
433 }
434
435 void reserve(size_type count)
436 {
437 rehash(count / max_load_factor_ + 1);
438 }
439
440 bool is_eq_to(const hash_table& other) const
441 {
442 if (size() != other.size())
443 return false;
444
445 auto it = begin();
446 while (it != end())
447 {
448 /**
449 * For each key K we will check how many
450 * instances of K are there in the table.
451 * Then we will check if the count for K
452 * is equal to that amount.
453 */
454
455 size_type cnt{};
456 auto tmp = it;
457
458 while (key_eq_(key_extractor_(*it), key_extractor_(*tmp)))
459 {
460 ++cnt;
461 if (++tmp == end())
462 break;
463 }
464
465 auto other_cnt = other.count(key_extractor_(*it));
466 if (cnt != other_cnt)
467 return false;
468
469 it = tmp; // tmp is one past *it's key.
470 }
471
472 return true;
473 }
474
475 ~hash_table()
476 {
477 // Lists are deleted in ~hash_table_bucket.
478 if (table_)
479 delete[] table_;
480 }
481
482 place_type find_insertion_spot(const key_type& key) const
483 {
484 return Policy::find_insertion_spot(*this, key);
485 }
486
487 place_type find_insertion_spot(key_type&& key) const
488 {
489 return Policy::find_insertion_spot(*this, key);
490 }
491
492 const key_type& get_key(const value_type& val) const
493 {
494 return key_extractor_(val);
495 }
496
497 bool keys_equal(const key_type& key, const value_type& val)
498 {
499 return key_eq_(key, key_extractor_(val));
500 }
501
502 bool keys_equal(const key_type& key, const value_type& val) const
503 {
504 return key_eq_(key, key_extractor_(val));
505 }
506
507 hash_table_bucket<value_type, size_type>* table()
508 {
509 return table_;
510 }
511
512 hash_table_bucket<value_type, size_type>* head(size_type idx)
513 {
514 if (idx < bucket_count_)
515 return table_[idx]->head;
516 else
517 return nullptr;
518 }
519
520 void rehash_if_needed()
521 {
522 if (size_ > max_load_factor_ * bucket_count_)
523 rehash(bucket_count_ * bucket_count_growth_factor_);
524 }
525
526 void increment_size()
527 {
528 ++size_;
529
530 rehash_if_needed();
531 }
532
533 void decrement_size()
534 {
535 --size_;
536 }
537
538 private:
539 hash_table_bucket<value_type, size_type>* table_;
540 size_type bucket_count_;
541 size_type size_;
542 hasher hasher_;
543 key_equal key_eq_;
544 key_extract key_extractor_;
545 float max_load_factor_;
546
547 static constexpr float bucket_count_growth_factor_{1.25};
548
549 size_type get_bucket_idx_(const key_type& key) const
550 {
551 return hasher_(key) % bucket_count_;
552 }
553
554 size_type first_filled_bucket_() const
555 {
556 size_type res{};
557 while (res < bucket_count_)
558 {
559 if (table_[res].head)
560 return res;
561 ++res;
562 }
563
564 /**
565 * Note: This is used for iterators,
566 * so we need to return a valid index.
567 * But since table_[0].head is nullptr
568 * we know that if we return 0 the
569 * created iterator will test as equal
570 * to end().
571 */
572 return 0;
573 }
574
575 friend Policy;
576 };
577}
578
579#endif
Note: See TracBrowser for help on using the repository browser.