source: mainline/uspace/lib/cpp/include/internal/hash_table.hpp@ 7f379fe

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

cpp: implemented multi policy operations, fixed constness of some parameters

  • Property mode set to 100644
File size: 39.4 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 template<class Table, class Key>
140 static typename Table::size_type count(const Table& table, const Key& key)
141 {
142 return table.find(key) == table.end() ? 0 : 1;
143 }
144
145 template<class Table, class Key>
146 static typename Table::hint_type find_insertion_spot(const Table& table, const Key& key)
147 {
148 auto idx = table.get_bucket_idx_(key);
149 return make_tuple(
150 &table.table_[idx],
151 table.table_[idx].head,
152 idx
153 );
154 }
155
156 template<class Table, class Key>
157 static typename Table::size_type erase(Table& table, const Key& key)
158 {
159 auto idx = table.get_bucket_idx_(key);
160 auto head = table.table_[idx].head;
161 auto current = head;
162
163 do
164 {
165 if (table.key_eq_(key, table.key_extractor_(current->value)))
166 {
167 --table.size_;
168
169 if (current == head)
170 {
171 if (current->next != head)
172 table.table_[idx].head = current->next;
173 else
174 table.table_[idx].head = nullptr;
175 }
176
177 current->unlink();
178 delete current;
179
180 return 1;
181 }
182 else
183 current = current->next;
184 }
185 while (current != head);
186
187 return 0;
188 }
189
190 template<class Table, class Key>
191 static pair<
192 typename Table::iterator,
193 typename Table::iterator
194 > equal_range(const Table& table, const Key& key)
195 {
196 auto it = table.find(key);
197 return make_pair(it, ++it);
198 }
199
200 template<class Table, class Key>
201 static pair<
202 typename Table::const_iterator,
203 typename Table::const_iterator
204 > equal_range_const(const Table& table, const Key& key)
205 { // Note: We cannot overload by return type, so we use a different name.
206 auto it = table.find(key);
207 return make_pair(it, ++it);
208 }
209 };
210
211 struct hash_multi_policy
212 {
213 template<class Table, class Key>
214 static typename Table::size_type count(const Table& table, const Key& key)
215 {
216 auto head = table.table_[get_bucket_idx_(key)].head;
217 if (!head)
218 return 0;
219
220 auto current = head;
221 typename Table::size_type res = 0;
222 do
223 {
224 if (table.key_eq_(key, table.key_extractor_(current->value)))
225 ++res;
226
227 current = current->next;
228 }
229 while (current != head);
230
231 return res;
232 }
233
234 template<class Table, class Key>
235 static typename Table::hint_type find_insertion_spot(const Table& table, const Key& key)
236 {
237 auto idx = table.get_bucket_idx_(key);
238 auto head = table.table_[idx].head;
239
240 if (head)
241 {
242 auto current = head;
243 do
244 {
245 if (table.keys_equal(key, table.get_key(current->value)))
246 {
247 return make_tuple(
248 &table.table_[idx],
249 current,
250 idx
251 );
252 }
253
254 current = current->next;
255 } while (current != head);
256 }
257
258 return make_tuple(
259 &table.table_[idx],
260 table.table_[idx].head,
261 idx
262 );
263 }
264
265 template<class Table, class Key>
266 static typename Table::size_type erase(Table& table, const Key& key)
267 {
268 auto idx = table.get_bucket_idx_(key);
269 auto it = table.begin(it);
270 typename Table::size_type res{};
271
272 while (it != table.end(it))
273 {
274 if (table.keys_equal(key, table.get_key(*it)))
275 {
276 while (table.keys_equal(key, table.get_key(*it)))
277 {
278 auto node = it.node();
279 ++it;
280 ++res;
281
282 node.unlink();
283 delete node;
284 --table.size_;
285 }
286
287 // Elements with equal keys are next to each other.
288 return res;
289 }
290
291 ++it;
292 }
293
294 return res;
295 }
296
297 template<class Table, class Key>
298 static pair<
299 typename Table::iterator,
300 typename Table::iterator
301 > equal_range(const Table& table, const Key& key)
302 {
303 auto first = table.find(key);
304 if (first == table.end())
305 return make_pair(end(), end());
306
307 auto last = first;
308 while (table.keys_equal(key, table.get_key(*last)))
309 ++last;
310
311 // The second iterator points one behind the range.
312 ++last;
313
314 return make_pair(first, last);
315 }
316
317 template<class Table, class Key>
318 static pair<
319 typename Table::const_iterator,
320 typename Table::const_iterator
321 > equal_range_const(const Table& table, const Key& key)
322 {
323 auto first = table.find(key);
324 if (first == table.end())
325 return make_pair(end(), end());
326
327 auto last = first;
328 while (table.keys_equal(key, table.get_key(*last)))
329 ++last;
330
331 // The second iterator points one behind the range.
332 ++last;
333
334 return make_pair(first, last);
335 }
336 };
337
338 template<class Value, class ConstReference, class ConstPointer, class Size>
339 class hash_table_const_iterator
340 {
341 public:
342 using value_type = Value;
343 using size_type = Size;
344 using const_reference = ConstReference;
345 using const_pointer = ConstPointer;
346 using difference_type = ptrdiff_t;
347
348 using iterator_category = forward_iterator_tag;
349
350 hash_table_const_iterator(const hash_table_bucket<value_type, size_type>* table = nullptr,
351 size_type idx = size_type{}, size_type max_idx = size_type{},
352 const list_node<value_type>* current = nullptr)
353 : table_{table}, idx_{idx}, max_idx_{max_idx}, current_{current}
354 { /* DUMMY BODY */ }
355
356 hash_table_const_iterator(const hash_table_const_iterator&) = default;
357 hash_table_const_iterator& operator=(const hash_table_const_iterator&) = default;
358
359 const_reference operator*() const
360 {
361 return current_->value;
362 }
363
364 const_pointer operator->() const
365 {
366 return &current_->value;
367 }
368
369 hash_table_const_iterator& operator++()
370 {
371 current_ = current_->next;
372 if (current_ == table_[idx_].head)
373 {
374 if (idx_ < max_idx_)
375 {
376 while (!table_[++idx_].head)
377 { /* DUMMY BODY */ }
378
379 if (idx_ < max_idx_)
380 current_ = table_[idx_].head;
381 else
382 current_ = nullptr;
383 }
384 else
385 current_ = nullptr;
386 }
387
388 return *this;
389 }
390
391 hash_table_const_iterator operator++(int)
392 {
393 auto tmp_current = current_;
394 auto tmp_idx = idx_;
395
396 current_ = current_->next;
397 if (current_ == table_[idx_].head)
398 {
399 if (idx_ < max_idx_)
400 {
401 while (!table_[++idx_].head)
402 { /* DUMMY BODY */ }
403
404 if (idx_ < max_idx_)
405 current_ = table_[idx_].head;
406 else
407 current_ = nullptr;
408 }
409 else
410 current_ = nullptr;
411 }
412
413 return hash_table_const_iterator{
414 table_, tmp_idx, max_idx_, tmp_current
415 };
416 }
417
418 list_node<value_type>* node()
419 {
420 return const_cast<list_node<value_type>*>(current_);
421 }
422
423 const list_node<value_type>* node() const
424 {
425 return current_;
426 }
427
428 size_type idx() const
429 {
430 return idx_;
431 }
432
433 private:
434 const hash_table_bucket<value_type, size_type>* table_;
435 size_type idx_;
436 size_type max_idx_;
437 const list_node<value_type>* current_;
438 };
439
440 template<class Value, class ConstRef, class ConstPtr, class Size>
441 bool operator==(const hash_table_const_iterator<Value, ConstRef, ConstPtr, Size>& lhs,
442 const hash_table_const_iterator<Value, ConstRef, ConstPtr, Size>& rhs)
443 {
444 return lhs.node() == rhs.node();
445 }
446
447 template<class Value, class ConstRef, class ConstPtr, class Size>
448 bool operator!=(const hash_table_const_iterator<Value, ConstRef, ConstPtr, Size>& lhs,
449 const hash_table_const_iterator<Value, ConstRef, ConstPtr, Size>& rhs)
450 {
451 return !(lhs == rhs);
452 }
453
454 template<class Value, class Reference, class Pointer, class Size>
455 class hash_table_iterator
456 {
457 public:
458 using value_type = Value;
459 using size_type = Size;
460 using reference = Reference;
461 using pointer = Pointer;
462 using difference_type = ptrdiff_t;
463
464 using iterator_category = forward_iterator_tag;
465
466 hash_table_iterator(hash_table_bucket<value_type, size_type>* table = nullptr,
467 size_type idx = size_type{}, size_type max_idx = size_type{},
468 list_node<value_type>* current = nullptr)
469 : table_{table}, idx_{idx}, max_idx_{max_idx}, current_{current}
470 { /* DUMMY BODY */ }
471
472 hash_table_iterator(const hash_table_iterator&) = default;
473 hash_table_iterator& operator=(const hash_table_iterator&) = default;
474
475 reference operator*()
476 {
477 return current_->value;
478 }
479
480 pointer operator->()
481 {
482 return &current_->value;
483 }
484
485 hash_table_iterator& operator++()
486 {
487 current_ = current_->next;
488 if (current_ == table_[idx_].head)
489 {
490 if (idx_ < max_idx_)
491 {
492 while (!table_[++idx_].head)
493 { /* DUMMY BODY */ }
494
495 if (idx_ < max_idx_)
496 current_ = table_[idx_].head;
497 else
498 current_ = nullptr;
499 }
500 else
501 current_ = nullptr;
502 }
503
504 return *this;
505 }
506
507 hash_table_iterator operator++(int)
508 {
509 auto tmp_current = current_;
510 auto tmp_idx = idx_;
511
512 current_ = current_->next;
513 if (current_ == table_[idx_].head)
514 {
515 if (idx_ < max_idx_)
516 {
517 while (!table_[++idx_].head)
518 { /* DUMMY BODY */ }
519
520 if (idx_ < max_idx_)
521 current_ = table_[idx_].head;
522 else
523 current_ = nullptr;
524 }
525 else
526 current_ = nullptr;
527 }
528
529 return hash_table_iterator{
530 table_, tmp_idx, max_idx_, tmp_current
531 };
532 }
533
534 list_node<value_type>* node()
535 {
536 return current_;
537 }
538
539 const list_node<value_type>* node() const
540 {
541 return current_;
542 }
543
544 size_type idx() const
545 {
546 return idx_;
547 }
548
549 template<class ConstRef, class ConstPtr>
550 operator hash_table_const_iterator<
551 Value, ConstRef, ConstPtr, Size
552 >() const
553 {
554 return hash_table_const_iterator<value_type, ConstRef, ConstPtr, size_type>{
555 table_, idx_, max_idx_, current_
556 };
557 }
558
559 private:
560 hash_table_bucket<value_type, size_type>* table_;
561 size_type idx_;
562 size_type max_idx_;
563 list_node<value_type>* current_;
564 };
565
566 template<class Value, class Ref, class Ptr, class Size>
567 bool operator==(const hash_table_iterator<Value, Ref, Ptr, Size>& lhs,
568 const hash_table_iterator<Value, Ref, Ptr, Size>& rhs)
569 {
570 return lhs.node() == rhs.node();
571 }
572
573 template<class Value, class Ref, class Ptr, class Size>
574 bool operator!=(const hash_table_iterator<Value, Ref, Ptr, Size>& lhs,
575 const hash_table_iterator<Value, Ref, Ptr, Size>& rhs)
576 {
577 return !(lhs == rhs);
578 }
579
580 template<class Value, class ConstReference, class ConstPointer>
581 class hash_table_const_local_iterator
582 {
583 public:
584 using value_type = Value;
585 using const_reference = ConstReference;
586 using const_pointer = ConstPointer;
587 using difference_type = ptrdiff_t;
588
589 using iterator_category = forward_iterator_tag;
590
591 // TODO: requirement for forward iterator is default constructibility, fix others!
592 hash_table_const_local_iterator(const list_node<value_type>* head = nullptr,
593 const list_node<value_type>* current = nullptr)
594 : head_{head}, current_{current}
595 { /* DUMMY BODY */ }
596
597 hash_table_const_local_iterator(const hash_table_const_local_iterator&) = default;
598 hash_table_const_local_iterator& operator=(const hash_table_const_local_iterator&) = default;
599
600 const_reference operator*() const
601 {
602 return current_->value;
603 }
604
605 const_pointer operator->() const
606 {
607 return &current_->value;
608 }
609
610 hash_table_const_local_iterator& operator++()
611 {
612 current_ = current_->next;
613 if (current_ == head_)
614 current_ = nullptr;
615
616 return *this;
617 }
618
619 hash_table_const_local_iterator operator++(int)
620 {
621 auto tmp = current_;
622 current_ = current_->next;
623 if (current_ == head_)
624 current_ = nullptr;
625
626 return hash_table_const_local_iterator{head_, tmp};
627 }
628
629
630 list_node<value_type>* node()
631 {
632 return const_cast<list_node<value_type>*>(current_);
633 }
634
635 const list_node<value_type>* node() const
636 {
637 return current_;
638 }
639
640 private:
641 const list_node<value_type>* head_;
642 const list_node<value_type>* current_;
643 };
644
645 template<class Value, class ConstRef, class ConstPtr>
646 bool operator==(const hash_table_const_local_iterator<Value, ConstRef, ConstPtr>& lhs,
647 const hash_table_const_local_iterator<Value, ConstRef, ConstPtr>& rhs)
648 {
649 return lhs.node() == rhs.node();
650 }
651
652 template<class Value, class ConstRef, class ConstPtr>
653 bool operator!=(const hash_table_const_local_iterator<Value, ConstRef, ConstPtr>& lhs,
654 const hash_table_const_local_iterator<Value, ConstRef, ConstPtr>& rhs)
655 {
656 return !(lhs == rhs);
657 }
658
659 template<class Value, class Reference, class Pointer>
660 class hash_table_local_iterator
661 {
662 public:
663 using value_type = Value;
664 using reference = Reference;
665 using pointer = Pointer;
666 using difference_type = ptrdiff_t;
667
668 using iterator_category = forward_iterator_tag;
669
670 hash_table_local_iterator(list_node<value_type>* head = nullptr,
671 list_node<value_type>* current = nullptr)
672 : head_{head}, current_{current}
673 { /* DUMMY BODY */ }
674
675 hash_table_local_iterator(const hash_table_local_iterator&) = default;
676 hash_table_local_iterator& operator=(const hash_table_local_iterator&) = default;
677
678 reference operator*()
679 {
680 return current_->value;
681 }
682
683 pointer operator->()
684 {
685 return &current_->value;
686 }
687
688 hash_table_local_iterator& operator++()
689 {
690 current_ = current_->next;
691 if (current_ == head_)
692 current_ = nullptr;
693
694 return *this;
695 }
696
697 hash_table_local_iterator operator++(int)
698 {
699 auto tmp = current_;
700 current_ = current_->next;
701 if (current_ == head_)
702 current_ = nullptr;
703
704 return hash_table_local_iterator{head_, tmp};
705 }
706
707 list_node<value_type>* node()
708 {
709 return current_;
710 }
711
712 const list_node<value_type>* node() const
713 {
714 return current_;
715 }
716
717 template<class ConstRef, class ConstPtr>
718 operator hash_table_const_local_iterator<
719 Value, ConstRef, ConstPtr
720 >() const
721 {
722 return hash_table_const_local_iterator<
723 value_type, ConstRef, ConstPtr
724 >{head_, current_};
725 }
726
727 private:
728 list_node<value_type>* head_;
729 list_node<value_type>* current_;
730 };
731
732 template<class Value, class Ref, class Ptr>
733 bool operator==(const hash_table_local_iterator<Value, Ref, Ptr>& lhs,
734 const hash_table_local_iterator<Value, Ref, Ptr>& rhs)
735 {
736 return lhs.node() == rhs.node();
737 }
738
739 template<class Value, class Ref, class Ptr>
740 bool operator!=(const hash_table_local_iterator<Value, Ref, Ptr>& lhs,
741 const hash_table_local_iterator<Value, Ref, Ptr>& rhs)
742 {
743 return !(lhs == rhs);
744 }
745
746 template<
747 class Value, class Key, class KeyExtractor,
748 class Hasher, class KeyEq,
749 class Alloc, class Size,
750 class Iterator, class ConstIterator,
751 class LocalIterator, class ConstLocalIterator,
752 class Policy
753 >
754 class hash_table
755 {
756 public:
757 using value_type = Value;
758 using key_type = Key;
759 using size_type = Size;
760 using allocator_type = Alloc;
761 using key_equal = KeyEq;
762 using hasher = Hasher;
763 using key_extract = KeyExtractor;
764
765 using iterator = Iterator;
766 using const_iterator = ConstIterator;
767 using local_iterator = LocalIterator;
768 using const_local_iterator = ConstLocalIterator;
769
770 using node_type = list_node<value_type>;
771
772 using hint_type = tuple<
773 hash_table_bucket<value_type, size_type>*,
774 list_node<value_type>*, size_type
775 >;
776
777 hash_table(size_type buckets, float max_load_factor = 1.f)
778 : table_{new hash_table_bucket<value_type, size_type>[buckets]()},
779 bucket_count_{buckets}, size_{}, hasher_{}, key_eq_{},
780 key_extractor_{}, max_load_factor_{max_load_factor}
781 { /* DUMMY BODY */ }
782
783 hash_table(size_type buckets, const hasher& hf, const key_equal& eql,
784 float max_load_factor = 1.f)
785 : table_{new hash_table_bucket<value_type, size_type>[buckets]()},
786 bucket_count_{buckets}, size_{}, hasher_{hf}, key_eq_{eql},
787 key_extractor_{}, max_load_factor_{max_load_factor}
788 { /* DUMMY BODY */ }
789
790 hash_table(const hash_table& other)
791 : hash_table{other.bucket_count_, other.hasher_, other.key_eq_,
792 other.max_load_factor_}
793 {
794 for (const auto& x: other)
795 {
796 auto spot = find_insertion_spot(key_extractor_(x));
797 insert(spot, x);
798 }
799 }
800
801 hash_table(hash_table&& other)
802 : table_{other.table_}, bucket_count_{other.bucket_count_},
803 size_{other.size_}, hasher_{move(other.hasher_)},
804 key_eq_{move(other.key_eq_)}, key_extractor_{move(other.key_extractor_)},
805 max_load_factor_{other.max_load_factor_}
806 {
807 other.table_ = nullptr;
808 other.bucket_count_ = size_type{};
809 other.size_ = size_type{};
810 other.max_load_factor_ = 1.f;
811 }
812
813 hash_table& operator=(const hash_table& other)
814 {
815 hash_table tmp{other};
816 tmp.swap(*this);
817
818 return *this;
819 }
820
821 hash_table& operator=(hash_table&& other)
822 {
823 hash_table tmp{move(other)};
824 tmp.swap(*this);
825
826 return *this;
827 }
828
829 bool empty() const noexcept
830 {
831 return size_ == 0;
832 }
833
834 size_type size() const noexcept
835 {
836 return size_;
837 }
838
839 template<class Allocator>
840 size_type max_size(Allocator& alloc)
841 {
842 return allocator_traits<Allocator>::max_size(alloc);
843 }
844
845 iterator begin() noexcept
846 {
847 return iterator{
848 table_, size_type{}, bucket_count_,
849 table_[0].head
850 };
851 }
852
853 const_iterator begin() const noexcept
854 {
855 return cbegin();
856 }
857
858 iterator end() noexcept
859 {
860 return iterator{};
861 }
862
863 const_iterator end() const noexcept
864 {
865 return cend();
866 }
867
868 const_iterator cbegin() const noexcept
869 {
870 return const_iterator{
871 table_, size_type{}, bucket_count_,
872 table_[0].head
873 };
874 }
875
876 const_iterator cend() const noexcept
877 {
878 return const_iterator{};
879 }
880
881 template<class Allocator, class... Args>
882 void emplace(const hint_type& where, Allocator& alloc, Args&&... args)
883 {
884 if (!hint_ok_(where))
885 return;
886
887 auto node = new list_node<value_type>{forward<Args&&>(args)...};
888 if (get<1>(where) == nullptr) // Append here will create a new head.
889 get<0>(where)->append(node);
890 else // Prepending before an exact position is common in the standard.
891 get<1>(where)->prepend(node);
892
893 ++size_;
894
895 rehash_if_needed();
896 }
897
898 void insert(const hint_type& where, const value_type& val)
899 {
900 if (!hint_ok_(where))
901 return;
902
903 auto node = new list_node<value_type>{val};
904 if (get<1>(where) == nullptr)
905 get<0>(where)->append(node);
906 else
907 get<1>(where)->prepend(node);
908
909 ++size_;
910
911 rehash_if_needed();
912 }
913
914 void insert(const hint_type& where, value_type&& val)
915 {
916 if (!hint_ok_(where))
917 return;
918
919 auto node = new list_node<value_type>{forward<value_type>(val)};
920 if (get<1>(where) == nullptr)
921 get<0>(where)->append(node);
922 else
923 get<1>(where)->prepend(node);
924
925 ++size_;
926
927 rehash_if_needed();
928 }
929
930 size_type erase(const key_type& key)
931 {
932 return Policy::erase(*this, key);
933 }
934
935 iterator erase(const_iterator it)
936 {
937 auto node = it.node();
938 auto idx = it.idx();
939
940 /**
941 * Note: This way we will continue on the next bucket
942 * if this is the last element in its bucket.
943 */
944 iterator res{table_, idx, size_, node};
945 ++res;
946
947 if (table_[idx].head == node)
948 {
949 if (node->next != node)
950 table_[idx].head = node->next;
951 else
952 table_[idx].head = nullptr;
953 }
954
955 node->unlink();
956 delete node;
957
958 return res;
959 }
960
961 void clear() noexcept
962 {
963 for (size_type i = 0; i < bucket_count_; ++i)
964 table_[i].clear();
965 size_ = size_type{};
966 }
967
968 void swap(hash_table& other)
969 noexcept(allocator_traits<allocator_type>::is_always_equal::value &&
970 noexcept(swap(declval<Hasher&>(), declval<Hasher&>())) &&
971 noexcept(swap(declval<KeyEq&>(), declval<KeyEq&>())))
972 {
973 std::swap(table_, other.table_);
974 std::swap(bucket_count_, other.bucket_count_);
975 std::swap(size_, other.size_);
976 std::swap(hasher_, other.hasher_);
977 std::swap(key_eq_, other.key_eq_);
978 std::swap(max_load_factor_, other.max_load_factor_);
979 }
980
981 hasher hash_function() const
982 {
983 return hasher_;
984 }
985
986 key_equal key_eq() const
987 {
988 return key_eq_;
989 }
990
991 iterator find(const key_type& key)
992 {
993 auto idx = get_bucket_idx_(key);
994 auto head = table_[idx].head;
995
996 if (!head)
997 return end();
998
999 auto current = head;
1000 do
1001 {
1002 if (key_eq_(key, key_extractor_(current->value)))
1003 return iterator{table_, idx, size_, current};
1004 current = current->next;
1005 }
1006 while (current != head);
1007
1008 return end();
1009 }
1010
1011 const_iterator find(const key_type& key) const
1012 {
1013 auto idx = get_bucket_idx_(key);
1014 auto head = table_[idx].head;
1015
1016 if (!head)
1017 return end();
1018
1019 auto current = head;
1020 do
1021 {
1022 if (key_eq_(key, key_extractor_(current->value)))
1023 return iterator{table_, idx, size_, current};
1024 current = current->next;
1025 }
1026 while (current != head);
1027
1028 return end();
1029 }
1030
1031 size_type count(const key_type& key) const
1032 {
1033 return Policy::count(*this, key);
1034 }
1035
1036 pair<iterator, iterator> equal_range(const key_type& key)
1037 {
1038 return Policy::equal_range(*this, key);
1039 }
1040
1041 pair<const_iterator, const_iterator> equal_range(const key_type& key) const
1042 {
1043 return Policy::equal_range_const(*this, key);
1044 }
1045
1046 size_type bucket_count() const noexcept
1047 {
1048 return bucket_count_;
1049 }
1050
1051 size_type max_bucket_count() const noexcept
1052 {
1053 return numeric_limits<size_type>::max() /
1054 sizeof(hash_table_bucket<value_type, size_type>);
1055 }
1056
1057 size_type bucket_size(size_type n) const
1058 {
1059 return table_[n].size();
1060 }
1061
1062 size_type bucket(const key_type& key) const
1063 {
1064 return get_bucket_idx_(key);
1065 }
1066
1067 local_iterator begin(size_type n)
1068 {
1069 return local_iterator{table_[n].head, table_[n].head};
1070 }
1071
1072 const_local_iterator begin(size_type n) const
1073 {
1074 return cbegin(n);
1075 }
1076
1077 local_iterator end(size_type n)
1078 {
1079 return local_iterator{};
1080 }
1081
1082 const_local_iterator end(size_type n) const
1083 {
1084 return cend(n);
1085 }
1086
1087 const_local_iterator cbegin(size_type n) const
1088 {
1089 return const_local_iterator{table_[n].head, table_[n].head};
1090 }
1091
1092 const_local_iterator cend(size_type n) const
1093 {
1094 return const_local_iterator{};
1095 }
1096
1097 float load_factor() const noexcept
1098 {
1099 return size_ / static_cast<float>(bucket_count_);
1100 }
1101
1102 float max_load_factor() const noexcept
1103 {
1104 return max_load_factor_;
1105 }
1106
1107 void max_load_factor(float factor)
1108 {
1109 if (factor > 0.f)
1110 max_load_factor_ = factor;
1111
1112 rehash_if_needed();
1113 }
1114
1115 void rehash(size_type count)
1116 {
1117 if (count < size_ / max_load_factor_)
1118 count = size_ / max_load_factor_;
1119
1120 /**
1121 * Note: If an exception is thrown, there
1122 * is no effect. Since this is the only
1123 * place where an exception (no mem) can
1124 * be thrown and no changes to this have been
1125 * made, we're ok.
1126 */
1127 hash_table new_table{count, max_load_factor_};
1128
1129 for (std::size_t i = 0; i < bucket_count_; ++i)
1130 {
1131 auto head = table_[i].head;
1132 if (!head)
1133 continue;
1134
1135 auto current = head;
1136
1137 do
1138 {
1139 auto next = current->next;
1140
1141 current->next = current;
1142 current->prev = current;
1143
1144 auto where = Policy::find_insertion_spot(
1145 new_table, key_extractor_(current->value)
1146 );
1147
1148 /**
1149 * Note: We're rehashing, so we know each
1150 * key can be inserted.
1151 */
1152 auto new_bucket = get<0>(where);
1153 auto new_successor = get<1>(where);
1154
1155 if (new_successor)
1156 new_successor->append(current);
1157 else
1158 new_bucket->append(current);
1159
1160 current = next;
1161 } while (current != head);
1162
1163 table_[i].head = nullptr;
1164 }
1165
1166 new_table.size_ = size_;
1167 swap(new_table);
1168
1169 delete[] new_table.table_;
1170 new_table.table_ = nullptr;
1171 }
1172
1173 void reserve(size_type count)
1174 {
1175 rehash(count / max_load_factor_ + 1);
1176 }
1177
1178 bool is_eq_to(const hash_table& other)
1179 {
1180 // TODO: implement
1181 return false;
1182 }
1183
1184 ~hash_table()
1185 {
1186 // Lists are deleted in ~hash_table_bucket.
1187 if (table_)
1188 delete[] table_;
1189 }
1190
1191 hint_type find_insertion_spot(const key_type& key)
1192 {
1193 return Policy::find_insertion_spot(*this, key);
1194 }
1195
1196 hint_type find_insertion_spot(key_type&& key)
1197 {
1198 return Policy::find_insertion_spot(*this, key);
1199 }
1200
1201 const key_type& get_key(const value_type& val)
1202 {
1203 return key_extractor_(val);
1204 }
1205
1206 bool keys_equal(const key_type& key, const value_type& val)
1207 {
1208 return key_eq_(key, key_extractor_(val));
1209 }
1210
1211 hash_table_bucket<value_type, size_type>* table()
1212 {
1213 return table_;
1214 }
1215
1216 hash_table_bucket<value_type, size_type>* head(size_type idx)
1217 {
1218 if (idx < bucket_count_)
1219 return &table_[idx];
1220 else
1221 return nullptr;
1222 }
1223
1224 void rehash_if_needed()
1225 {
1226 if (size_ > max_load_factor_ * bucket_count_)
1227 rehash(bucket_count_ * bucket_count_growth_factor_);
1228 }
1229
1230 void increment_size()
1231 {
1232 ++size_;
1233
1234 rehash_if_needed();
1235 }
1236
1237 void decrement_size()
1238 {
1239 --size_;
1240 }
1241
1242 node_type* find_node_or_return_head(const key_type& key,
1243 const hash_table_bucket<value_type, size_type>& bucket)
1244 {
1245 if (bucket.head)
1246 {
1247 auto head = bucket.head;
1248 auto current = bucket.head;
1249
1250 do
1251 {
1252 if (keys_equal(key, current->value))
1253 return current;
1254 else
1255 current = current->next;
1256 }
1257 while (current != head);
1258
1259 return head;
1260 }
1261 else
1262 return nullptr;
1263 }
1264
1265 private:
1266 hash_table_bucket<value_type, size_type>* table_;
1267 size_type bucket_count_;
1268 size_type size_;
1269 hasher hasher_;
1270 key_equal key_eq_;
1271 key_extract key_extractor_;
1272 float max_load_factor_;
1273
1274 static constexpr float bucket_count_growth_factor_{1.25};
1275
1276 size_type get_bucket_idx_(const key_type& key) const
1277 {
1278 return hasher_(key) % bucket_count_;
1279 }
1280
1281 bool hint_ok_(const hint_type& hint)
1282 {
1283 // TODO: pass this to the policy, because the multi policy
1284 // will need to check if a similar key is close,
1285 // that is something like:
1286 // return get<1>(hint)->prev->key == key || !bucket.contains(key)
1287 // TODO: also, make it public and make hint usage one level above?
1288 // (since we already have insert with decisive hint)
1289 return get<0>(hint) != nullptr && get<2>(hint) < bucket_count_;
1290 }
1291
1292 // Praise C++11 for this.
1293 friend Policy;
1294 };
1295}
1296
1297#endif
Note: See TracBrowser for help on using the repository browser.