diff --git a/lab3/CMakeLists.txt b/lab3/CMakeLists.txt new file mode 100644 index 0000000..30cef60 --- /dev/null +++ b/lab3/CMakeLists.txt @@ -0,0 +1,22 @@ +cmake_minimum_required(VERSION 3.14) +project(flat_hash_map_lab) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +add_compile_options(-msse2 -Wall -Wextra -Wpedantic) + +include(FetchContent) +FetchContent_Declare( + googletest + GIT_REPOSITORY https://github.com/google/googletest.git + GIT_TAG v1.14.0 +) +FetchContent_MakeAvailable(googletest) + +enable_testing() + +add_executable(flat_hash_map_tests tests.cpp) +target_link_libraries(flat_hash_map_tests GTest::gtest_main) + +include(GoogleTest) +gtest_discover_tests(flat_hash_map_tests) diff --git a/lab3/flat_hash_map.cpp b/lab3/flat_hash_map.cpp new file mode 100644 index 0000000..013efe9 --- /dev/null +++ b/lab3/flat_hash_map.cpp @@ -0,0 +1,792 @@ +#include +#include +#include + +#include "flat_hash_map.hpp" + +template +typename flat_hash_map::value_type* +flat_hash_map::Slot::ptr() { + return reinterpret_cast(storage); +} + +template +const typename flat_hash_map::value_type* +flat_hash_map::Slot::ptr() const { + return reinterpret_cast(storage); +} + +template +typename flat_hash_map::size_type +flat_hash_map::CalculateCapacity(size_type requested) { + if (requested == 0) return 16; + + size_type capacity = 16; + while (capacity < requested) { + capacity *= 2; + } + return capacity; +} + +template +uint8_t flat_hash_map::H2(size_t hash_value) { + uint8_t h = static_cast(hash_value >> 7); + if (h >= kDeleted) { + h = kDeleted - 1; + } + return h; +} + +template +typename flat_hash_map::size_type +flat_hash_map::FindSlot(const Key& key, + size_t hash_value) const { + if (capacity_ == 0) return 0; + + const uint8_t h2_value = H2(hash_value); + size_type index = hash_value & (capacity_ - 1); + const size_type start_index = index; + + while (true) { + const size_type group_start = index & ~15; + + __m128i group = _mm_loadu_si128( + reinterpret_cast(metadata_ + group_start)); + + __m128i target = _mm_set1_epi8(h2_value); + __m128i cmp_match = _mm_cmpeq_epi8(group, target); + + __m128i empty_vec = _mm_set1_epi8(kEmpty); + __m128i cmp_empty = _mm_cmpeq_epi8(group, empty_vec); + + int match_mask = _mm_movemask_epi8(cmp_match); + int empty_mask = _mm_movemask_epi8(cmp_empty); + + while (match_mask != 0) { + int bit_pos = __builtin_ctz(match_mask); + size_type slot_index = (group_start + bit_pos) & (capacity_ - 1); + + if (metadata_[slot_index] == h2_value && + equal_(slots_[slot_index].ptr()->first, key)) { + return slot_index; + } + + match_mask &= (match_mask - 1); + } + + if (empty_mask != 0) { + return capacity_; + } + + index = (group_start + 16) & (capacity_ - 1); + + if (index == (start_index & ~15)) { + break; + } + } + + return capacity_; +} + +template +typename flat_hash_map::size_type +flat_hash_map::FindInsertSlot( + const Key& key, size_t hash_value) const { + if (capacity_ == 0) return 0; + + const uint8_t h2_value = H2(hash_value); + size_type index = hash_value & (capacity_ - 1); + size_type first_deleted = capacity_; + const size_type start_index = index; + + while (true) { + const size_type group_start = index & ~15; + + __m128i group = _mm_loadu_si128( + reinterpret_cast(metadata_ + group_start)); + + __m128i target = _mm_set1_epi8(h2_value); + __m128i cmp_match = _mm_cmpeq_epi8(group, target); + + __m128i empty_vec = _mm_set1_epi8(kEmpty); + __m128i cmp_empty = _mm_cmpeq_epi8(group, empty_vec); + + __m128i deleted_vec = _mm_set1_epi8(kDeleted); + __m128i cmp_deleted = _mm_cmpeq_epi8(group, deleted_vec); + + int match_mask = _mm_movemask_epi8(cmp_match); + int empty_mask = _mm_movemask_epi8(cmp_empty); + int deleted_mask = _mm_movemask_epi8(cmp_deleted); + + while (match_mask != 0) { + int bit_pos = __builtin_ctz(match_mask); + size_type slot_index = (group_start + bit_pos) & (capacity_ - 1); + + if (metadata_[slot_index] == h2_value && + equal_(slots_[slot_index].ptr()->first, key)) { + return slot_index; + } + + match_mask &= (match_mask - 1); + } + + if (first_deleted == capacity_ && deleted_mask != 0) { + int bit_pos = __builtin_ctz(deleted_mask); + first_deleted = (group_start + bit_pos) & (capacity_ - 1); + } + + if (empty_mask != 0) { + if (first_deleted != capacity_) { + return first_deleted; + } + int bit_pos = __builtin_ctz(empty_mask); + return (group_start + bit_pos) & (capacity_ - 1); + } + + index = (group_start + 16) & (capacity_ - 1); + + if (index == (start_index & ~15)) { + if (first_deleted != capacity_) { + return first_deleted; + } + break; + } + } + + return capacity_; +} + +template +void flat_hash_map::Allocate(size_type new_capacity) { + slots_ = static_cast(::operator new(new_capacity * sizeof(Slot))); + metadata_ = new uint8_t[new_capacity]; + std::fill(metadata_, metadata_ + new_capacity, kEmpty); + capacity_ = new_capacity; +} + +template +void flat_hash_map::Deallocate() { + if (slots_) { + for (size_type i = 0; i < capacity_; ++i) { + if (metadata_[i] < kDeleted) { + slots_[i].ptr()->~value_type(); + } + } + ::operator delete(slots_); + delete[] metadata_; + } +} + +template +void flat_hash_map::RehashImpl( + size_type new_capacity) { + Slot* old_slots = slots_; + uint8_t* old_metadata = metadata_; + size_type old_capacity = capacity_; + + Allocate(new_capacity); + size_ = 0; + + for (size_type i = 0; i < old_capacity; ++i) { + if (old_metadata[i] < kDeleted) { + value_type* old_val = old_slots[i].ptr(); + Key key_copy = std::move(const_cast(old_val->first)); + mapped_type val_copy = std::move(old_val->second); + old_val->~value_type(); + InsertImpl(std::move(key_copy), std::move(val_copy)); + } + } + + ::operator delete(old_slots); + delete[] old_metadata; +} + +template +std::pair::iterator, bool> +flat_hash_map::InsertImpl(const Key& key, + const mapped_type& value) { + if (capacity_ == 0) { + Allocate(CalculateCapacity(16)); + } + + if (static_cast(size_ + 1) > capacity_ * max_load_factor_) { + RehashImpl(capacity_ * 2); + } + + size_t hash_value = hash_(key); + size_type slot = FindInsertSlot(key, hash_value); + + if (slot < capacity_ && metadata_[slot] < kDeleted) { + return {iterator(this, slot), false}; + } + + if (slot == capacity_) { + RehashImpl(capacity_ * 2); + hash_value = hash_(key); + slot = FindInsertSlot(key, hash_value); + } + + new (slots_[slot].ptr()) value_type(key, value); + metadata_[slot] = H2(hash_value); + ++size_; + + return {iterator(this, slot), true}; +} + +template +std::pair::iterator, bool> +flat_hash_map::InsertImpl(Key&& key, + mapped_type&& value) { + if (capacity_ == 0) { + Allocate(CalculateCapacity(16)); + } + + if (static_cast(size_ + 1) > capacity_ * max_load_factor_) { + RehashImpl(capacity_ * 2); + } + + size_t hash_value = hash_(key); + size_type slot = FindInsertSlot(key, hash_value); + + if (slot < capacity_ && metadata_[slot] < kDeleted) { + return {iterator(this, slot), false}; + } + + if (slot == capacity_) { + RehashImpl(capacity_ * 2); + hash_value = hash_(key); + slot = FindInsertSlot(key, hash_value); + } + + new (slots_[slot].ptr()) value_type(std::move(key), std::move(value)); + metadata_[slot] = H2(hash_value); + ++size_; + + return {iterator(this, slot), true}; +} + +template +flat_hash_map::iterator::iterator(flat_hash_map* map, + size_type index) + : map_(map), index_(index) {} + +template +flat_hash_map::iterator::iterator() + : map_(nullptr), index_(0) {} + +template +typename flat_hash_map::iterator::reference +flat_hash_map::iterator::operator*() const { + return *map_->slots_[index_].ptr(); +} + +template +typename flat_hash_map::iterator::pointer +flat_hash_map::iterator::operator->() const { + return map_->slots_[index_].ptr(); +} + +template +typename flat_hash_map::iterator& +flat_hash_map::iterator::operator++() { + ++index_; + while (index_ < map_->capacity_ && + map_->metadata_[index_] >= flat_hash_map::kDeleted) { + ++index_; + } + return *this; +} + +template +typename flat_hash_map::iterator +flat_hash_map::iterator::operator++(int) { + iterator tmp = *this; + ++(*this); + return tmp; +} + +template +bool flat_hash_map::iterator::operator==( + const iterator& other) const { + return map_ == other.map_ && index_ == other.index_; +} + +template +bool flat_hash_map::iterator::operator!=( + const iterator& other) const { + return !(*this == other); +} + +template +flat_hash_map::const_iterator::const_iterator( + const flat_hash_map* map, size_type index) + : map_(map), index_(index) {} + +template +flat_hash_map::const_iterator::const_iterator() + : map_(nullptr), index_(0) {} + +template +flat_hash_map::const_iterator::const_iterator( + const iterator& it) + : map_(it.map_), index_(it.index_) {} + +template +typename flat_hash_map::const_iterator::reference +flat_hash_map::const_iterator::operator*() const { + return *map_->slots_[index_].ptr(); +} + +template +typename flat_hash_map::const_iterator::pointer +flat_hash_map::const_iterator::operator->() const { + return map_->slots_[index_].ptr(); +} + +template +typename flat_hash_map::const_iterator& +flat_hash_map::const_iterator::operator++() { + ++index_; + while (index_ < map_->capacity_ && + map_->metadata_[index_] >= flat_hash_map::kDeleted) { + ++index_; + } + return *this; +} + +template +typename flat_hash_map::const_iterator +flat_hash_map::const_iterator::operator++(int) { + const_iterator tmp = *this; + ++(*this); + return tmp; +} + +template +bool flat_hash_map::const_iterator::operator==( + const const_iterator& other) const { + return map_ == other.map_ && index_ == other.index_; +} + +template +bool flat_hash_map::const_iterator::operator!=( + const const_iterator& other) const { + return !(*this == other); +} + +template +flat_hash_map::flat_hash_map() + : slots_(nullptr), + metadata_(nullptr), + capacity_(0), + size_(0), + max_load_factor_(0.75f), + hash_(), + equal_() {} + +template +flat_hash_map::flat_hash_map(size_type bucket_count, + const Hash& hash, + const KeyEqual& equal) + : slots_(nullptr), + metadata_(nullptr), + capacity_(0), + size_(0), + max_load_factor_(0.75f), + hash_(hash), + equal_(equal) { + if (bucket_count > 0) { + Allocate(CalculateCapacity(bucket_count)); + } +} + +template +template +flat_hash_map::flat_hash_map(InputIt first, + InputIt last, + size_type bucket_count, + const Hash& hash, + const KeyEqual& equal) + : flat_hash_map(bucket_count, hash, equal) { + insert(first, last); +} + +template +flat_hash_map::flat_hash_map( + std::initializer_list init, size_type bucket_count, + const Hash& hash, const KeyEqual& equal) + : flat_hash_map(bucket_count, hash, equal) { + insert(init); +} + +template +flat_hash_map::flat_hash_map( + const flat_hash_map& other) + : slots_(nullptr), + metadata_(nullptr), + capacity_(0), + size_(0), + max_load_factor_(other.max_load_factor_), + hash_(other.hash_), + equal_(other.equal_) { + if (other.capacity_ > 0) { + Allocate(other.capacity_); + for (size_type i = 0; i < other.capacity_; ++i) { + metadata_[i] = other.metadata_[i]; + if (metadata_[i] < kDeleted) { + new (slots_[i].ptr()) value_type(*other.slots_[i].ptr()); + ++size_; + } + } + } +} + +template +flat_hash_map::flat_hash_map( + flat_hash_map&& other) noexcept + : slots_(other.slots_), + metadata_(other.metadata_), + capacity_(other.capacity_), + size_(other.size_), + max_load_factor_(other.max_load_factor_), + hash_(std::move(other.hash_)), + equal_(std::move(other.equal_)) { + other.slots_ = nullptr; + other.metadata_ = nullptr; + other.capacity_ = 0; + other.size_ = 0; +} + +template +flat_hash_map::~flat_hash_map() { + Deallocate(); +} + +template +flat_hash_map& +flat_hash_map::operator=(const flat_hash_map& other) { + if (this != &other) { + flat_hash_map tmp(other); + swap(tmp); + } + return *this; +} + +template +flat_hash_map& +flat_hash_map::operator=( + flat_hash_map&& other) noexcept { + if (this != &other) { + Deallocate(); + slots_ = other.slots_; + metadata_ = other.metadata_; + capacity_ = other.capacity_; + size_ = other.size_; + max_load_factor_ = other.max_load_factor_; + hash_ = std::move(other.hash_); + equal_ = std::move(other.equal_); + + other.slots_ = nullptr; + other.metadata_ = nullptr; + other.capacity_ = 0; + other.size_ = 0; + } + return *this; +} + +template +flat_hash_map& +flat_hash_map::operator=( + std::initializer_list init) { + clear(); + insert(init); + return *this; +} + +template +void flat_hash_map::swap( + flat_hash_map& other) noexcept { + std::swap(slots_, other.slots_); + std::swap(metadata_, other.metadata_); + std::swap(capacity_, other.capacity_); + std::swap(size_, other.size_); + std::swap(max_load_factor_, other.max_load_factor_); + std::swap(hash_, other.hash_); + std::swap(equal_, other.equal_); +} + +template +typename flat_hash_map::iterator +flat_hash_map::begin() noexcept { + size_type i = 0; + while (i < capacity_ && metadata_[i] >= kDeleted) { + ++i; + } + return iterator(this, i); +} + +template +typename flat_hash_map::const_iterator +flat_hash_map::begin() const noexcept { + size_type i = 0; + while (i < capacity_ && metadata_[i] >= kDeleted) { + ++i; + } + return const_iterator(this, i); +} + +template +typename flat_hash_map::const_iterator +flat_hash_map::cbegin() const noexcept { + return begin(); +} + +template +typename flat_hash_map::iterator +flat_hash_map::end() noexcept { + return iterator(this, capacity_); +} + +template +typename flat_hash_map::const_iterator +flat_hash_map::end() const noexcept { + return const_iterator(this, capacity_); +} + +template +typename flat_hash_map::const_iterator +flat_hash_map::cend() const noexcept { + return end(); +} + +template +bool flat_hash_map::empty() const noexcept { + return size_ == 0; +} + +template +typename flat_hash_map::size_type +flat_hash_map::size() const noexcept { + return size_; +} + +template +void flat_hash_map::clear() noexcept { + for (size_type i = 0; i < capacity_; ++i) { + if (metadata_[i] < kDeleted) { + slots_[i].ptr()->~value_type(); + metadata_[i] = kEmpty; + } + } + size_ = 0; +} + +template +std::pair::iterator, bool> +flat_hash_map::insert(const value_type& value) { + return InsertImpl(value.first, value.second); +} + +template +std::pair::iterator, bool> +flat_hash_map::insert(value_type&& value) { + Key key_copy = std::move(const_cast(value.first)); + return InsertImpl(std::move(key_copy), std::move(value.second)); +} + +template +template +void flat_hash_map::insert(InputIt first, + InputIt last) { + for (auto it = first; it != last; ++it) { + insert(*it); + } +} + +template +void flat_hash_map::insert( + std::initializer_list init) { + insert(init.begin(), init.end()); +} + +template +template +std::pair::iterator, bool> +flat_hash_map::emplace(Args&&... args) { + value_type temp(std::forward(args)...); + return insert(std::move(temp)); +} + +template +typename flat_hash_map::size_type +flat_hash_map::erase(const key_type& key) { + if (capacity_ == 0) return 0; + + size_t hash_value = hash_(key); + size_type slot = FindSlot(key, hash_value); + + if (slot == capacity_) { + return 0; + } + + slots_[slot].ptr()->~value_type(); + metadata_[slot] = kDeleted; + --size_; + return 1; +} + +template +typename flat_hash_map::iterator +flat_hash_map::erase(iterator pos) { + if (pos.index_ < capacity_ && metadata_[pos.index_] < kDeleted) { + slots_[pos.index_].ptr()->~value_type(); + metadata_[pos.index_] = kDeleted; + --size_; + } + ++pos; + return pos; +} + +template +void flat_hash_map::reserve(size_type new_capacity) { + if (new_capacity > capacity_) { + RehashImpl(CalculateCapacity(new_capacity)); + } +} + +template +typename flat_hash_map::size_type +flat_hash_map::capacity() const noexcept { + return capacity_; +} + +template +float flat_hash_map::load_factor() const noexcept { + return capacity_ == 0 ? 0.0f : static_cast(size_) / capacity_; +} + +template +void flat_hash_map::max_load_factor(float ml) { + max_load_factor_ = ml; +} + +template +float flat_hash_map::max_load_factor() const noexcept { + return max_load_factor_; +} + +template +typename flat_hash_map::mapped_type& +flat_hash_map::operator[](const key_type& key) { + if (capacity_ == 0) { + Allocate(CalculateCapacity(16)); + } + + size_t hash_value = hash_(key); + size_type slot = FindInsertSlot(key, hash_value); + + if (slot < capacity_ && metadata_[slot] < kDeleted) { + return slots_[slot].ptr()->second; + } + + auto result = InsertImpl(key, mapped_type{}); + return result.first->second; +} + +template +typename flat_hash_map::mapped_type& +flat_hash_map::operator[](key_type&& key) { + if (capacity_ == 0) { + Allocate(CalculateCapacity(16)); + } + + size_t hash_value = hash_(key); + size_type slot = FindInsertSlot(key, hash_value); + + if (slot < capacity_ && metadata_[slot] < kDeleted) { + return slots_[slot].ptr()->second; + } + + auto result = InsertImpl(std::move(key), mapped_type{}); + return result.first->second; +} + +template +typename flat_hash_map::mapped_type& +flat_hash_map::at(const key_type& key) { + if (capacity_ == 0) { + throw std::out_of_range("key not found"); + } + + size_t hash_value = hash_(key); + size_type slot = FindSlot(key, hash_value); + + if (slot == capacity_) { + throw std::out_of_range("key not found"); + } + + return slots_[slot].ptr()->second; +} + +template +const typename flat_hash_map::mapped_type& +flat_hash_map::at(const key_type& key) const { + if (capacity_ == 0) { + throw std::out_of_range("key not found"); + } + + size_t hash_value = hash_(key); + size_type slot = FindSlot(key, hash_value); + + if (slot == capacity_) { + throw std::out_of_range("key not found"); + } + + return slots_[slot].ptr()->second; +} + +template +typename flat_hash_map::iterator +flat_hash_map::find(const key_type& key) { + if (capacity_ == 0) { + return end(); + } + + size_t hash_value = hash_(key); + size_type slot = FindSlot(key, hash_value); + + if (slot == capacity_) { + return end(); + } + + return iterator(this, slot); +} + +template +typename flat_hash_map::const_iterator +flat_hash_map::find(const key_type& key) const { + if (capacity_ == 0) { + return end(); + } + + size_t hash_value = hash_(key); + size_type slot = FindSlot(key, hash_value); + + if (slot == capacity_) { + return end(); + } + + return const_iterator(this, slot); +} + +template +bool flat_hash_map::contains( + const key_type& key) const { + return find(key) != end(); +} + +template +typename flat_hash_map::size_type +flat_hash_map::count(const key_type& key) const { + return contains(key) ? 1 : 0; +} diff --git a/lab3/flat_hash_map.hpp b/lab3/flat_hash_map.hpp new file mode 100644 index 0000000..bbf440e --- /dev/null +++ b/lab3/flat_hash_map.hpp @@ -0,0 +1,185 @@ +#ifndef FLAT_HASH_MAP_HPP_ +#define FLAT_HASH_MAP_HPP_ + +#include +#include +#include +#include +#include + +template , + class KeyEqual = std::equal_to> +class flat_hash_map { + public: + using key_type = Key; + using mapped_type = T; + using value_type = std::pair; + using size_type = std::size_t; + using hasher = Hash; + using key_equal = KeyEqual; + + class iterator; + class const_iterator; + + flat_hash_map(); + + explicit flat_hash_map(size_type bucket_count, const Hash& hash = Hash(), + const KeyEqual& equal = KeyEqual()); + + template + flat_hash_map(InputIt first, InputIt last, size_type bucket_count = 0, + const Hash& hash = Hash(), const KeyEqual& equal = KeyEqual()); + + flat_hash_map(std::initializer_list init, + size_type bucket_count = 0, const Hash& hash = Hash(), + const KeyEqual& equal = KeyEqual()); + + flat_hash_map(const flat_hash_map& other); + flat_hash_map(flat_hash_map&& other) noexcept; + + ~flat_hash_map(); + + flat_hash_map& operator=(const flat_hash_map& other); + flat_hash_map& operator=(flat_hash_map&& other) noexcept; + flat_hash_map& operator=(std::initializer_list init); + + void swap(flat_hash_map& other) noexcept; + + iterator begin() noexcept; + const_iterator begin() const noexcept; + const_iterator cbegin() const noexcept; + + iterator end() noexcept; + const_iterator end() const noexcept; + const_iterator cend() const noexcept; + + bool empty() const noexcept; + size_type size() const noexcept; + void clear() noexcept; + + std::pair insert(const value_type& value); + std::pair insert(value_type&& value); + + template + void insert(InputIt first, InputIt last); + + void insert(std::initializer_list init); + + template + std::pair emplace(Args&&... args); + + size_type erase(const key_type& key); + iterator erase(iterator pos); + + void reserve(size_type new_capacity); + size_type capacity() const noexcept; + float load_factor() const noexcept; + void max_load_factor(float ml); + float max_load_factor() const noexcept; + + mapped_type& operator[](const key_type& key); + mapped_type& operator[](key_type&& key); + + mapped_type& at(const key_type& key); + const mapped_type& at(const key_type& key) const; + + iterator find(const key_type& key); + const_iterator find(const key_type& key) const; + + bool contains(const key_type& key) const; + size_type count(const key_type& key) const; + + private: + static constexpr uint8_t kEmpty = 0xFF; + static constexpr uint8_t kDeleted = 0xFE; + + struct Slot { + alignas(value_type) uint8_t storage[sizeof(value_type)]; + value_type* ptr(); + const value_type* ptr() const; + }; + + Slot* slots_; + uint8_t* metadata_; + size_type capacity_; + size_type size_; + float max_load_factor_; + Hash hash_; + KeyEqual equal_; + + static size_type CalculateCapacity(size_type requested); + static uint8_t H2(size_t hash_value); + + size_type FindSlot(const Key& key, size_t hash_value) const; + size_type FindInsertSlot(const Key& key, size_t hash_value) const; + + void Allocate(size_type new_capacity); + void Deallocate(); + void RehashImpl(size_type new_capacity); + + std::pair InsertImpl(const Key& key, + const mapped_type& value); + std::pair InsertImpl(Key&& key, mapped_type&& value); +}; + +template +class flat_hash_map::iterator { + friend class flat_hash_map; + friend class const_iterator; + + flat_hash_map* map_; + size_type index_; + + iterator(flat_hash_map* map, size_type index); + + public: + using difference_type = std::ptrdiff_t; + using value_type = flat_hash_map::value_type; + using pointer = value_type*; + using reference = value_type&; + using iterator_category = std::forward_iterator_tag; + + iterator(); + + reference operator*() const; + pointer operator->() const; + + iterator& operator++(); + iterator operator++(int); + + bool operator==(const iterator& other) const; + bool operator!=(const iterator& other) const; +}; + +template +class flat_hash_map::const_iterator { + friend class flat_hash_map; + + const flat_hash_map* map_; + size_type index_; + + const_iterator(const flat_hash_map* map, size_type index); + + public: + using difference_type = std::ptrdiff_t; + using value_type = flat_hash_map::value_type; + using pointer = const value_type*; + using reference = const value_type&; + using iterator_category = std::forward_iterator_tag; + + const_iterator(); + const_iterator(const iterator& it); + + reference operator*() const; + pointer operator->() const; + + const_iterator& operator++(); + const_iterator operator++(int); + + bool operator==(const const_iterator& other) const; + bool operator!=(const const_iterator& other) const; +}; + +#include "flat_hash_map.cpp" + +#endif // FLAT_HASH_MAP_HPP_ diff --git a/lab3/tests.cpp b/lab3/tests.cpp new file mode 100644 index 0000000..0c58443 --- /dev/null +++ b/lab3/tests.cpp @@ -0,0 +1,257 @@ +#include "flat_hash_map.hpp" +#include +#include +#include + +class FlatHashMapTest : public ::testing::Test { +protected: + flat_hash_map map; + flat_hash_map str_map; +}; + +TEST_F(FlatHashMapTest, DefaultConstructor) { + flat_hash_map m; + EXPECT_TRUE(m.empty()); + EXPECT_EQ(m.size(), 0); +} + +TEST_F(FlatHashMapTest, BucketCountConstructor) { + flat_hash_map m(100); + EXPECT_TRUE(m.empty()); + EXPECT_GE(m.capacity(), 100); +} + +TEST_F(FlatHashMapTest, InitializerListConstructor) { + flat_hash_map m = {{1, "one"}, {2, "two"}, {3, "three"}}; + EXPECT_EQ(m.size(), 3); + EXPECT_EQ(m[1], "one"); +} + +TEST_F(FlatHashMapTest, CopyConstructor) { + map[1] = "one"; + map[2] = "two"; + flat_hash_map copy(map); + EXPECT_EQ(copy.size(), 2); + EXPECT_EQ(copy[1], "one"); +} + +TEST_F(FlatHashMapTest, MoveConstructor) { + map[1] = "one"; + flat_hash_map moved(std::move(map)); + EXPECT_EQ(moved[1], "one"); + EXPECT_EQ(map.size(), 0); +} + +TEST_F(FlatHashMapTest, CopyAssignment) { + map[1] = "one"; + flat_hash_map copy; + copy = map; + EXPECT_EQ(copy[1], "one"); +} + +TEST_F(FlatHashMapTest, MoveAssignment) { + map[1] = "one"; + flat_hash_map moved; + moved = std::move(map); + EXPECT_EQ(moved[1], "one"); +} + +TEST_F(FlatHashMapTest, Swap) { + map[1] = "one"; + flat_hash_map other; + other[2] = "two"; + map.swap(other); + EXPECT_EQ(map[2], "two"); + EXPECT_EQ(other[1], "one"); +} + +TEST_F(FlatHashMapTest, Iterator) { + map = {{1, "one"}, {2, "two"}, {3, "three"}}; + int count = 0; + for (auto it = map.begin(); it != map.end(); ++it) { + count++; + } + EXPECT_EQ(count, 3); +} + +TEST_F(FlatHashMapTest, RangeBasedFor) { + map = {{1, "one"}, {2, "two"}}; + int count = 0; + for (const auto& pair : map) { + count++; + } + EXPECT_EQ(count, 2); +} + +TEST_F(FlatHashMapTest, Empty) { + EXPECT_TRUE(map.empty()); + map[1] = "one"; + EXPECT_FALSE(map.empty()); +} + +TEST_F(FlatHashMapTest, Size) { + EXPECT_EQ(map.size(), 0); + map[1] = "one"; + EXPECT_EQ(map.size(), 1); +} + +TEST_F(FlatHashMapTest, Clear) { + map = {{1, "one"}, {2, "two"}}; + map.clear(); + EXPECT_TRUE(map.empty()); +} + +TEST_F(FlatHashMapTest, InsertLvalue) { + std::pair value(1, "one"); + auto result = map.insert(value); + EXPECT_TRUE(result.second); + EXPECT_EQ(map.size(), 1); +} + +TEST_F(FlatHashMapTest, InsertDuplicate) { + map.insert({1, "one"}); + auto result = map.insert({1, "uno"}); + EXPECT_FALSE(result.second); + EXPECT_EQ(map[1], "one"); +} + +TEST_F(FlatHashMapTest, Emplace) { + auto result = map.emplace(1, "one"); + EXPECT_TRUE(result.second); + EXPECT_EQ(map[1], "one"); +} + +TEST_F(FlatHashMapTest, EraseByKey) { + map = {{1, "one"}, {2, "two"}}; + size_t erased = map.erase(2); + EXPECT_EQ(erased, 1); + EXPECT_EQ(map.size(), 1); +} + +TEST_F(FlatHashMapTest, EraseByIterator) { + map = {{1, "one"}, {2, "two"}}; + auto it = map.find(2); + map.erase(it); + EXPECT_FALSE(map.contains(2)); +} + +TEST_F(FlatHashMapTest, Reserve) { + map.reserve(100); + EXPECT_GE(map.capacity(), 100); +} + +TEST_F(FlatHashMapTest, LoadFactor) { + map.reserve(100); + for (int i = 0; i < 50; ++i) { + map[i] = std::to_string(i); + } + EXPECT_GT(map.load_factor(), 0.0f); +} + +TEST_F(FlatHashMapTest, SubscriptOperator) { + map[1] = "one"; + EXPECT_EQ(map[1], "one"); +} + +TEST_F(FlatHashMapTest, AtValid) { + map[1] = "one"; + EXPECT_EQ(map.at(1), "one"); +} + +TEST_F(FlatHashMapTest, AtInvalid) { + EXPECT_THROW(map.at(99), std::out_of_range); +} + +TEST_F(FlatHashMapTest, Find) { + map[1] = "one"; + auto it = map.find(1); + ASSERT_NE(it, map.end()); + EXPECT_EQ(it->second, "one"); +} + +TEST_F(FlatHashMapTest, Contains) { + map[1] = "one"; + EXPECT_TRUE(map.contains(1)); + EXPECT_FALSE(map.contains(99)); +} + +TEST_F(FlatHashMapTest, Count) { + map[1] = "one"; + EXPECT_EQ(map.count(1), 1); + EXPECT_EQ(map.count(99), 0); +} + +TEST_F(FlatHashMapTest, LargeInsertion) { + for (int i = 0; i < 1000; ++i) { + map[i] = std::to_string(i); + } + EXPECT_EQ(map.size(), 1000); +} + +TEST_F(FlatHashMapTest, RangeConstructor) { + std::vector> vec = {{1, "one"}, {2, "two"}, {3, "three"}}; + flat_hash_map m(vec.begin(), vec.end()); + EXPECT_EQ(m.size(), 3); + EXPECT_EQ(m[2], "two"); +} + +TEST_F(FlatHashMapTest, RangeInsert) { + std::vector> vec = {{1, "one"}, {2, "two"}}; + map.insert(vec.begin(), vec.end()); + EXPECT_EQ(map.size(), 2); + EXPECT_EQ(map[1], "one"); +} + +TEST_F(FlatHashMapTest, SubscriptOperatorRvalue) { + int key = 42; + map[std::move(key)] = "value"; + EXPECT_EQ(map[42], "value"); +} + +TEST_F(FlatHashMapTest, AtConstVersion) { + map[1] = "one"; + const auto& const_map = map; + EXPECT_EQ(const_map.at(1), "one"); +} + +TEST_F(FlatHashMapTest, FindConstVersion) { + map[1] = "one"; + const auto& const_map = map; + auto it = const_map.find(1); + EXPECT_NE(it, const_map.end()); + EXPECT_EQ(it->second, "one"); +} + +TEST_F(FlatHashMapTest, ConstIterators) { + map = {{1, "one"}, {2, "two"}}; + const auto& const_map = map; + int count = 0; + for (auto it = const_map.cbegin(); it != const_map.cend(); ++it) { + count++; + } + EXPECT_EQ(count, 2); +} + +TEST_F(FlatHashMapTest, AssignmentOperatorInitList) { + map = {{1, "one"}, {2, "two"}, {3, "three"}}; + EXPECT_EQ(map.size(), 3); + EXPECT_EQ(map[2], "two"); +} + +TEST_F(FlatHashMapTest, EraseIteratorReturnValue) { + map = {{1, "one"}, {2, "two"}, {3, "three"}}; + auto it = map.find(1); + auto next_it = map.erase(it); + EXPECT_NE(next_it, map.end()); +} + +TEST_F(FlatHashMapTest, MaxLoadFactor) { + EXPECT_EQ(map.max_load_factor(), 0.75f); + map.max_load_factor(0.5f); + EXPECT_EQ(map.max_load_factor(), 0.5f); +} + +int main(int argc, char **argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +}