From b40c19ddc7622b0f8754be75c64b6b6f85c1819f Mon Sep 17 00:00:00 2001 From: szdytom Date: Mon, 4 Aug 2025 14:46:34 +0800 Subject: [PATCH] feat: add SmallMap implementation and associated tests Signed-off-by: szdytom --- CMakeLists.txt | 9 +- util/CMakeLists.txt | 10 ++ util/include/istd_util/small_map.h | 160 +++++++++++++++++++++++++++++ util/include/util.h | 1 + util/test/CMakeLists.txt | 9 ++ util/test/small_map.cpp | 77 ++++++++++++++ 6 files changed, 265 insertions(+), 1 deletion(-) create mode 100644 util/CMakeLists.txt create mode 100644 util/include/istd_util/small_map.h create mode 100644 util/include/util.h create mode 100644 util/test/CMakeLists.txt create mode 100644 util/test/small_map.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 352e30c..c4981cb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,11 +1,18 @@ cmake_minimum_required(VERSION 3.27) project(instructed LANGUAGES CXX) -# Optionally build examples +include(CTest) +enable_testing() + +# Optionally build examples & Tests option(BUILD_EXAMPLES "Build example programs" ON) +option(BUILD_TESTS "Build tests" ON) # Load third-party libraries add_subdirectory(third_party) +# Add util library +add_subdirectory(util) + # Add tilemap library and examples add_subdirectory(tilemap) diff --git a/util/CMakeLists.txt b/util/CMakeLists.txt new file mode 100644 index 0000000..fa5532e --- /dev/null +++ b/util/CMakeLists.txt @@ -0,0 +1,10 @@ +cmake_minimum_required(VERSION 3.27) + +# Define util library sources (header-only for now) +add_library(istd_util INTERFACE) +target_include_directories(istd_util INTERFACE include) +target_compile_features(istd_util INTERFACE cxx_std_23) + +if (BUILD_TESTS) + add_subdirectory("test/") +endif() diff --git a/util/include/istd_util/small_map.h b/util/include/istd_util/small_map.h new file mode 100644 index 0000000..5726da7 --- /dev/null +++ b/util/include/istd_util/small_map.h @@ -0,0 +1,160 @@ + +/** + * @file small_map.h + * @brief Provides a simple sorted map implementation for small key-value sets. + */ +#ifndef ISTD_UTIL_SMALL_MAP_H +#define ISTD_UTIL_SMALL_MAP_H + +#include +#include +#include + +namespace istd { + +/** + * @brief A simple sorted map for small key-value sets. + * + * Stores entries in a sorted vector and provides basic map operations. + * + * @tparam T_Key Type of the key. + * @tparam T_Value Type of the value. + */ +template +class SmallMap { + /** + * @brief Internal entry structure for key-value pairs. + */ + struct Entry { + T_Key key; ///< Key of the entry + T_Value value; ///< Value of the entry + + /** + * @brief Comparison operator for sorting entries by key. + * @param other_key Key to compare with. + * @return True if this entry's key is less than other_key. + */ + bool operator<(const T_Key &other_key) const { + return key < other_key; + } + }; + + std::vector entries_; ///< Container for all entries + +public: + /** + * @brief Default constructor. + */ + SmallMap() = default; + + /** + * @brief Inserts a new key-value pair. + * @param key Key to insert. + * @param value Value to associate with the key. + * @throws std::invalid_argument if the key already exists. + */ + void insert(const T_Key &key, const T_Value &value) { + // Binary search for existing key + auto it = std::lower_bound(entries_.begin(), entries_.end(), key); + + if (it != entries_.end() && it->key == key) { + // Key exists, but it shouldn't + throw std::invalid_argument("Key already exists in SmallMap"); + } + + // Insert new entry in sorted order + entries_.insert(it, {key, value}); + } + + /** + * @brief Accesses the value associated with a key. + * @tparam Self Type of the SmallMap instance. + * @param key Key to look up. + * @return Reference to the value associated with the key. + * @throws std::out_of_range if the key does not exist. + */ + template + auto &&operator[](this Self &&self, const T_Key &key) { + auto it = std::lower_bound( + self.entries_.begin(), self.entries_.end(), key + ); + + if (it != self.entries_.end() && it->key == key) { + return it->value; // Return existing value + } + throw std::out_of_range("Key not found in SmallMap"); + } + + /** + * @brief Removes all entries from the map. + */ + void clear() noexcept { + entries_.clear(); + } + + /** + * @brief Returns the number of entries in the map. + * @return Number of key-value pairs. + */ + size_t size() const noexcept { + return entries_.size(); + } + + /** + * @brief Checks if the map is empty. + * @return True if the map contains no entries. + */ + bool empty() const noexcept { + return entries_.empty(); + } + + /** + * @brief Removes the entry with the specified key. + * @param key Key to remove. + * @throws std::out_of_range if the key does not exist. + */ + void erase(const T_Key &key) { + auto it = std::lower_bound(entries_.begin(), entries_.end(), key); + if (it != entries_.end() && it->key == key) { + entries_.erase(it); // Remove the entry + } else { + throw std::out_of_range("Key not found in SmallMap"); + } + } + + /** + * @brief Returns an iterator to the beginning of the entries. + * @return Iterator to the first entry. + */ + auto begin() noexcept { + return entries_.begin(); + } + + /** + * @brief Returns an iterator to the end of the entries. + * @return Iterator to one past the last entry. + */ + auto end() noexcept { + return entries_.end(); + } + + /** + * @brief Returns a const iterator to the beginning of the entries. + * @return Const iterator to the first entry. + */ + auto cbegin() const noexcept { + return entries_.cbegin(); + } + + /** + * @brief Returns a const iterator to the end of the entries. + * @return Const iterator to one past the last entry. + */ + auto cend() const noexcept { + return entries_.cend(); + } +}; + +} // namespace istd + +#endif \ No newline at end of file diff --git a/util/include/util.h b/util/include/util.h new file mode 100644 index 0000000..da7952e --- /dev/null +++ b/util/include/util.h @@ -0,0 +1 @@ +#include "istd_util/small_map.h" diff --git a/util/test/CMakeLists.txt b/util/test/CMakeLists.txt new file mode 100644 index 0000000..8fb31e4 --- /dev/null +++ b/util/test/CMakeLists.txt @@ -0,0 +1,9 @@ +cmake_minimum_required(VERSION 3.27) + +add_executable(test_small_map small_map.cpp) +target_link_libraries(test_small_map PRIVATE istd_util) +target_compile_features(test_small_map PRIVATE cxx_std_23) + +include(CTest) +enable_testing() +add_test(NAME util_small_map COMMAND test_small_map) diff --git a/util/test/small_map.cpp b/util/test/small_map.cpp new file mode 100644 index 0000000..3ab36aa --- /dev/null +++ b/util/test/small_map.cpp @@ -0,0 +1,77 @@ +#include "istd_util/small_map.h" +#include + +int main() { + using namespace istd; + // Test insert and size + SmallMap map; + assert(map.empty()); + map.insert(1, 10); + map.insert(2, 20); + map.insert(3, 30); + assert(map.size() == 3); + assert(!map.empty()); + + // Test operator[] + assert(map[1] == 10); + assert(map[2] == 20); + assert(map[3] == 30); + + // Test erase + map.erase(2); + assert(map.size() == 2); + assert(map[1] == 10); + assert(map[3] == 30); + + // Test clear + map.clear(); + assert(map.empty()); + + // Test duplicate insert throws + SmallMap map2; + map2.insert(5, 50); + bool thrown = false; + try { + map2.insert(5, 60); + } catch (const std::invalid_argument &) { + thrown = true; + } + assert(thrown); + + // Test out_of_range on operator[] + thrown = false; + try { + (void)map2[99]; + } catch (const std::out_of_range &) { + thrown = true; + } + assert(thrown); + + // Test erase throws on missing key + thrown = false; + try { + map2.erase(99); + } catch (const std::out_of_range &) { + thrown = true; + } + assert(thrown); + + // Test iterator + map2.insert(6, 60); + map2.insert(7, 70); + int sum = 0; + for (auto it = map2.begin(); it != map2.end(); ++it) { + sum += it->value; + } + assert(sum == 180); // 50 + 60 + 70 + + // Test const iterators + const auto &cmap = map2; + sum = 0; + for (auto it = cmap.cbegin(); it != cmap.cend(); ++it) { + sum += it->value; + } + assert(sum == 180); + + return 0; +}