#pragma once #include "iterator.h" #include "concepts/container.h" #include "concepts/size_fitter.h" #include #include namespace NFlatHash { namespace NPrivate { template struct TTypeIdentity { using type = T; }; } // namespace NPrivate template < class Hash, class KeyEqual, class Container, class KeyGetter, class Probing, class SizeFitter, class Expander, // Used in the TSet to make iterator behave as const_iterator template class IteratorModifier = NPrivate::TTypeIdentity> class TTable { private: static_assert(NConcepts::ContainerV); static_assert(NConcepts::SizeFitterV); template class TIteratorImpl : public TIterator { private: using TBase = TIterator; friend class TTable; using TBase::TBase; public: TIteratorImpl() : TBase(nullptr, 0) {} }; public: using value_type = typename Container::value_type; using size_type = typename Container::size_type; using difference_type = typename Container::difference_type; using hasher = Hash; using key_equal = KeyEqual; using reference = value_type&; using const_reference = const value_type&; using iterator = TIteratorImpl::type, typename IteratorModifier::type>; using const_iterator = TIteratorImpl; using allocator_type = typename Container::allocator_type; using pointer = typename Container::pointer; using const_pointer = typename Container::const_pointer; private: TTable(Container buckets) : Buckets_(std::move(buckets)) { SizeFitter_.Update(bucket_count()); } static constexpr size_type INIT_SIZE = 8; public: template TTable(size_type initSize, Rest&&... rest) : Buckets_(initSize == 0 ? INIT_SIZE : SizeFitter_.EvalSize(initSize), std::forward(rest)...) { SizeFitter_.Update(bucket_count()); } TTable(const TTable&) = default; TTable(TTable&& rhs) : SizeFitter_(std::move(rhs.SizeFitter_)) , Buckets_(std::move(rhs.Buckets_)) , Hasher_(std::move(rhs.Hasher_)) , KeyEqual_(std::move(rhs.KeyEqual_)) { TTable tmp{ Buckets_.Clone(INIT_SIZE) }; tmp.swap(rhs); } TTable& operator=(const TTable&) = default; TTable& operator=(TTable&& rhs) { TTable tmp(std::move(rhs)); swap(tmp); return *this; } // Iterators iterator begin() { return &Buckets_; } const_iterator begin() const { return const_cast(this)->begin(); } const_iterator cbegin() const { return begin(); } iterator end() { return { &Buckets_, bucket_count() }; } const_iterator end() const { return const_cast(this)->end(); } const_iterator cend() const { return end(); } // Capacity bool empty() const noexcept { return size() == 0; } size_type size() const noexcept { return Buckets_.Taken(); } // Modifiers void clear() { Container tmp(Buckets_.Clone(bucket_count())); Buckets_.Swap(tmp); } std::pair insert(const value_type& value) { return InsertImpl(value); } std::pair insert(value_type&& value) { return InsertImpl(std::move(value)); } template std::enable_if_t, value_type>, std::pair> insert(T&& value) { return insert(value_type(std::forward(value))); } iterator insert(const_iterator, const value_type& value) { // TODO(tender-bum) return insert(value).first; } iterator insert(const_iterator, value_type&& value) { // TODO(tender-bum) return insert(std::move(value)).first; } template iterator insert(const_iterator, T&& value) { // TODO(tender-bum) return insert(value_type(std::forward(value))).first; } template void insert(InputIt first, InputIt last) { while (first != last) { insert(*first++); } } void insert(std::initializer_list il) { insert(il.begin(), il.end()); } template std::pair emplace(Args&&... args) { return insert(value_type(std::forward(args)...)); } template iterator emplace_hint(const_iterator, Args&&... args) { // TODO(tender-bum) return emplace(std::forward(args)...).first; } void erase(const_iterator pos) { static_assert(NConcepts::RemovalContainerV, "That kind of table doesn't allow erasing. Use another table instead."); if constexpr (NConcepts::RemovalContainerV) { Buckets_.DeleteNode(pos.Idx_); } } void erase(const_iterator f, const_iterator l) { while (f != l) { auto nxt = f; ++nxt; erase(f); f = nxt; } } template std::enable_if_t && !std::is_convertible_v, size_type> erase(const K& key) { auto it = find(key); if (it != end()) { erase(it); return 1; } return 0; } void swap(TTable& rhs) noexcept(noexcept(std::declval().Swap(std::declval()))) { DoSwap(SizeFitter_, rhs.SizeFitter_); Buckets_.Swap(rhs.Buckets_); DoSwap(Hasher_, rhs.Hasher_); DoSwap(KeyEqual_, rhs.KeyEqual_); } // Lookup template size_type count(const K& key) const { return contains(key); } template iterator find(const K& key) { size_type hs = hash_function()(key); auto idx = FindProperBucket(hs, key); if (Buckets_.IsTaken(idx)) { return { &Buckets_, idx }; } return end(); } template const_iterator find(const K& key) const { return const_cast(this)->find(key); } template bool contains(const K& key) const { size_type hs = hash_function()(key); return Buckets_.IsTaken(FindProperBucket(hs, key)); } // Bucket interface size_type bucket_count() const noexcept { return Buckets_.Size(); } size_type bucket_size(size_type idx) const { return Buckets_.IsTaken(idx); } // Hash policy float load_factor() const noexcept { return (float)(bucket_count() - Buckets_.Empty()) / bucket_count(); } void rehash(size_type sz) { if (sz != 0) { auto newBuckets = SizeFitter_.EvalSize(sz); size_type occupied = bucket_count() - Buckets_.Empty(); if (Expander::NeedGrow(occupied, newBuckets)) { newBuckets = Max(newBuckets, SizeFitter_.EvalSize(Expander::SuitableSize(size()))); } RehashImpl(newBuckets); } else { RehashImpl(SizeFitter_.EvalSize(Expander::SuitableSize(size()))); } } void reserve(size_type sz) { rehash(sz); } // TODO(tender-bum) // Observers constexpr auto hash_function() const noexcept { return Hasher_; } constexpr auto key_eq() const noexcept { return KeyEqual_; } public: template std::pair InsertImpl(T&& value) { return TryCreate(KeyGetter::Apply(value), [&](size_type idx) { Buckets_.InitNode(idx, std::forward(value)); }); } template Y_FORCE_INLINE std::pair TryCreate(const T& key, F nodeInit) { size_type hs = hash_function()(key); size_type idx = FindProperBucket(hs, key); if (!Buckets_.IsTaken(idx)) { if (Expander::WillNeedGrow(bucket_count() - Buckets_.Empty(), bucket_count())) { RehashImpl(); idx = FindProperBucket(hs, key); } nodeInit(idx); return { iterator{ &Buckets_, idx }, true }; } return { iterator{ &Buckets_, idx }, false }; } template size_type FindProperBucket(size_type hs, const K& key) const { return Probing::FindBucket(SizeFitter_, hs, bucket_count(), [&](size_type idx) { if constexpr (NConcepts::RemovalContainerV) { return Buckets_.IsEmpty(idx) || Buckets_.IsTaken(idx) && key_eq()(KeyGetter::Apply(Buckets_.Node(idx)), key); } else { return Buckets_.IsEmpty(idx) || key_eq()(KeyGetter::Apply(Buckets_.Node(idx)), key); } }); } void RehashImpl() { if constexpr (NConcepts::RemovalContainerV) { size_type occupied = bucket_count() - Buckets_.Empty(); if (size() < occupied / 2) { rehash(bucket_count()); // Just clearing all deleted elements } else { RehashImpl(SizeFitter_.EvalSize(Expander::EvalNewSize(bucket_count()))); } } else { RehashImpl(SizeFitter_.EvalSize(Expander::EvalNewSize(bucket_count()))); } } void RehashImpl(size_type newSize) { TTable tmp = Buckets_.Clone(newSize); for (auto& value : *this) { size_type hs = hash_function()(KeyGetter::Apply(value)); tmp.Buckets_.InitNode( tmp.FindProperBucket(hs, KeyGetter::Apply(value)), std::move_if_noexcept(value)); } swap(tmp); } public: SizeFitter SizeFitter_; Container Buckets_; hasher Hasher_; key_equal KeyEqual_; }; } // namespace NFlatHash