#pragma once #include #include #include #include #include #include #include #include namespace uc::detail { // Flat open-addressed value -> duplicate-chain metadata. The actual duplicate // links live in AdaptiveSequence's dense ID table, so one bucket is paid per // distinct value rather than one node allocation per element. template , class Equal = std::equal_to> class FlatDuplicateIndex { public: using id_type = std::uint32_t; static constexpr id_type invalid_id = std::numeric_limits::max(); struct Entry { id_type head = invalid_id; std::uint32_t count = 0; }; FlatDuplicateIndex() { rehash(16); } [[nodiscard]] const Entry* find(const Key& key) const { const auto position = find_existing(key); return position == npos ? nullptr : &buckets_[position].entry; } [[nodiscard]] Entry* find(const Key& key) { const auto position = find_existing(key); return position == npos ? nullptr : &buckets_[position].entry; } Entry& ensure(const Key& key) { maybe_grow(); const auto [position, found] = find_insert_position(key); auto& bucket = buckets_[position]; if (!found) { if (bucket.state == State::tombstone) { --tombstones_; } bucket.key.emplace(key); bucket.entry = {}; bucket.state = State::occupied; ++size_; } return bucket.entry; } bool erase_key(const Key& key) { const auto position = find_existing(key); if (position == npos) { return false; } auto& bucket = buckets_[position]; bucket.key.reset(); bucket.entry = {}; bucket.state = State::tombstone; --size_; ++tombstones_; if (tombstones_ > buckets_.size() / 4) { rehash(buckets_.size()); } return true; } void clear() { buckets_.clear(); size_ = 0; tombstones_ = 0; rehash(16); } [[nodiscard]] std::size_t distinct_values() const noexcept { return size_; } [[nodiscard]] std::size_t bucket_count() const noexcept { return buckets_.size(); } [[nodiscard]] std::size_t allocated_bytes() const noexcept { return buckets_.capacity() * sizeof(Bucket); } private: enum class State : std::uint8_t { empty, occupied, tombstone }; struct Bucket { std::optional key; Entry entry; State state = State::empty; }; static constexpr std::size_t npos = std::numeric_limits::max(); [[nodiscard]] std::size_t mask() const noexcept { return buckets_.size() - 1; } [[nodiscard]] std::size_t find_existing(const Key& key) const { if (buckets_.empty()) { return npos; } auto position = hasher_(key) & mask(); for (std::size_t probe = 0; probe < buckets_.size(); ++probe) { const auto& bucket = buckets_[position]; if (bucket.state == State::empty) { return npos; } if (bucket.state == State::occupied && equal_(*bucket.key, key)) { return position; } position = (position + 1) & mask(); } return npos; } [[nodiscard]] std::pair find_insert_position(const Key& key) const { auto position = hasher_(key) & mask(); auto first_tombstone = npos; for (std::size_t probe = 0; probe < buckets_.size(); ++probe) { const auto& bucket = buckets_[position]; if (bucket.state == State::empty) { return {first_tombstone == npos ? position : first_tombstone, false}; } if (bucket.state == State::tombstone) { if (first_tombstone == npos) { first_tombstone = position; } } else if (equal_(*bucket.key, key)) { return {position, true}; } position = (position + 1) & mask(); } if (first_tombstone != npos) { return {first_tombstone, false}; } throw std::length_error("flat hash index is full"); } void maybe_grow() { if ((size_ + tombstones_ + 1) * 100 >= buckets_.size() * 82) { rehash(buckets_.size() * 2); } } void rehash(std::size_t requested_capacity) { std::size_t capacity = 16; while (capacity < requested_capacity) { capacity *= 2; } auto old = std::move(buckets_); buckets_.assign(capacity, Bucket{}); size_ = 0; tombstones_ = 0; for (auto& bucket : old) { if (bucket.state != State::occupied) { continue; } const auto [position, found] = find_insert_position(*bucket.key); (void)found; auto& target = buckets_[position]; target.key.emplace(std::move(*bucket.key)); target.entry = bucket.entry; target.state = State::occupied; ++size_; } } Hash hasher_{}; Equal equal_{}; std::vector buckets_; std::size_t size_ = 0; std::size_t tombstones_ = 0; }; } // namespace uc::detail