source: mainline/uspace/lib/cpp/include/internal/hash_table.hpp@ 871cfe0c

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

cpp: added insertion, iterators and some misc operations to aux::hash_table

  • Property mode set to 100644
File size: 27.3 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 <memory>
36#include <tuple>
37#include <utility>
38
39namespace std::aux
40{
41 /**
42 * To save code, we're going to implement one hash table
43 * for both unordered_map and unordered_set. To do this,
44 * we create one inner hash table that is oblivious to its
45 * held type (and uses a key extractor to get the key from it)
46 * and two proxies that either use plain Key type or a pair
47 * of a key and a value.
48 * Note: I am aware the naming is very unimaginative here,
49 * not my strong side :)
50 */
51
52 template<class Key, class Value>
53 struct key_value_key_extractor
54 {
55 Key& operator()(pair<Key, Value>& p) const noexcept
56 {
57 return p.first;
58 }
59 };
60
61 template<class Key>
62 struct key_no_value_key_extractor
63 {
64 Key& operator()(Key& k) const noexcept
65 {
66 return k;
67 }
68 };
69
70 struct key_value_allocator
71 { /* DUMMY BODY */ };
72
73 template<class Value, class Size>
74 struct hash_table_bucket
75 {
76 /**
77 * Note: We use a doubly linked list because
78 * we need to use hints, which point to the
79 * element after the hinted spot.
80 */
81
82 list_node<Value>* head;
83
84 Size size() const noexcept
85 {
86 // TODO: implement
87 }
88
89 void append(list_node<Value>* node)
90 {
91 if (!head)
92 head = node;
93 else
94 head->append(node);
95 }
96
97 ~hash_table_bucket()
98 {
99 // TODO: deallocate the entire list
100 }
101 };
102
103 struct hash_single_policy
104 {
105 // TODO: umap/uset operations
106
107 template<class Table, class Key>
108 static typename Table::size_type count(const Table& table, const Key& key)
109 {
110 return table.find(key) == table.end() ? 0 : 1;
111 }
112
113 template<class Table, class Key>
114 static typename Table::hint_type find_insertion_spot(const Table& table, const Key& key)
115 {
116 auto idx = table.get_bucket_idx_(key);
117 return make_tuple(
118 &table.table_[idx],
119 table.table_[idx].head,
120 idx
121 );
122 }
123 };
124
125 struct hash_multi_policy
126 {
127 // TODO: umultimap/umultiset operations
128
129 template<class Table, class Key>
130 static typename Table::size_type count(const Table& table, const Key& key)
131 {
132 auto head = table.table_[get_bucket_idx_(key)].head;
133 if (!head)
134 return 0;
135
136 auto current = head;
137 typename Table::size_type res = 0;
138 do
139 {
140 if (table.key_eq_(key, table.key_extractor_(current->value)))
141 ++res;
142
143 current = current->next;
144 }
145 while (current != head);
146
147 return res;
148 }
149
150 template<class Table, class Key>
151 static typename Table::hint_type find_insertion_spot(const Table& table, const Key& key)
152 {
153 // TODO: find the right bucket, in that, find the right
154 // link (if key is already in it, place the new copy
155 // next to it, otherwise just return head)
156 }
157 };
158
159 template<class Value, class ConstReference, class ConstPointer, class Size>
160 class hash_table_const_iterator
161 {
162 public:
163 using value_type = Value;
164 using size_type = Size;
165 using const_reference = ConstReference;
166 using const_pointer = ConstPointer;
167 using difference_type = ptrdiff_t;
168
169 using iterator_category = forward_iterator_tag;
170
171 hash_table_const_iterator(hash_table_bucket<value_type, size_type>* table = nullptr,
172 size_type idx = size_type{}, size_type max_idx = size_type{},
173 list_node<value_type>* current = nullptr)
174 : table_{table}, idx_{idx}, max_idx_{max_idx}, current_{current}
175 { /* DUMMY BODY */ }
176
177 hash_table_const_iterator(const hash_table_const_iterator&) = default;
178 hash_table_const_iterator& operator=(const hash_table_const_iterator&) = default;
179
180 const_reference operator*() const
181 {
182 return current_->value;
183 }
184
185 const_pointer operator->() const
186 {
187 return &current_->value;
188 }
189
190 hash_table_const_iterator& operator++()
191 {
192 current_ = current_->next;
193 if (current_ == table_[idx_].head)
194 {
195 if (idx_ < max_idx_)
196 {
197 while (!table_[++idx_].head)
198 { /* DUMMY BODY */ }
199
200 if (idx_ < max_idx_)
201 current_ = table_[idx_].head;
202 else
203 current_ = nullptr;
204 }
205 else
206 current_ = nullptr;
207 }
208
209 return *this;
210 }
211
212 hash_table_const_iterator operator++(int)
213 {
214 auto tmp_current = current_;
215 auto tmp_idx = idx_;
216
217 current_ = current_->next;
218 if (current_ == table_[idx_].head)
219 {
220 if (idx_ < max_idx_)
221 {
222 while (!table_[++idx_].head)
223 { /* DUMMY BODY */ }
224
225 if (idx_ < max_idx_)
226 current_ = table_[idx_].head;
227 else
228 current_ = nullptr;
229 }
230 else
231 current_ = nullptr;
232 }
233
234 return hash_table_const_iterator{
235 table_, tmp_idx, max_idx_, tmp_current
236 };
237 }
238
239 list_node<value_type>* node()
240 {
241 return current_;
242 }
243
244 const list_node<value_type>* node() const
245 {
246 return current_;
247 }
248
249 private:
250 hash_table_bucket<value_type, size_type>* table_;
251 size_type idx_;
252 size_type max_idx_;
253 list_node<value_type>* current_;
254 };
255
256 template<class Value, class ConstRef, class ConstPtr, class Size>
257 bool operator==(const hash_table_const_iterator<Value, ConstRef, ConstPtr, Size>& lhs,
258 const hash_table_const_iterator<Value, ConstRef, ConstPtr, Size>& rhs)
259 {
260 return lhs.node() == rhs.node();
261 }
262
263 template<class Value, class ConstRef, class ConstPtr, class Size>
264 bool operator!=(const hash_table_const_iterator<Value, ConstRef, ConstPtr, Size>& lhs,
265 const hash_table_const_iterator<Value, ConstRef, ConstPtr, Size>& rhs)
266 {
267 return !(lhs == rhs);
268 }
269
270 template<class Value, class Reference, class Pointer, class Size>
271 class hash_table_iterator
272 {
273 public:
274 using value_type = Value;
275 using size_type = Size;
276 using reference = Reference;
277 using pointer = Pointer;
278 using difference_type = ptrdiff_t;
279
280 using iterator_category = forward_iterator_tag;
281
282 hash_table_iterator(hash_table_bucket<value_type, size_type>* table = nullptr,
283 size_type idx = size_type{}, size_type max_idx = size_type{},
284 list_node<value_type>* current = nullptr)
285 : table_{table}, idx_{idx}, max_idx_{max_idx}, current_{current}
286 { /* DUMMY BODY */ }
287
288 hash_table_iterator(const hash_table_iterator&) = default;
289 hash_table_iterator& operator=(const hash_table_iterator&) = default;
290
291 reference operator*()
292 {
293 return current_->value;
294 }
295
296 pointer operator->()
297 {
298 return &current_->value;
299 }
300
301 hash_table_iterator& operator++()
302 {
303 current_ = current_->next;
304 if (current_ == table_[idx_].head)
305 {
306 if (idx_ < max_idx_)
307 {
308 while (!table_[++idx_].head)
309 { /* DUMMY BODY */ }
310
311 if (idx_ < max_idx_)
312 current_ = table_[idx_].head;
313 else
314 current_ = nullptr;
315 }
316 else
317 current_ = nullptr;
318 }
319
320 return *this;
321 }
322
323 hash_table_iterator operator++(int)
324 {
325 auto tmp_current = current_;
326 auto tmp_idx = idx_;
327
328 current_ = current_->next;
329 if (current_ == table_[idx_].head)
330 {
331 if (idx_ < max_idx_)
332 {
333 while (!table_[++idx_].head)
334 { /* DUMMY BODY */ }
335
336 if (idx_ < max_idx_)
337 current_ = table_[idx_].head;
338 else
339 current_ = nullptr;
340 }
341 else
342 current_ = nullptr;
343 }
344
345 return hash_table_iterator{
346 table_, tmp_idx, max_idx_, tmp_current
347 };
348 }
349
350 list_node<value_type>* node()
351 {
352 return current_;
353 }
354
355 const list_node<value_type>* node() const
356 {
357 return current_;
358 }
359
360 template<class ConstRef, class ConstPtr>
361 operator hash_table_const_iterator<
362 Value, ConstRef, ConstPtr, Size
363 >() const
364 {
365 return hash_table_const_iterator{
366 table_, idx_, max_idx_, current_
367 };
368 }
369
370 private:
371 hash_table_bucket<value_type, size_type>* table_;
372 size_type idx_;
373 size_type max_idx_;
374 list_node<value_type>* current_;
375 };
376
377 template<class Value, class Ref, class Ptr, class Size>
378 bool operator==(const hash_table_iterator<Value, Ref, Ptr, Size>& lhs,
379 const hash_table_iterator<Value, Ref, Ptr, Size>& rhs)
380 {
381 return lhs.node() == rhs.node();
382 }
383
384 template<class Value, class Ref, class Ptr, class Size>
385 bool operator!=(const hash_table_iterator<Value, Ref, Ptr, Size>& lhs,
386 const hash_table_iterator<Value, Ref, Ptr, Size>& rhs)
387 {
388 return !(lhs == rhs);
389 }
390
391 template<class Value, class ConstReference, class ConstPointer>
392 class hash_table_const_local_iterator
393 {
394 public:
395 using value_type = Value;
396 using const_reference = ConstReference;
397 using const_pointer = ConstPointer;
398 using difference_type = ptrdiff_t;
399
400 using iterator_category = forward_iterator_tag;
401
402 // TODO: requirement for forward iterator is default constructibility, fix others!
403 hash_table_const_local_iterator(list_node<value_type>* head = nullptr,
404 list_node<value_type>* current = nullptr)
405 : head_{head}, current_{current}
406 { /* DUMMY BODY */ }
407
408 hash_table_const_local_iterator(const hash_table_const_local_iterator&) = default;
409 hash_table_const_local_iterator& operator=(const hash_table_const_local_iterator&) = default;
410
411 const_reference operator*() const
412 {
413 return current_->value;
414 }
415
416 const_pointer operator->() const
417 {
418 return &current_->value;
419 }
420
421 hash_table_const_local_iterator& operator++()
422 {
423 current_ = current_->next;
424 if (current_ == head_)
425 current_ = nullptr;
426
427 return *this;
428 }
429
430 hash_table_const_local_iterator operator++(int)
431 {
432 auto tmp = current_;
433 current_ = current_->next;
434 if (current_ == head_)
435 current_ = nullptr;
436
437 return hash_table_const_local_iterator{head_, tmp};
438 }
439
440 list_node<value_type>* node()
441 {
442 return current_;
443 }
444
445 const list_node<value_type>* node() const
446 {
447 return current_;
448 }
449
450 private:
451 list_node<value_type>* head_;
452 list_node<value_type>* current_;
453 };
454
455 template<class Value, class ConstRef, class ConstPtr>
456 bool operator==(const hash_table_const_local_iterator<Value, ConstRef, ConstPtr>& lhs,
457 const hash_table_const_local_iterator<Value, ConstRef, ConstPtr>& rhs)
458 {
459 return lhs.node() == rhs.node();
460 }
461
462 template<class Value, class ConstRef, class ConstPtr>
463 bool operator!=(const hash_table_const_local_iterator<Value, ConstRef, ConstPtr>& lhs,
464 const hash_table_const_local_iterator<Value, ConstRef, ConstPtr>& rhs)
465 {
466 return !(lhs == rhs);
467 }
468
469 template<class Value, class Reference, class Pointer>
470 class hash_table_local_iterator
471 {
472 public:
473 using value_type = Value;
474 using reference = Reference;
475 using pointer = Pointer;
476 using difference_type = ptrdiff_t;
477
478 using iterator_category = forward_iterator_tag;
479
480 hash_table_local_iterator(list_node<value_type>* head = nullptr,
481 list_node<value_type>* current = nullptr)
482 : head_{head}, current_{current}
483 { /* DUMMY BODY */ }
484
485 hash_table_local_iterator(const hash_table_local_iterator&) = default;
486 hash_table_local_iterator& operator=(const hash_table_local_iterator&) = default;
487
488 reference operator*()
489 {
490 return current_->value;
491 }
492
493 pointer operator->()
494 {
495 return &current_->value;
496 }
497
498 hash_table_local_iterator& operator++()
499 {
500 current_ = current_->next;
501 if (current_ == head_)
502 current_ = nullptr;
503
504 return *this;
505 }
506
507 hash_table_local_iterator operator++(int)
508 {
509 auto tmp = current_;
510 current_ = current_->next;
511 if (current_ == head_)
512 current_ = nullptr;
513
514 return hash_table_local_iterator{head_, tmp};
515 }
516
517 list_node<value_type>* node()
518 {
519 return current_;
520 }
521
522 const list_node<value_type>* node() const
523 {
524 return current_;
525 }
526
527 template<class ConstRef, class ConstPtr>
528 operator hash_table_const_local_iterator<
529 Value, ConstRef, ConstPtr
530 >() const
531 {
532 return hash_table_const_local_iterator{head_, current_};
533 }
534
535 private:
536 list_node<value_type>* head_;
537 list_node<value_type>* current_;
538 };
539
540 template<class Value, class Ref, class Ptr>
541 bool operator==(const hash_table_local_iterator<Value, Ref, Ptr>& lhs,
542 const hash_table_local_iterator<Value, Ref, Ptr>& rhs)
543 {
544 return lhs.node() == rhs.node();
545 }
546
547 template<class Value, class Ref, class Ptr>
548 bool operator!=(const hash_table_local_iterator<Value, Ref, Ptr>& lhs,
549 const hash_table_local_iterator<Value, Ref, Ptr>& rhs)
550 {
551 return !(lhs == rhs);
552 }
553
554 template<
555 class Value, class Key, class KeyExtractor,
556 class Hasher, class KeyEq,
557 class Alloc, class Size,
558 class Iterator, class ConstIterator,
559 class LocalIterator, class ConstLocalIterator,
560 class Policy
561 >
562 class hash_table
563 {
564 public:
565 using value_type = Value;
566 using key_type = Key;
567 using size_type = Size;
568 using allocator_type = Alloc;
569 using key_equal = KeyEq;
570 using hasher = Hasher;
571 using key_extract = KeyExtractor;
572
573 using iterator = Iterator;
574 using const_iterator = ConstIterator;
575 using local_iterator = LocalIterator;
576 using const_local_iterator = ConstLocalIterator;
577
578 using hint_type = tuple<
579 hash_table_bucket<value_type, size_type>*,
580 list_node<value_type>*, size_type
581 >;
582
583 hash_table(size_type buckets, float max_load_factor = 1.f)
584 : table_{new hash_table_bucket<value_type, size_type>[buckets]},
585 bucket_count_{buckets}, size_{}, hasher_{}, key_eq_{},
586 key_extractor_{}, max_load_factor_{max_load_factor}
587 { /* DUMMY BODY */ }
588
589 bool empty() const noexcept
590 {
591 return size_ == 0;
592 }
593
594 size_type size() const noexcept
595 {
596 return size_;
597 }
598
599 template<class Allocator>
600 size_type max_size(Allocator& alloc)
601 {
602 return allocator_traits<Allocator>::max_size(alloc);
603 }
604
605 iterator begin() noexcept
606 {
607 return iterator{
608 table_, size_type{}, bucket_count_,
609 table_[0].head
610 };
611 }
612
613 const_iterator begin() const noexcept
614 {
615 return cbegin();
616 }
617
618 iterator end() noexcept
619 {
620 return iterator{};
621 }
622
623 const_iterator end() const noexcept
624 {
625 return cend();
626 }
627
628 const_iterator cbegin() const noexcept
629 {
630 return const_iterator{
631 table_, size_type{}, bucket_count_,
632 table_[0].head
633 };
634 }
635
636 const_iterator cend() const noexcept
637 {
638 return const_iterator{};
639 }
640
641 template<class Allocator, class... Args>
642 iterator emplace(Allocator& alloc, Args&&... args)
643 {
644 // TODO: use allocator traits of allocator_type but pass alloc!
645 // TODO: also, try_emplace should be one level above (we don't know
646 // keys)
647 }
648
649 void insert(const hint_type& where, const value_type& val)
650 {
651 if (!hint_ok_(where))
652 return;
653
654 auto node = new list_node<value_type>{val};
655 if (get<1>(where) == nullptr) // Append here will create a new head.
656 get<0>(where)->append(node);
657 else // Prepending before an exact position is common in the standard.
658 get<1>(where)->prepend(node);
659
660 ++size_;
661 }
662
663 void insert(const hint_type& where, value_type&& val)
664 {
665 if (!hint_ok_(where))
666 return;
667
668 auto node = new list_node<value_type>{forward<value_type>(val)};
669 if (get<1>(where) == nullptr)
670 get<0>(where)->append(node);
671 else
672 get<1>(where)->prepend(node);
673
674 ++size_;
675 }
676
677 size_type erase(const key_type& key)
678 {
679 // TODO: implement
680 }
681
682 iterator erase(const_iterator it)
683 {
684 // TODO: implement
685 }
686
687 void clear() noexcept
688 {
689 delete[] table_;
690
691 size_ = size_type{};
692 table_ = new hash_table_bucket<value_type, size_type>[bucket_count_];
693 }
694
695 template<class Allocator>
696 void swap(hash_table& other)
697 noexcept(allocator_traits<Allocator>::is_always_equal::value &&
698 noexcept(swap(declval<Hasher&>(), declval<Hasher&>())) &&
699 noexcept(swap(declval<KeyEq&>(), declval<KeyEq&>())))
700 {
701 std::swap(table_, other.table_);
702 std::swap(bucket_count_, other.bucket_count_);
703 std::swap(size_, other.size_);
704 std::swap(hasher_, other.hasher_);
705 std::swap(key_eq_, other.key_eq_);
706 std::swap(max_load_factor_, other.max_load_factor_);
707 }
708
709 hasher hash_function() const
710 {
711 return hasher_;
712 }
713
714 key_equal key_eq() const
715 {
716 return key_eq_;
717 }
718
719 iterator find(const key_type& key)
720 {
721 // TODO: implement
722 }
723
724 const_iterator find(const key_type& key) const
725 {
726 // TODO: implement
727 }
728
729 size_type count(const key_type& key) const
730 {
731 return Policy::count(*this, key);
732 }
733
734 pair<iterator, iterator> equal_range(const key_type& key)
735 {
736 // TODO: implement
737 }
738
739 pair<const_iterator, const_iterator> equal_range(const key_type& key) const
740 {
741 // TODO: implement
742 }
743
744 size_type bucket_count() const noexcept
745 {
746 return bucket_count_;
747 }
748
749 size_type max_bucket_count() const noexcept
750 {
751 // TODO: implement
752 return 0;
753 }
754
755 size_type bucket_size(size_type n) const
756 {
757 return table_[n].size();
758 }
759
760 size_type bucket(const key_type& key) const
761 {
762 return get_bucket_idx_(key);
763 }
764
765 local_iterator begin(size_type n)
766 {
767 return local_iterator{table_[n].head, table_[n].head};
768 }
769
770 const_local_iterator begin(size_type n) const
771 {
772 return cbegin(n);
773 }
774
775 local_iterator end(size_type n)
776 {
777 return local_iterator{};
778 }
779
780 const_local_iterator end(size_type n) const
781 {
782 return cend(n);
783 }
784
785 const_local_iterator cbegin(size_type n) const
786 {
787 return const_local_iterator{table_[n].head, table_[n].head};
788 }
789
790 const_local_iterator cend(size_type n) const
791 {
792 return const_local_iterator{};
793 }
794
795 float load_factor() const noexcept
796 {
797 return size_ / static_cast<float>(bucket_count_);
798 }
799
800 float max_load_factor() const noexcept
801 {
802 return max_load_factor_;
803 }
804
805 void max_load_factor(float factor)
806 {
807 /**
808 * Note: According to the standard, this function
809 * can have no effect.
810 */
811 }
812
813 void rehash(size_type n)
814 {
815 // TODO: implement
816 // TODO: exception thrown in rehash means the rehash
817 // has no effect, so we create a new table, insert in it
818 // and then swap
819 }
820
821 void reserve(size_type n)
822 {
823 // TODO: implement
824 }
825
826 bool is_eq_to(hash_table& other)
827 {
828 // TODO: implement
829 return false;
830 }
831
832 ~hash_table()
833 {
834 // Lists are deleted in ~hash_table_bucket.
835 delete[] table_;
836 }
837
838 void set_hint(const_iterator hint)
839 {
840 // TODO: hint_ should be a ptr and we extract it here,
841 // then set it to nullptr after each operation
842 }
843
844 hint_type find_insertion_spot(const key_type& key)
845 {
846 return Policy::find_insertion_spot(*this, key);
847 }
848
849 hint_type find_insertion_spot(key_type&& key)
850 {
851 return Policy::find_insertion_spot(*this, key);
852 }
853
854 /* private: */
855 hash_table_bucket<value_type, size_type>* table_;
856 size_type bucket_count_;
857 size_type size_;
858 hasher hasher_;
859 key_equal key_eq_;
860 key_extract key_extractor_;
861 float max_load_factor_;
862
863 size_type get_bucket_idx_(const key_type& key) const
864 {
865 return hasher_(key) % bucket_count_;
866 }
867
868 bool hint_ok_(const hint_type& hint)
869 {
870 // TODO: pass this to the policy, because the multi policy
871 // will need to check if a similar key is close,
872 // that is something like:
873 // return get<1>(hint)->prev->key == key || !bucket.contains(key)
874 return get<0>(hint) != nullptr && get<2>(hint) < bucket_count_;
875 }
876
877 // Praise C++11 for this.
878 friend Policy;
879 };
880}
881
882namespace std
883{
884 template<>
885 struct allocator_traits<aux::key_value_allocator>
886 {
887 template<class Alloc, class Key, class Value, class... Args>
888 static void construct(Alloc& alloc, pair<Key, Value>* ptr, Args&&... args)
889 {
890 alloc.construct(&ptr->second, forward<Args>(args)...);
891 }
892 };
893}
894
895#endif
Note: See TracBrowser for help on using the repository browser.