feat: add SmallMap implementation and associated tests

Signed-off-by: szdytom <szdytom@qq.com>
This commit is contained in:
方而静 2025-08-04 14:46:34 +08:00
parent 280aff5465
commit b40c19ddc7
Signed by: szTom
GPG Key ID: 072D999D60C6473C
6 changed files with 265 additions and 1 deletions

View File

@ -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)

10
util/CMakeLists.txt Normal file
View File

@ -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()

View File

@ -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 <algorithm>
#include <stdexcept>
#include <vector>
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<typename T_Key, typename T_Value>
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<Entry> 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<typename Self>
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

1
util/include/util.h Normal file
View File

@ -0,0 +1 @@
#include "istd_util/small_map.h"

9
util/test/CMakeLists.txt Normal file
View File

@ -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)

77
util/test/small_map.cpp Normal file
View File

@ -0,0 +1,77 @@
#include "istd_util/small_map.h"
#include <cassert>
int main() {
using namespace istd;
// Test insert and size
SmallMap<int, int> 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<int, int> 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;
}