source: mainline/uspace/lib/cpp/include/internal/hash_table.hpp@ 379ce989

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

cpp: added a prepend function to table bucket, this helps us keep equal keys in multi containers together

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