source: mainline/uspace/lib/cpp/include/internal/hash_table.hpp@ 402f18d6

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since 402f18d6 was db628a0, checked in by Dzejrou <dzejrou@…>, 7 years ago

cpp: fixed constness error and added max_bucket_count implementation

  • Property mode set to 100644
File size: 37.5 KB
Line 
1/*
2 * Copyright (c) 2018 Jaroslav Jindrak
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * - Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * - Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * - The name of the author may not be used to endorse or promote products
15 * derived from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29#ifndef LIBCPP_INTERNAL_HASH_TABLE
30#define LIBCPP_INTERNAL_HASH_TABLE
31
32#include <cstdlib>
33#include <internal/list.hpp>
34#include <iterator>
35#include <limits>
36#include <memory>
37#include <tuple>
38#include <utility>
39
40namespace std::aux
41{
42 /**
43 * To save code, we're going to implement one hash table
44 * for both unordered_map and unordered_set. To do this,
45 * we create one inner hash table that is oblivious to its
46 * held type (and uses a key extractor to get the key from it)
47 * and two proxies that either use plain Key type or a pair
48 * of a key and a value.
49 * Note: I am aware the naming is very unimaginative here,
50 * not my strong side :)
51 */
52
53 template<class Key, class Value>
54 struct key_value_key_extractor
55 {
56 const Key& operator()(const pair<const Key, Value>& p) const noexcept
57 {
58 return p.first;
59 }
60 };
61
62 template<class Key>
63 struct key_no_value_key_extractor
64 {
65 Key& operator()(Key& k) const noexcept
66 {
67 return k;
68 }
69
70 const Key& operator()(const Key& k) const noexcept
71 {
72 return k;
73 }
74 };
75
76 template<class Value, class Size>
77 struct hash_table_bucket
78 {
79 /**
80 * Note: We use a doubly linked list because
81 * we need to use hints, which point to the
82 * element after the hinted spot.
83 */
84
85 list_node<Value>* head;
86
87 hash_table_bucket()
88 : head{}
89 { /* DUMMY BODY */ }
90
91 Size size() const noexcept
92 {
93 auto current = head;
94 Size res{};
95
96 do
97 {
98 ++res;
99 current = current->next;
100 }
101 while (current != head);
102
103 return res;
104 }
105
106 void append(list_node<Value>* node)
107 {
108 if (!head)
109 head = node;
110 else
111 head->append(node);
112 }
113
114 void clear()
115 {
116 if (!head)
117 return;
118
119 auto current = head;
120 do
121 {
122 auto tmp = current;
123 current = current->next;
124 delete tmp;
125 }
126 while (current != head);
127
128 head = nullptr;
129 }
130
131 ~hash_table_bucket()
132 {
133 clear();
134 }
135 };
136
137 struct hash_single_policy
138 {
139 // TODO: umap/uset operations
140
141 template<class Table, class Key>
142 static typename Table::size_type count(const Table& table, const Key& key)
143 {
144 return table.find(key) == table.end() ? 0 : 1;
145 }
146
147 template<class Table, class Key>
148 static typename Table::hint_type find_insertion_spot(const Table& table, const Key& key)
149 {
150 auto idx = table.get_bucket_idx_(key);
151 return make_tuple(
152 &table.table_[idx],
153 table.table_[idx].head,
154 idx
155 );
156 }
157
158 template<class Table, class Key>
159 static typename Table::size_type erase(Table& table, const Key& key)
160 {
161 auto idx = table.get_bucket_idx_(key);
162 auto head = table.table_[idx].head;
163 auto current = head;
164
165 do
166 {
167 if (table.key_eq_(key, table.key_extractor_(current->value)))
168 {
169 --table.size_;
170
171 if (current == head)
172 {
173 if (current->next != head)
174 table.table_[idx].head = current->next;
175 else
176 table.table_[idx].head = nullptr;
177 }
178
179 current->unlink();
180 delete current;
181
182 return 1;
183 }
184 else
185 current = current->next;
186 }
187 while (current != head);
188
189 return 0;
190 }
191
192 template<class Table, class Key>
193 static pair<
194 typename Table::iterator,
195 typename Table::iterator
196 > equal_range(Table& table, const Key& key)
197 {
198 auto it = table.find(key);
199 return make_pair(it, ++it);
200 }
201
202 template<class Table, class Key>
203 static pair<
204 typename Table::const_iterator,
205 typename Table::const_iterator
206 > equal_range_const(Table& table, const Key& key)
207 { // Note: We cannot overload by return type, so we use a different name.
208 auto it = table.find(key);
209 return make_pair(it, ++it);
210 }
211 };
212
213 struct hash_multi_policy
214 {
215 // TODO: umultimap/umultiset operations
216
217 template<class Table, class Key>
218 static typename Table::size_type count(const Table& table, const Key& key)
219 {
220 auto head = table.table_[get_bucket_idx_(key)].head;
221 if (!head)
222 return 0;
223
224 auto current = head;
225 typename Table::size_type res = 0;
226 do
227 {
228 if (table.key_eq_(key, table.key_extractor_(current->value)))
229 ++res;
230
231 current = current->next;
232 }
233 while (current != head);
234
235 return res;
236 }
237
238 template<class Table, class Key>
239 static typename Table::hint_type find_insertion_spot(const Table& table, const Key& key)
240 {
241 // TODO: find the right bucket, in that, find the right
242 // link (if key is already in it, place the new copy
243 // next to it, otherwise just return head)
244 }
245
246 template<class Table, class Key>
247 static typename Table::size_type erase(Table& table, const Key& key)
248 {
249 // TODO: erase all items with given key
250 }
251
252 template<class Table, class Key>
253 static pair<
254 typename Table::iterator,
255 typename Table::iterator
256 > equal_range(Table& table, const Key& key)
257 {
258 // TODO: implement
259 }
260
261 template<class Table, class Key>
262 static pair<
263 typename Table::const_iterator,
264 typename Table::const_iterator
265 > equal_range_const(Table& table, const Key& key)
266 {
267 // TODO: implement
268 }
269 };
270
271 template<class Value, class ConstReference, class ConstPointer, class Size>
272 class hash_table_const_iterator
273 {
274 public:
275 using value_type = Value;
276 using size_type = Size;
277 using const_reference = ConstReference;
278 using const_pointer = ConstPointer;
279 using difference_type = ptrdiff_t;
280
281 using iterator_category = forward_iterator_tag;
282
283 hash_table_const_iterator(const hash_table_bucket<value_type, size_type>* table = nullptr,
284 size_type idx = size_type{}, size_type max_idx = size_type{},
285 const list_node<value_type>* current = nullptr)
286 : table_{table}, idx_{idx}, max_idx_{max_idx}, current_{current}
287 { /* DUMMY BODY */ }
288
289 hash_table_const_iterator(const hash_table_const_iterator&) = default;
290 hash_table_const_iterator& operator=(const hash_table_const_iterator&) = default;
291
292 const_reference operator*() const
293 {
294 return current_->value;
295 }
296
297 const_pointer operator->() const
298 {
299 return &current_->value;
300 }
301
302 hash_table_const_iterator& operator++()
303 {
304 current_ = current_->next;
305 if (current_ == table_[idx_].head)
306 {
307 if (idx_ < max_idx_)
308 {
309 while (!table_[++idx_].head)
310 { /* DUMMY BODY */ }
311
312 if (idx_ < max_idx_)
313 current_ = table_[idx_].head;
314 else
315 current_ = nullptr;
316 }
317 else
318 current_ = nullptr;
319 }
320
321 return *this;
322 }
323
324 hash_table_const_iterator operator++(int)
325 {
326 auto tmp_current = current_;
327 auto tmp_idx = idx_;
328
329 current_ = current_->next;
330 if (current_ == table_[idx_].head)
331 {
332 if (idx_ < max_idx_)
333 {
334 while (!table_[++idx_].head)
335 { /* DUMMY BODY */ }
336
337 if (idx_ < max_idx_)
338 current_ = table_[idx_].head;
339 else
340 current_ = nullptr;
341 }
342 else
343 current_ = nullptr;
344 }
345
346 return hash_table_const_iterator{
347 table_, tmp_idx, max_idx_, tmp_current
348 };
349 }
350
351 list_node<value_type>* node()
352 {
353 return const_cast<list_node<value_type>*>(current_);
354 }
355
356 const list_node<value_type>* node() const
357 {
358 return current_;
359 }
360
361 size_type idx() const
362 {
363 return idx_;
364 }
365
366 private:
367 const hash_table_bucket<value_type, size_type>* table_;
368 size_type idx_;
369 size_type max_idx_;
370 const list_node<value_type>* current_;
371 };
372
373 template<class Value, class ConstRef, class ConstPtr, class Size>
374 bool operator==(const hash_table_const_iterator<Value, ConstRef, ConstPtr, Size>& lhs,
375 const hash_table_const_iterator<Value, ConstRef, ConstPtr, Size>& rhs)
376 {
377 return lhs.node() == rhs.node();
378 }
379
380 template<class Value, class ConstRef, class ConstPtr, class Size>
381 bool operator!=(const hash_table_const_iterator<Value, ConstRef, ConstPtr, Size>& lhs,
382 const hash_table_const_iterator<Value, ConstRef, ConstPtr, Size>& rhs)
383 {
384 return !(lhs == rhs);
385 }
386
387 template<class Value, class Reference, class Pointer, class Size>
388 class hash_table_iterator
389 {
390 public:
391 using value_type = Value;
392 using size_type = Size;
393 using reference = Reference;
394 using pointer = Pointer;
395 using difference_type = ptrdiff_t;
396
397 using iterator_category = forward_iterator_tag;
398
399 hash_table_iterator(hash_table_bucket<value_type, size_type>* table = nullptr,
400 size_type idx = size_type{}, size_type max_idx = size_type{},
401 list_node<value_type>* current = nullptr)
402 : table_{table}, idx_{idx}, max_idx_{max_idx}, current_{current}
403 { /* DUMMY BODY */ }
404
405 hash_table_iterator(const hash_table_iterator&) = default;
406 hash_table_iterator& operator=(const hash_table_iterator&) = default;
407
408 reference operator*()
409 {
410 return current_->value;
411 }
412
413 pointer operator->()
414 {
415 return &current_->value;
416 }
417
418 hash_table_iterator& operator++()
419 {
420 current_ = current_->next;
421 if (current_ == table_[idx_].head)
422 {
423 if (idx_ < max_idx_)
424 {
425 while (!table_[++idx_].head)
426 { /* DUMMY BODY */ }
427
428 if (idx_ < max_idx_)
429 current_ = table_[idx_].head;
430 else
431 current_ = nullptr;
432 }
433 else
434 current_ = nullptr;
435 }
436
437 return *this;
438 }
439
440 hash_table_iterator operator++(int)
441 {
442 auto tmp_current = current_;
443 auto tmp_idx = idx_;
444
445 current_ = current_->next;
446 if (current_ == table_[idx_].head)
447 {
448 if (idx_ < max_idx_)
449 {
450 while (!table_[++idx_].head)
451 { /* DUMMY BODY */ }
452
453 if (idx_ < max_idx_)
454 current_ = table_[idx_].head;
455 else
456 current_ = nullptr;
457 }
458 else
459 current_ = nullptr;
460 }
461
462 return hash_table_iterator{
463 table_, tmp_idx, max_idx_, tmp_current
464 };
465 }
466
467 list_node<value_type>* node()
468 {
469 return current_;
470 }
471
472 const list_node<value_type>* node() const
473 {
474 return current_;
475 }
476
477 size_type idx() const
478 {
479 return idx_;
480 }
481
482 template<class ConstRef, class ConstPtr>
483 operator hash_table_const_iterator<
484 Value, ConstRef, ConstPtr, Size
485 >() const
486 {
487 return hash_table_const_iterator<value_type, ConstRef, ConstPtr, size_type>{
488 table_, idx_, max_idx_, current_
489 };
490 }
491
492 private:
493 hash_table_bucket<value_type, size_type>* table_;
494 size_type idx_;
495 size_type max_idx_;
496 list_node<value_type>* current_;
497 };
498
499 template<class Value, class Ref, class Ptr, class Size>
500 bool operator==(const hash_table_iterator<Value, Ref, Ptr, Size>& lhs,
501 const hash_table_iterator<Value, Ref, Ptr, Size>& rhs)
502 {
503 return lhs.node() == rhs.node();
504 }
505
506 template<class Value, class Ref, class Ptr, class Size>
507 bool operator!=(const hash_table_iterator<Value, Ref, Ptr, Size>& lhs,
508 const hash_table_iterator<Value, Ref, Ptr, Size>& rhs)
509 {
510 return !(lhs == rhs);
511 }
512
513 template<class Value, class ConstReference, class ConstPointer>
514 class hash_table_const_local_iterator
515 {
516 public:
517 using value_type = Value;
518 using const_reference = ConstReference;
519 using const_pointer = ConstPointer;
520 using difference_type = ptrdiff_t;
521
522 using iterator_category = forward_iterator_tag;
523
524 // TODO: requirement for forward iterator is default constructibility, fix others!
525 hash_table_const_local_iterator(const list_node<value_type>* head = nullptr,
526 const list_node<value_type>* current = nullptr)
527 : head_{head}, current_{current}
528 { /* DUMMY BODY */ }
529
530 hash_table_const_local_iterator(const hash_table_const_local_iterator&) = default;
531 hash_table_const_local_iterator& operator=(const hash_table_const_local_iterator&) = default;
532
533 const_reference operator*() const
534 {
535 return current_->value;
536 }
537
538 const_pointer operator->() const
539 {
540 return &current_->value;
541 }
542
543 hash_table_const_local_iterator& operator++()
544 {
545 current_ = current_->next;
546 if (current_ == head_)
547 current_ = nullptr;
548
549 return *this;
550 }
551
552 hash_table_const_local_iterator operator++(int)
553 {
554 auto tmp = current_;
555 current_ = current_->next;
556 if (current_ == head_)
557 current_ = nullptr;
558
559 return hash_table_const_local_iterator{head_, tmp};
560 }
561
562
563 list_node<value_type>* node()
564 {
565 return const_cast<list_node<value_type>*>(current_);
566 }
567
568 const list_node<value_type>* node() const
569 {
570 return current_;
571 }
572
573 private:
574 const list_node<value_type>* head_;
575 const list_node<value_type>* current_;
576 };
577
578 template<class Value, class ConstRef, class ConstPtr>
579 bool operator==(const hash_table_const_local_iterator<Value, ConstRef, ConstPtr>& lhs,
580 const hash_table_const_local_iterator<Value, ConstRef, ConstPtr>& rhs)
581 {
582 return lhs.node() == rhs.node();
583 }
584
585 template<class Value, class ConstRef, class ConstPtr>
586 bool operator!=(const hash_table_const_local_iterator<Value, ConstRef, ConstPtr>& lhs,
587 const hash_table_const_local_iterator<Value, ConstRef, ConstPtr>& rhs)
588 {
589 return !(lhs == rhs);
590 }
591
592 template<class Value, class Reference, class Pointer>
593 class hash_table_local_iterator
594 {
595 public:
596 using value_type = Value;
597 using reference = Reference;
598 using pointer = Pointer;
599 using difference_type = ptrdiff_t;
600
601 using iterator_category = forward_iterator_tag;
602
603 hash_table_local_iterator(list_node<value_type>* head = nullptr,
604 list_node<value_type>* current = nullptr)
605 : head_{head}, current_{current}
606 { /* DUMMY BODY */ }
607
608 hash_table_local_iterator(const hash_table_local_iterator&) = default;
609 hash_table_local_iterator& operator=(const hash_table_local_iterator&) = default;
610
611 reference operator*()
612 {
613 return current_->value;
614 }
615
616 pointer operator->()
617 {
618 return &current_->value;
619 }
620
621 hash_table_local_iterator& operator++()
622 {
623 current_ = current_->next;
624 if (current_ == head_)
625 current_ = nullptr;
626
627 return *this;
628 }
629
630 hash_table_local_iterator operator++(int)
631 {
632 auto tmp = current_;
633 current_ = current_->next;
634 if (current_ == head_)
635 current_ = nullptr;
636
637 return hash_table_local_iterator{head_, tmp};
638 }
639
640 list_node<value_type>* node()
641 {
642 return current_;
643 }
644
645 const list_node<value_type>* node() const
646 {
647 return current_;
648 }
649
650 template<class ConstRef, class ConstPtr>
651 operator hash_table_const_local_iterator<
652 Value, ConstRef, ConstPtr
653 >() const
654 {
655 return hash_table_const_local_iterator<
656 value_type, ConstRef, ConstPtr
657 >{head_, current_};
658 }
659
660 private:
661 list_node<value_type>* head_;
662 list_node<value_type>* current_;
663 };
664
665 template<class Value, class Ref, class Ptr>
666 bool operator==(const hash_table_local_iterator<Value, Ref, Ptr>& lhs,
667 const hash_table_local_iterator<Value, Ref, Ptr>& rhs)
668 {
669 return lhs.node() == rhs.node();
670 }
671
672 template<class Value, class Ref, class Ptr>
673 bool operator!=(const hash_table_local_iterator<Value, Ref, Ptr>& lhs,
674 const hash_table_local_iterator<Value, Ref, Ptr>& rhs)
675 {
676 return !(lhs == rhs);
677 }
678
679 template<
680 class Value, class Key, class KeyExtractor,
681 class Hasher, class KeyEq,
682 class Alloc, class Size,
683 class Iterator, class ConstIterator,
684 class LocalIterator, class ConstLocalIterator,
685 class Policy
686 >
687 class hash_table
688 {
689 public:
690 using value_type = Value;
691 using key_type = Key;
692 using size_type = Size;
693 using allocator_type = Alloc;
694 using key_equal = KeyEq;
695 using hasher = Hasher;
696 using key_extract = KeyExtractor;
697
698 using iterator = Iterator;
699 using const_iterator = ConstIterator;
700 using local_iterator = LocalIterator;
701 using const_local_iterator = ConstLocalIterator;
702
703 using node_type = list_node<value_type>;
704
705 using hint_type = tuple<
706 hash_table_bucket<value_type, size_type>*,
707 list_node<value_type>*, size_type
708 >;
709
710 hash_table(size_type buckets, float max_load_factor = 1.f)
711 : table_{new hash_table_bucket<value_type, size_type>[buckets]()},
712 bucket_count_{buckets}, size_{}, hasher_{}, key_eq_{},
713 key_extractor_{}, max_load_factor_{max_load_factor}
714 { /* DUMMY BODY */ }
715
716 hash_table(size_type buckets, const hasher& hf, const key_equal& eql,
717 float max_load_factor = 1.f)
718 : table_{new hash_table_bucket<value_type, size_type>[buckets]()},
719 bucket_count_{buckets}, size_{}, hasher_{hf}, key_eq_{eql},
720 key_extractor_{}, max_load_factor_{max_load_factor}
721 { /* DUMMY BODY */ }
722
723 hash_table(const hash_table& other)
724 : hash_table{other.bucket_count_, other.hasher_, other.key_eq_,
725 other.max_load_factor_}
726 {
727 for (const auto& x: other)
728 {
729 auto spot = find_insertion_spot(key_extractor_(x));
730 insert(spot, x);
731 }
732 }
733
734 hash_table(hash_table&& other)
735 : table_{other.table_}, bucket_count_{other.bucket_count_},
736 size_{other.size_}, hasher_{move(other.hasher_)},
737 key_eq_{move(other.key_eq_)}, key_extractor_{move(other.key_extractor_)},
738 max_load_factor_{other.max_load_factor_}
739 {
740 other.table_ = nullptr;
741 other.bucket_count_ = size_type{};
742 other.size_ = size_type{};
743 other.max_load_factor_ = 1.f;
744 }
745
746 hash_table& operator=(const hash_table& other)
747 {
748 hash_table tmp{other};
749 tmp.swap(*this);
750
751 return *this;
752 }
753
754 hash_table& operator=(hash_table&& other)
755 {
756 hash_table tmp{move(other)};
757 tmp.swap(*this);
758
759 return *this;
760 }
761
762 bool empty() const noexcept
763 {
764 return size_ == 0;
765 }
766
767 size_type size() const noexcept
768 {
769 return size_;
770 }
771
772 template<class Allocator>
773 size_type max_size(Allocator& alloc)
774 {
775 return allocator_traits<Allocator>::max_size(alloc);
776 }
777
778 iterator begin() noexcept
779 {
780 return iterator{
781 table_, size_type{}, bucket_count_,
782 table_[0].head
783 };
784 }
785
786 const_iterator begin() const noexcept
787 {
788 return cbegin();
789 }
790
791 iterator end() noexcept
792 {
793 return iterator{};
794 }
795
796 const_iterator end() const noexcept
797 {
798 return cend();
799 }
800
801 const_iterator cbegin() const noexcept
802 {
803 return const_iterator{
804 table_, size_type{}, bucket_count_,
805 table_[0].head
806 };
807 }
808
809 const_iterator cend() const noexcept
810 {
811 return const_iterator{};
812 }
813
814 template<class Allocator, class... Args>
815 void emplace(const hint_type& where, Allocator& alloc, Args&&... args)
816 {
817 if (!hint_ok_(where))
818 return;
819
820 auto node = new list_node<value_type>{forward<Args&&>(args)...};
821 if (get<1>(where) == nullptr) // Append here will create a new head.
822 get<0>(where)->append(node);
823 else // Prepending before an exact position is common in the standard.
824 get<1>(where)->prepend(node);
825
826 ++size_;
827
828 rehash_if_needed();
829 }
830
831 void insert(const hint_type& where, const value_type& val)
832 {
833 if (!hint_ok_(where))
834 return;
835
836 auto node = new list_node<value_type>{val};
837 if (get<1>(where) == nullptr)
838 get<0>(where)->append(node);
839 else
840 get<1>(where)->prepend(node);
841
842 ++size_;
843
844 rehash_if_needed();
845 }
846
847 void insert(const hint_type& where, value_type&& val)
848 {
849 if (!hint_ok_(where))
850 return;
851
852 auto node = new list_node<value_type>{forward<value_type>(val)};
853 if (get<1>(where) == nullptr)
854 get<0>(where)->append(node);
855 else
856 get<1>(where)->prepend(node);
857
858 ++size_;
859
860 rehash_if_needed();
861 }
862
863 size_type erase(const key_type& key)
864 {
865 return Policy::erase(*this, key);
866 }
867
868 iterator erase(const_iterator it)
869 {
870 auto node = it.node();
871 auto idx = it.idx();
872
873 /**
874 * Note: This way we will continue on the next bucket
875 * if this is the last element in its bucket.
876 */
877 iterator res{table_, idx, size_, node};
878 ++res;
879
880 if (table_[idx].head == node)
881 {
882 if (node->next != node)
883 table_[idx].head = node->next;
884 else
885 table_[idx].head = nullptr;
886 }
887
888 node->unlink();
889 delete node;
890
891 return res;
892 }
893
894 void clear() noexcept
895 {
896 for (size_type i = 0; i < bucket_count_; ++i)
897 table_[i].clear();
898 size_ = size_type{};
899 }
900
901 void swap(hash_table& other)
902 noexcept(allocator_traits<allocator_type>::is_always_equal::value &&
903 noexcept(swap(declval<Hasher&>(), declval<Hasher&>())) &&
904 noexcept(swap(declval<KeyEq&>(), declval<KeyEq&>())))
905 {
906 std::swap(table_, other.table_);
907 std::swap(bucket_count_, other.bucket_count_);
908 std::swap(size_, other.size_);
909 std::swap(hasher_, other.hasher_);
910 std::swap(key_eq_, other.key_eq_);
911 std::swap(max_load_factor_, other.max_load_factor_);
912 }
913
914 hasher hash_function() const
915 {
916 return hasher_;
917 }
918
919 key_equal key_eq() const
920 {
921 return key_eq_;
922 }
923
924 iterator find(const key_type& key)
925 {
926 auto idx = get_bucket_idx_(key);
927 auto head = table_[idx].head;
928
929 if (!head)
930 return end();
931
932 auto current = head;
933 do
934 {
935 if (key_eq_(key, key_extractor_(current->value)))
936 return iterator{table_, idx, size_, current};
937 current = current->next;
938 }
939 while (current != head);
940
941 return end();
942 }
943
944 const_iterator find(const key_type& key) const
945 {
946 auto idx = get_bucket_idx_(key);
947 auto head = table_[idx].head;
948
949 if (!head)
950 return end();
951
952 auto current = head;
953 do
954 {
955 if (key_eq_(key, key_extractor_(current->value)))
956 return iterator{table_, idx, size_, current};
957 current = current->next;
958 }
959 while (current != head);
960
961 return end();
962 }
963
964 size_type count(const key_type& key) const
965 {
966 return Policy::count(*this, key);
967 }
968
969 pair<iterator, iterator> equal_range(const key_type& key)
970 {
971 return Policy::equal_range(*this, key);
972 }
973
974 pair<const_iterator, const_iterator> equal_range(const key_type& key) const
975 {
976 return Policy::equal_range_const(*this, key);
977 }
978
979 size_type bucket_count() const noexcept
980 {
981 return bucket_count_;
982 }
983
984 size_type max_bucket_count() const noexcept
985 {
986 return numeric_limits<size_type>::max() /
987 sizeof(hash_table_bucket<value_type, size_type>);
988 }
989
990 size_type bucket_size(size_type n) const
991 {
992 return table_[n].size();
993 }
994
995 size_type bucket(const key_type& key) const
996 {
997 return get_bucket_idx_(key);
998 }
999
1000 local_iterator begin(size_type n)
1001 {
1002 return local_iterator{table_[n].head, table_[n].head};
1003 }
1004
1005 const_local_iterator begin(size_type n) const
1006 {
1007 return cbegin(n);
1008 }
1009
1010 local_iterator end(size_type n)
1011 {
1012 return local_iterator{};
1013 }
1014
1015 const_local_iterator end(size_type n) const
1016 {
1017 return cend(n);
1018 }
1019
1020 const_local_iterator cbegin(size_type n) const
1021 {
1022 return const_local_iterator{table_[n].head, table_[n].head};
1023 }
1024
1025 const_local_iterator cend(size_type n) const
1026 {
1027 return const_local_iterator{};
1028 }
1029
1030 float load_factor() const noexcept
1031 {
1032 return size_ / static_cast<float>(bucket_count_);
1033 }
1034
1035 float max_load_factor() const noexcept
1036 {
1037 return max_load_factor_;
1038 }
1039
1040 void max_load_factor(float factor)
1041 {
1042 if (factor > 0.f)
1043 max_load_factor_ = factor;
1044
1045 rehash_if_needed();
1046 }
1047
1048 void rehash(size_type count)
1049 {
1050 if (count < size_ / max_load_factor_)
1051 count = size_ / max_load_factor_;
1052
1053 /**
1054 * Note: If an exception is thrown, there
1055 * is no effect. Since this is the only
1056 * place where an exception (no mem) can
1057 * be thrown and no changes to this have been
1058 * made, we're ok.
1059 */
1060 hash_table new_table{count, max_load_factor_};
1061
1062 for (std::size_t i = 0; i < bucket_count_; ++i)
1063 {
1064 auto head = table_[i].head;
1065 if (!head)
1066 continue;
1067
1068 auto current = head;
1069
1070 do
1071 {
1072 auto next = current->next;
1073
1074 current->next = current;
1075 current->prev = current;
1076
1077 auto where = Policy::find_insertion_spot(
1078 new_table, key_extractor_(current->value)
1079 );
1080
1081 /**
1082 * Note: We're rehashing, so we know each
1083 * key can be inserted.
1084 */
1085 auto new_bucket = get<0>(where);
1086 auto new_successor = get<1>(where);
1087
1088 if (new_successor)
1089 new_successor->append(current);
1090 else
1091 new_bucket->append(current);
1092
1093 current = next;
1094 } while (current != head);
1095
1096 table_[i].head = nullptr;
1097 }
1098
1099 new_table.size_ = size_;
1100 swap(new_table);
1101
1102 delete[] new_table.table_;
1103 new_table.table_ = nullptr;
1104 }
1105
1106 void reserve(size_type count)
1107 {
1108 rehash(count / max_load_factor_ + 1);
1109 }
1110
1111 bool is_eq_to(const hash_table& other)
1112 {
1113 // TODO: implement
1114 return false;
1115 }
1116
1117 ~hash_table()
1118 {
1119 // Lists are deleted in ~hash_table_bucket.
1120 if (table_)
1121 delete[] table_;
1122 }
1123
1124 hint_type find_insertion_spot(const key_type& key)
1125 {
1126 return Policy::find_insertion_spot(*this, key);
1127 }
1128
1129 hint_type find_insertion_spot(key_type&& key)
1130 {
1131 return Policy::find_insertion_spot(*this, key);
1132 }
1133
1134 const key_type& get_key(const value_type& val)
1135 {
1136 return key_extractor_(val);
1137 }
1138
1139 bool keys_equal(const key_type& key, const value_type& val)
1140 {
1141 return key_eq_(key, key_extractor_(val));
1142 }
1143
1144 hash_table_bucket<value_type, size_type>* table()
1145 {
1146 return table_;
1147 }
1148
1149 hash_table_bucket<value_type, size_type>* head(size_type idx)
1150 {
1151 if (idx < bucket_count_)
1152 return &table_[idx];
1153 else
1154 return nullptr;
1155 }
1156
1157 void rehash_if_needed()
1158 {
1159 if (size_ > max_load_factor_ * bucket_count_)
1160 rehash(bucket_count_ * bucket_count_growth_factor_);
1161 }
1162
1163 void increment_size()
1164 {
1165 ++size_;
1166
1167 rehash_if_needed();
1168 }
1169
1170 void decrement_size()
1171 {
1172 --size_;
1173 }
1174
1175 node_type* find_node_or_return_head(const key_type& key,
1176 const hash_table_bucket<value_type, size_type>& bucket)
1177 {
1178 if (bucket.head)
1179 {
1180 auto head = bucket.head;
1181 auto current = bucket.head;
1182
1183 do
1184 {
1185 if (keys_equal(key, current->value))
1186 return current;
1187 else
1188 current = current->next;
1189 }
1190 while (current != head);
1191
1192 return head;
1193 }
1194 else
1195 return nullptr;
1196 }
1197
1198 private:
1199 hash_table_bucket<value_type, size_type>* table_;
1200 size_type bucket_count_;
1201 size_type size_;
1202 hasher hasher_;
1203 key_equal key_eq_;
1204 key_extract key_extractor_;
1205 float max_load_factor_;
1206
1207 static constexpr float bucket_count_growth_factor_{1.25};
1208
1209 size_type get_bucket_idx_(const key_type& key) const
1210 {
1211 return hasher_(key) % bucket_count_;
1212 }
1213
1214 bool hint_ok_(const hint_type& hint)
1215 {
1216 // TODO: pass this to the policy, because the multi policy
1217 // will need to check if a similar key is close,
1218 // that is something like:
1219 // return get<1>(hint)->prev->key == key || !bucket.contains(key)
1220 // TODO: also, make it public and make hint usage one level above?
1221 // (since we already have insert with decisive hint)
1222 return get<0>(hint) != nullptr && get<2>(hint) < bucket_count_;
1223 }
1224
1225 // Praise C++11 for this.
1226 friend Policy;
1227 };
1228}
1229
1230#endif
Note: See TracBrowser for help on using the repository browser.