├── .gitignore ├── test ├── detail │ └── catch_main.cpp ├── CMakeLists.txt ├── test_flat_map.cpp └── test_static_flat_map.cpp ├── .gitmodules ├── bench ├── CMakeLists.txt └── bench.cpp ├── include ├── CMakeLists.txt └── FlatMap │ ├── StaticFlatMap.hpp │ └── FlatMap.hpp ├── third_party └── CMakeLists.txt ├── Makefile ├── README.md └── CMakeLists.txt /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | -------------------------------------------------------------------------------- /test/detail/catch_main.cpp: -------------------------------------------------------------------------------- 1 | #define CATCH_CONFIG_MAIN 2 | #include 3 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "third_party/benchmark"] 2 | path = third_party/benchmark 3 | url = git@github.com:google/benchmark.git 4 | [submodule "third_party/Catch2"] 5 | path = third_party/Catch2 6 | url = git@github.com:catchorg/Catch2.git 7 | -------------------------------------------------------------------------------- /bench/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(bench bench.cpp) 2 | target_link_libraries(bench WarningFlags) 3 | target_link_libraries(bench FlatMap) 4 | target_link_libraries(bench benchmark) 5 | set_target_properties(bench PROPERTIES CXX_STANDARD 17) 6 | -------------------------------------------------------------------------------- /include/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # add_library(FlatMap INTERFACE) 2 | target_sources(FlatMap INTERFACE 3 | "${CMAKE_CURRENT_SOURCE_DIR}/FlatMap/StaticFlatMap.hpp" 4 | # "${CMAKE_CURRENT_SOURCE_DIR}/flatmaps/flat_map.hpp" 5 | ) 6 | # target_include_directories(FlatMap INTERFACE 7 | # "${CMAKE_CURRENT_SOURCE_DIR}") 8 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(unittest 2 | detail/catch_main.cpp 3 | test_static_flat_map.cpp 4 | test_flat_map.cpp 5 | ) 6 | set_target_properties(unittest PROPERTIES CXX_STANDARD 17) 7 | target_link_libraries(unittest PUBLIC WarningFlags) 8 | target_link_libraries(unittest PUBLIC Catch2) 9 | target_link_libraries(unittest PUBLIC FlatMap) 10 | -------------------------------------------------------------------------------- /third_party/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | if (FLATMAP_BUILD_TESTS) 2 | add_subdirectory(Catch2) 3 | endif(FLATMAP_BUILD_TESTS) 4 | 5 | if (FLATMAP_BUILD_BENCMARKS) 6 | option(BENCHMARK_ENABLE_TESTING OFF) 7 | option(BENCHMARK_ENABLE_EXCEPTIONS ON) 8 | option(BENCHMARK_ENABLE_INSTALL OFF) 9 | option(BENCHMARK_DOWNLOAD_DEPENDENCIES OFF) 10 | add_subdirectory(benchmark) 11 | endif (FLATMAP_BUILD_BENCMARKS) 12 | -------------------------------------------------------------------------------- /test/test_flat_map.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | TEST_CASE("FM empty", "[FlatMap]") 5 | { 6 | FlatMap m; 7 | REQUIRE(m.size() == 0u); 8 | REQUIRE(m.empty() == true); 9 | 10 | FlatMap::iterator it1 = m.begin(); 11 | FlatMap::iterator it2 = m.end(); 12 | REQUIRE(it1 == it2); 13 | 14 | m.insert(std::make_pair(1, 1)); 15 | REQUIRE(m.empty() == false); 16 | REQUIRE(m.size() == 1u); 17 | } 18 | 19 | TEST_CASE("FM lower_bound", "[FlatMap]") 20 | { 21 | FlatMap m; 22 | FlatMap::iterator it = m.lower_bound(3); 23 | REQUIRE(it == m.end()); 24 | 25 | FlatMap m2; 26 | m.swap(m2); 27 | 28 | using std::swap; 29 | swap(m, m2); 30 | } 31 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CC=gcc-8 2 | CXX=g++-8 3 | GENERATOR=Ninja 4 | 5 | 6 | build: debug release 7 | 8 | .PHONY: run 9 | run: release 10 | ./build/release/bench/bench 11 | 12 | .PHONY: test 13 | test: debug 14 | ./build/debug/test/unittest 15 | 16 | .PHONY: release 17 | release: build/release 18 | ninja -C build/release 19 | 20 | .PHONY: debug 21 | debug: build/debug 22 | ninja -C build/debug 23 | 24 | build/release: 25 | mkdir -p build/release 26 | CC=$(CC) CXX=$(CXX) cmake -G$(GENERATOR) -DCMAKE_BUILD_TYPE=Release -Bbuild/release -H. 27 | 28 | build/debug: 29 | mkdir -p build/debug 30 | CC=$(CC) CXX=$(CXX) cmake -G$(GENERATOR) -DCMAKE_BUILD_TYPE=Debug -Bbuild/debug -H. 31 | 32 | .PHONY: fullclean 33 | fullclean: 34 | rm -rf build/ 35 | 36 | .PHONY: clean 37 | clean: 38 | ninja -C build/debug clean 39 | ninja -C build/release clean 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # StaticFlatMap 2 | A high performance map. 3 | 4 | This is a map implementation (actually a multimap) implemented with performance first in mind. 5 | The map uses concurrent memory giving fast iteration and copy. Though resulting in slow inserts 6 | and removes. 7 | 8 | The implementation is based on keepinag a sorted array as the map object and searching with 9 | a binary search for lookups. 10 | 11 | It's good when 12 | 1. You can know your keys in advance 13 | 2. You have a small map 14 | 3. You have small objects 15 | 16 | No so good when: 17 | 1. The map is large 18 | 2. The objects are large 19 | 3. There are a lot of insertions and deletions 20 | 21 | 22 | Good luck and have fun 23 | 24 | Quick Installation: 25 | 26 | System Wide: 27 | ``` 28 | mkdir build 29 | cd build/ 30 | cmake .. 31 | make 32 | make install 33 | ``` 34 | 35 | Project: 36 | ``` 37 | add_subdirectory(StaticFlatMap) 38 | target_link_libraries( PUBLIC FlatMap) 39 | ``` 40 | 41 | OR 42 | 43 | ``` 44 | cp -R include/FlatMap 45 | ``` 46 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.0) 2 | 3 | project(FlatMap 4 | VERSION 0.0.1 5 | LANGUAGES CXX 6 | ) 7 | 8 | option(FLATMAP_BUILD_BENCMARKS "Build map benchmarks" ON) 9 | option(FLATMAP_BUILD_TESTS "Build unittests" ON) 10 | 11 | add_library(WarningFlags INTERFACE) 12 | target_compile_options(WarningFlags INTERFACE 13 | -Wall 14 | -Werror 15 | ) 16 | 17 | # Create 'linkable' target, to use in your project do: 18 | # target_link_libraries( PUBLIC FlatMap) 19 | add_library(FlatMap INTERFACE) 20 | target_include_directories(FlatMap INTERFACE 21 | $) 22 | target_include_directories(FlatMap SYSTEM INTERFACE 23 | $/include>) 24 | install(DIRECTORY include/FlatMap DESTINATION include) 25 | # target_include_directories(FlatMap INTERFACE include/) 26 | # target_include_directories(FlatMap 27 | # INTERFACE 28 | # $ 29 | # $ 30 | # ) 31 | 32 | # namespaced alias: 33 | add_library(FlatMap::FlatMap ALIAS FlatMap) 34 | 35 | add_subdirectory(third_party) 36 | add_subdirectory(include) 37 | 38 | if (FLATMAP_BUILD_TESTS) 39 | add_subdirectory(test) 40 | endif (FLATMAP_BUILD_TESTS) 41 | 42 | if (FLATMAP_BUILD_BENCMARKS) 43 | add_subdirectory(bench) 44 | endif (FLATMAP_BUILD_BENCMARKS) 45 | -------------------------------------------------------------------------------- /test/test_static_flat_map.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | // TODO: add emplace() 5 | // TODO: add static_assert on TriviallyCopyable 6 | // TODO: add capacity() function (I guess same as max_size()) for consistency 7 | // TODO: add erase(const key_type& key) 8 | // TODO: maxMembers should be exposed constexpr: 9 | // probably both make max_size() constexpr, and 10 | // add static constexpr member 11 | 12 | TEST_CASE("SFM insert", "[StaticFlatMap]") 13 | { 14 | constexpr int kCount = 100; 15 | StaticFlatMap m; 16 | for (int i = 0; i < kCount; ++i) { 17 | auto it = m.Insert(std::make_pair(i, i + 1)); 18 | REQUIRE(it != m.end()); 19 | REQUIRE(it->first == i); 20 | REQUIRE(it->second == i + 1); 21 | } 22 | } 23 | 24 | TEST_CASE("SFM lookup", "[StaticFlatMap]") 25 | { 26 | constexpr int kCount = 100; 27 | StaticFlatMap m; 28 | for (int i = 0; i < kCount; ++i) { 29 | m.Insert(std::make_pair(i, i + 1)); 30 | } 31 | 32 | SECTION("successful lookups") { 33 | for (int i = 0; i < kCount; ++i) { 34 | auto it = m.Find(i); 35 | REQUIRE(it != m.end()); 36 | REQUIRE(it->first == i); 37 | REQUIRE(it->second == i + 1); 38 | } 39 | } 40 | 41 | SECTION("unsuccessful lookups") { 42 | for (int i = kCount; i < 2*kCount; ++i) { 43 | auto it = m.Find(i); 44 | REQUIRE(it == m.end()); 45 | } 46 | } 47 | 48 | SECTION("const lookups") { 49 | const auto& m2 = m; 50 | for (int i = 0; i < kCount; ++i) { 51 | auto it = m2.Find(i); 52 | REQUIRE(it != m2.end()); 53 | REQUIRE(it->first == i); 54 | REQUIRE(it->second == i + 1); 55 | } 56 | } 57 | } 58 | 59 | TEST_CASE("SFM erase", "[StaticFlatMap]") 60 | { 61 | constexpr int kCount = 100; 62 | StaticFlatMap m; 63 | for (int i = 0; i < kCount; ++i) { 64 | m.Insert(std::make_pair(i, i + 1)); 65 | } 66 | 67 | SECTION("successful erase by key") { 68 | REQUIRE(m.size() == static_cast(kCount)); 69 | 70 | for (int i = 0; i < kCount; i += 2) { 71 | auto it1 = m.Find(i); 72 | REQUIRE(it1 != m.end()); 73 | 74 | auto it2 = m.Erase(i); 75 | auto it3 = m.Find(i+1); 76 | REQUIRE(it2 == it3); 77 | 78 | auto it4 = m.Find(i); 79 | REQUIRE(it4 == m.end()); 80 | } 81 | 82 | REQUIRE(m.size() == static_cast(kCount / 2)); 83 | } 84 | 85 | // TODO: Erase(const KeyType& key) doesn't work like std::m::erase() 86 | // which returns number of elements removed. 87 | // SECTION("unsuccessful erase by key") { 88 | // REQUIRE(m.size() == 100u); 89 | // for (int i = 100; i < 200; ++i) { 90 | // auto it = m.Erase(i); 91 | // REQUIRE(it == m.end()); 92 | // } 93 | // REQUIRE(m.size() == 100u); 94 | // } 95 | } 96 | 97 | TEST_CASE("SFM lookup after erase and insert", "[StaticFlatMap]") 98 | { 99 | constexpr int kCount = 100; 100 | StaticFlatMap m; 101 | for (int i = 0; i < kCount; ++i) { 102 | auto it = m.Insert(std::make_pair(i, i + 1)); 103 | REQUIRE(it != m.end()); 104 | } 105 | 106 | for (int i = 1; i < kCount; i += 2) { 107 | m.Erase(i); 108 | } 109 | 110 | for (int i = 1; i < kCount; i += 2) { 111 | auto it = m.Find(i); 112 | REQUIRE(it == m.end()); 113 | m.Insert(std::make_pair(i, i + 2)); 114 | } 115 | 116 | for (int i = 0; i < kCount; ++i) { 117 | auto it = m.Find(i); 118 | REQUIRE(it != m.end()); 119 | REQUIRE(it->first == i); 120 | REQUIRE(it->second == i + 1 + (i & 1)); 121 | } 122 | } 123 | 124 | TEST_CASE("SFM copy", "[StaticFlatMap]") 125 | { 126 | using FlatMap = StaticFlatMap; 127 | FlatMap m1; 128 | FlatMap::iterator it1, it2, it3; 129 | constexpr int kCount = 200; 130 | for (int i = 0; i < kCount; ++i) { 131 | m1.insert(std::make_pair(rand(), rand())); 132 | } 133 | 134 | // copy ctor 135 | FlatMap m2 = m1; 136 | REQUIRE(m1.size() == m2.size()); 137 | REQUIRE(m1.capacity() == m2.capacity()); 138 | it1 = m1.begin(); 139 | it2 = m2.begin(); 140 | while (it1 != m1.end()) { 141 | REQUIRE(it2 != m2.end()); 142 | REQUIRE(it1->first == it2->first); 143 | REQUIRE(it1->second == it2->second); 144 | ++it1; 145 | ++it2; 146 | } 147 | REQUIRE(it1 == m1.end()); 148 | REQUIRE(it2 == m2.end()); 149 | 150 | FlatMap m3; 151 | for (int i = 0; i < kCount; ++i) { 152 | m3.insert(std::make_pair(rand(), rand())); 153 | } 154 | 155 | // copy assignment 156 | m3 = m2; 157 | REQUIRE(m3.size() == m2.size()); 158 | REQUIRE(m3.capacity() == m2.capacity()); 159 | it1 = m1.begin(); 160 | it2 = m2.begin(); 161 | it3 = m3.begin(); 162 | while (it1 != m1.end()) { 163 | REQUIRE(it2 != m2.end()); 164 | REQUIRE(it3 != m3.end()); 165 | REQUIRE(it2->first == it3->first); 166 | REQUIRE(it2->second == it3->second); 167 | REQUIRE(it2->first == it1->first); 168 | REQUIRE(it2->second == it1->second); 169 | ++it1; 170 | ++it2; 171 | ++it3; 172 | } 173 | REQUIRE(it1 == m1.end()); 174 | REQUIRE(it2 == m2.end()); 175 | REQUIRE(it3 == m3.end()); 176 | } 177 | -------------------------------------------------------------------------------- /bench/bench.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | 12 | #define SUCCESSFUL_LOOKUP_BENCH 1 13 | #define COPY_MAP_BENCH 1 14 | 15 | 16 | // The Google.Benchmark macros don't play nicely with templated types 17 | // because of the commas. Have to love macros... 18 | using IntIntStlMap = std::map; 19 | using IntIntStaticFlatMap32 = StaticFlatMap; 20 | using IntIntStaticFlatMap64 = StaticFlatMap; 21 | using IntIntStaticFlatMap128 = StaticFlatMap; 22 | using IntIntStaticFlatMap256 = StaticFlatMap; 23 | 24 | // ----------------------------------------------------------------------------- 25 | // Data Generators 26 | // 27 | 28 | auto getIntMapData(size_t n, int min = INT_MIN, int max = INT_MAX) 29 | { 30 | std::random_device rd; 31 | std::mt19937 gen(rd()); 32 | std::uniform_int_distribution dist(min, max); 33 | 34 | std::unordered_set vs; 35 | while (vs.size() < 2*n) { 36 | vs.insert(dist(gen)); 37 | } 38 | 39 | std::vector> present; 40 | std::vector missing; 41 | for (auto v : vs) { 42 | if (present.size() < n) { 43 | present.emplace_back(v, dist(gen)); 44 | } else { 45 | missing.emplace_back(v); 46 | } 47 | } 48 | assert(present.size() == n); 49 | assert(missing.size() == n); 50 | 51 | std::shuffle(present.begin(), present.end(), gen); 52 | std::shuffle(missing.begin(), missing.end(), gen); 53 | std::shuffle(present.begin(), present.end(), gen); 54 | std::shuffle(missing.begin(), missing.end(), gen); 55 | 56 | return std::make_pair(present, missing); 57 | } 58 | 59 | template 60 | auto getRandomData( 61 | const std::vector& vs, 62 | const std::vector& ms, 63 | int n, // number of elements 64 | double successPct // percent of elements to pull from `vs` 65 | ) 66 | { 67 | std::random_device rd; 68 | std::mt19937 gen(rd()); 69 | std::uniform_int_distribution dist1(0, vs.size() - 1); 70 | std::uniform_int_distribution dist2(0, ms.size() - 1); 71 | std::uniform_int_distribution prob(0, 100); 72 | 73 | using Key = typename KV::first_type; 74 | std::vector rs; 75 | rs.reserve(n); 76 | 77 | successPct = std::max(std::min(successPct, 100.0), 0.0); 78 | int present = n * successPct; 79 | int missing = n - present; 80 | 81 | std::generate_n( 82 | std::back_inserter(rs), 83 | present, 84 | [&]() -> Key { return vs[dist1(gen)].first; } 85 | ); 86 | 87 | std::generate_n( 88 | std::back_inserter(rs), 89 | missing, 90 | [&]() -> Key { return ms[dist2(gen)]; } 91 | ); 92 | 93 | // for good measure... 94 | std::shuffle(rs.begin(), rs.end(), gen); 95 | std::shuffle(rs.begin(), rs.end(), gen); 96 | std::shuffle(rs.begin(), rs.end(), gen); 97 | 98 | return rs; 99 | } 100 | 101 | // ----------------------------------------------------------------------------- 102 | // Successful Lookup Benchmarks 103 | // 104 | #if SUCCESSFUL_LOOKUP_BENCH 105 | 106 | #define SUCCESSFUL_LOOKUP_ARGS \ 107 | ->Args({1<<10, 10}) \ 108 | ->Args({2<<10, 10}) \ 109 | ->Args({1<<10, 20}) \ 110 | ->Args({2<<10, 20}) \ 111 | ->Args({1<<10, 32}) \ 112 | ->Args({2<<10, 32}) \ 113 | 114 | template 115 | static void BM_SuccessfulLookups(benchmark::State& state) { 116 | int count = 0; 117 | for (auto _ : state) { 118 | state.PauseTiming(); 119 | Map m; 120 | auto vals = getIntMapData(state.range(1)); 121 | for (auto&& v : vals.first) { 122 | m.insert(v); 123 | } 124 | auto data = getRandomData(vals.first, vals.second, state.range(0), 1.0); 125 | state.ResumeTiming(); 126 | 127 | for (auto key : data) { 128 | auto it = m.find(key); 129 | benchmark::DoNotOptimize(count += it == m.end()); 130 | } 131 | } 132 | 133 | // just to be safe, use `count` so compiler doesn't optimize away 134 | if (count != 0) { 135 | throw std::runtime_error(""); 136 | } 137 | } 138 | BENCHMARK_TEMPLATE(BM_SuccessfulLookups, IntIntStlMap ) SUCCESSFUL_LOOKUP_ARGS; 139 | BENCHMARK_TEMPLATE(BM_SuccessfulLookups, IntIntStaticFlatMap32 ) SUCCESSFUL_LOOKUP_ARGS; 140 | BENCHMARK_TEMPLATE(BM_SuccessfulLookups, IntIntStaticFlatMap64 ) SUCCESSFUL_LOOKUP_ARGS; 141 | BENCHMARK_TEMPLATE(BM_SuccessfulLookups, IntIntStaticFlatMap128) SUCCESSFUL_LOOKUP_ARGS; 142 | BENCHMARK_TEMPLATE(BM_SuccessfulLookups, IntIntStaticFlatMap256) SUCCESSFUL_LOOKUP_ARGS; 143 | 144 | #endif 145 | 146 | // ----------------------------------------------------------------------------- 147 | // Copy Map Benchmark 148 | // 149 | #if COPY_MAP_BENCH 150 | 151 | #define COPY_MAP_ARGS \ 152 | ->Arg(10) \ 153 | ->Arg(20) \ 154 | ->Arg(30) \ 155 | 156 | template 157 | static void BM_CopyMap(benchmark::State& state) { 158 | size_t count = 0; 159 | for (auto _ : state) { 160 | state.PauseTiming(); 161 | Map m; 162 | for (auto&& v : getIntMapData(state.range(0)).first) { 163 | m.insert(v); 164 | } 165 | state.ResumeTiming(); 166 | 167 | Map m2 = m; 168 | benchmark::DoNotOptimize(count += m2.size()); 169 | } 170 | 171 | // just to be safe, use `count` so compiler doesn't optimize away 172 | if (count == 0u) { 173 | throw std::runtime_error(""); 174 | } 175 | } 176 | BENCHMARK_TEMPLATE(BM_CopyMap, IntIntStlMap ) COPY_MAP_ARGS; 177 | BENCHMARK_TEMPLATE(BM_CopyMap, IntIntStaticFlatMap32 ) COPY_MAP_ARGS; 178 | BENCHMARK_TEMPLATE(BM_CopyMap, IntIntStaticFlatMap64 ) COPY_MAP_ARGS; 179 | BENCHMARK_TEMPLATE(BM_CopyMap, IntIntStaticFlatMap128) COPY_MAP_ARGS; 180 | BENCHMARK_TEMPLATE(BM_CopyMap, IntIntStaticFlatMap256) COPY_MAP_ARGS; 181 | 182 | #endif 183 | 184 | BENCHMARK_MAIN(); 185 | -------------------------------------------------------------------------------- /include/FlatMap/StaticFlatMap.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | 11 | // This class is a statically allocated version of a memory continuous map, mainly useful for small data sets. 12 | // Notice that this is a multimap! Inserting the same key twice will result with duplicate entries (sorted by order of insertion). 13 | // Getters will always return the first matching entry 14 | template < 15 | class _KeyType, 16 | class _ValueType, 17 | size_t _MaxMembers, 18 | class _Compare = std::less<_KeyType> 19 | > 20 | class StaticFlatMap 21 | : private _Compare 22 | { 23 | static_assert(std::is_trivially_copyable<_KeyType>::value, 24 | "StaticFlatMap key type must be IsTriviallyCopyable"); 25 | static_assert(std::is_trivially_copyable<_ValueType>::value, 26 | "StaticFlatMap value type must be IsTriviallyCopyable"); 27 | static_assert(std::is_default_constructible<_ValueType>::value, 28 | "StaticFlatMap value type must be default constructible"); 29 | 30 | public: 31 | using KeyType = _KeyType; 32 | using ValueType = _ValueType; 33 | using KeyValuePair = std::pair; 34 | using ContainerType = std::array; 35 | 36 | // Iterators 37 | using iterator = typename ContainerType::iterator; 38 | using const_iterator = typename ContainerType::const_iterator; 39 | using reverse_iterator = typename ContainerType::reverse_iterator; 40 | using const_reverse_iterator = typename ContainerType::const_reverse_iterator; 41 | using value_type = KeyValuePair; 42 | using key_type = KeyType; 43 | using mapped_type = ValueType; 44 | using key_compare = _Compare; 45 | 46 | struct value_compare { 47 | _Compare comp; 48 | value_compare(_Compare c) : comp(c) {} 49 | 50 | bool operator()(const value_type& x, const value_type& y) const { 51 | return comp(x.first, y.first); 52 | } 53 | }; 54 | 55 | StaticFlatMap(const std::initializer_list& values) 56 | { 57 | for (auto& value : values) 58 | { 59 | Insert(value); 60 | } 61 | } 62 | 63 | constexpr StaticFlatMap() noexcept {} 64 | 65 | iterator Insert(const KeyValuePair& val) 66 | { 67 | auto position = std::upper_bound(begin(), end(), val, compareFunction); 68 | insertByIterator(position, val); 69 | return position; 70 | } 71 | 72 | ValueType& at(const KeyType& key) 73 | { 74 | auto elem = Find(key); 75 | if (elem == end()) 76 | { 77 | throwOutOfRangeError(key, __PRETTY_FUNCTION__); 78 | } 79 | return elem->second; 80 | } 81 | 82 | iterator Find(const KeyType& key) noexcept 83 | { 84 | return const_cast(static_cast(*this).Find(key)); 85 | } 86 | 87 | const_iterator Find(const KeyType& key) const noexcept 88 | { 89 | KeyValuePair dummyPair{key, ValueType()}; 90 | auto it = std::lower_bound(&m_sortedArray[0], &m_sortedArray[m_endIndex], 91 | dummyPair, compareFunction); 92 | return it != end() && !key_comp()(key, it->first) ? it : end(); 93 | } 94 | 95 | iterator Erase(const_iterator position) 96 | { 97 | if (position == end() || m_endIndex == 0) 98 | { 99 | throwRangeError(*position, __PRETTY_FUNCTION__); 100 | } 101 | auto nonConstIter = (iterator)position; 102 | std::copy(nonConstIter + 1, end(), nonConstIter); 103 | --m_endIndex; 104 | return nonConstIter; 105 | } 106 | 107 | iterator Erase(const KeyType& key) { return Erase(Find(key)); } 108 | 109 | ValueType& operator[](const KeyType& key) 110 | { 111 | KeyValuePair dummyPair{key, ValueType()}; 112 | auto range = std::equal_range(begin(), end(), dummyPair, compareFunction); 113 | if (range.first == range.second) 114 | { 115 | // value was not found, inserting it in the right location 116 | insertByIterator(range.first, dummyPair); 117 | } 118 | return range.first->second; 119 | } 120 | 121 | // std::map compatibility 122 | template 123 | iterator erase(const_iterator position) { return Erase(position); } 124 | iterator erase(const KeyType& key) { return Erase(key); } 125 | iterator insert(const KeyValuePair& val) { return Insert(val); } 126 | iterator find(const KeyType& key) noexcept { return Find(key); } 127 | const_iterator find(const KeyType& key) const noexcept { return Find(key); } 128 | 129 | void Clear() noexcept { m_endIndex = 0; } 130 | void clear() noexcept { Clear(); } 131 | 132 | iterator begin() noexcept { return iterator(&m_sortedArray[0]); } 133 | iterator end() noexcept { return iterator(&m_sortedArray[m_endIndex]); } 134 | reverse_iterator rbegin() noexcept { return reverse_iterator(&m_sortedArray[m_endIndex]); } 135 | reverse_iterator rend() noexcept { return reverse_iterator(&m_sortedArray[0]); } 136 | 137 | const_iterator cbegin() const noexcept { return const_iterator(&m_sortedArray[0]); } 138 | const_iterator cend() const noexcept { return const_iterator(&m_sortedArray[m_endIndex]); } 139 | const_reverse_iterator crbegin() const noexcept { return const_reverse_iterator(&m_sortedArray[m_endIndex]); } 140 | const_reverse_iterator crend() const noexcept { return const_reverse_iterator(&m_sortedArray[0]); } 141 | 142 | const_iterator begin() const noexcept { return const_iterator (&m_sortedArray[0]); } 143 | const_iterator end() const noexcept { return const_iterator(&m_sortedArray[m_endIndex]); } 144 | const_reverse_iterator rbegin() const noexcept { return const_reverse_iterator(&m_sortedArray[m_endIndex]); } 145 | const_reverse_iterator rend() const noexcept { return const_reverse_iterator(&m_sortedArray[0]); } 146 | 147 | bool empty() const noexcept { return size() == 0; } 148 | size_t size() const noexcept { return m_endIndex; } 149 | constexpr size_t max_size() const noexcept { return capacity(); } 150 | constexpr size_t capacity() const noexcept { return _MaxMembers; } 151 | key_compare key_comp() const noexcept { return *this; } 152 | value_compare value_comp() const noexcept { return value_compare{key_comp()}; } 153 | 154 | private: 155 | 156 | void insertByIterator(const iterator& position, const KeyValuePair& val) 157 | { 158 | if (size() == _MaxMembers) 159 | throwRangeError(val, __PRETTY_FUNCTION__); 160 | std::copy_backward(position, end(), end() + 1); 161 | *position = val; 162 | ++m_endIndex; 163 | } 164 | 165 | void throwRangeError(const KeyValuePair& val, const char* throwingFunction) 166 | { 167 | std::stringstream errorMessage; 168 | errorMessage << throwingFunction << " : Out of range! key = " << val.first << " value = " << val.second; 169 | throw std::range_error(errorMessage.str().c_str()); 170 | } 171 | 172 | void throwOutOfRangeError(const KeyType& key, const char* throwingFunction) 173 | { 174 | std::stringstream errorMessage; 175 | errorMessage << throwingFunction << " : Could not find object in map! key = " << key; 176 | throw std::out_of_range(errorMessage.str().c_str()); 177 | } 178 | 179 | static bool compareFunction(const KeyValuePair& first, const KeyValuePair& second) noexcept 180 | { 181 | static _Compare less; 182 | return less(first.first, second.first); 183 | } 184 | 185 | ContainerType m_sortedArray; 186 | size_t m_endIndex = 0; 187 | }; 188 | 189 | -------------------------------------------------------------------------------- /include/FlatMap/FlatMap.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | 10 | template < 11 | typename _Key, 12 | typename _T, 13 | typename _Compare = std::less<_Key> 14 | > 15 | class FlatMap 16 | : private _Compare 17 | { 18 | static_assert(std::is_trivially_copyable<_Key>::value, 19 | "FlatMap key type must be Trivially Copyable"); 20 | static_assert(std::is_trivially_copyable<_T>::value, 21 | "FlatMap mapped type must be Trivially Copyable"); 22 | 23 | struct Iterator; 24 | struct PairPtr; 25 | // struct ConstIterator; 26 | 27 | public: 28 | using key_compare = _Compare; 29 | using key_type = _Key; 30 | using mapped_type = _T; 31 | using value_type = std::pair; 32 | using size_type = std::size_t; 33 | using iterator = Iterator; 34 | // using const_iterator = ConstIterator; 35 | using difference_type = std::ptrdiff_t; 36 | using reference = value_type&; 37 | using const_reference = const value_type&; 38 | using pointer = 39 | // using pointer = value_type*; 40 | using const_pointer = const value_type*; 41 | 42 | FlatMap(const key_compare& comp = key_compare()) noexcept 43 | : _Compare{comp} {} 44 | 45 | // FlatMap(std::initializer_list values) noexcept; 46 | // FlatMap(const FlatMap& other) noexcept; 47 | // FlatMap(FlatMap&& other) noexcept; 48 | // FlatMap& operator=(const FlatMap& other) noexcept; 49 | // FlatMap& operator=(FlatMap&& other) noexcept; 50 | // bool operator==(const FlatMap& other) noexcept; 51 | // bool operator!=(const FlatMap& other) noexcept; 52 | 53 | iterator begin() noexcept 54 | { 55 | return iterator{&_keys[0], &_vals[0]}; 56 | } 57 | 58 | // const_iterator begin() const noexcept; 59 | // const_iterator cbegin() const noexcept; 60 | iterator end() noexcept 61 | { 62 | return iterator{&_keys[_size], &_vals[_size]}; 63 | } 64 | // const_iterator end() const noexcept; 65 | // const_iterator cend() const noexcept; 66 | 67 | constexpr bool empty() const noexcept 68 | { 69 | return _size == 0u; 70 | } 71 | 72 | constexpr size_type size() const noexcept 73 | { 74 | return _size; 75 | } 76 | 77 | constexpr size_type max_size() const noexcept 78 | { 79 | return std::numeric_limits::max(); 80 | } 81 | 82 | // void clear() noexcept; 83 | 84 | std::pair insert(const value_type& x) noexcept 85 | { 86 | auto it = lower_bound(x.first); 87 | if (it == end() || !key_comp()(x.first, it->first)) 88 | return std::make_pair(it, false); 89 | // TODO: grow 90 | difference_type pos = it - begin(); 91 | difference_type cnt = _size - pos + 1; 92 | memmove(&_keys[pos+1], &_keys[pos], sizeof(*_keys)*cnt); 93 | memmove(&_vals[pos+1], &_vals[pos], sizeof(*_vals)*cnt); 94 | ++_size; 95 | return std::make_pair(iterator{&_keys[pos], &_vals[pos]}, true); 96 | } 97 | 98 | // template ::value>> 100 | // std::pair insert(P&& value) noexcept; 101 | 102 | // template 103 | // void insert(InputIt first, InputIt last) noexcept; 104 | 105 | // template 106 | // std::pair emplace(Args&&... args) noexcept; 107 | 108 | // iterator find(const key_type& key) noexcept; 109 | // const_iterator find(const key_type& key) const noexcept; 110 | 111 | // template 113 | // iterator find(const K& key) noexcept; 114 | 115 | // template 117 | // const_iterator find(const K& key) const noexcept; 118 | 119 | // iterator erase(const_iterator pos) noexcept; 120 | // iterator erase(iterator pos) noexcept; 121 | // iterator erase(const_iterator first, const_iterator last) noexcept; 122 | // size_type erase(const key_type& key) noexcept; 123 | 124 | // size_type count(const key_type& key) const noexcept; 125 | // bool contains(const key_type& key) const noexcept; 126 | 127 | iterator lower_bound(const key_type& key) noexcept 128 | { 129 | // TODO: dispatch off size 130 | return _lower_bound_linear(key); 131 | } 132 | // const_iterator lower_bound(const key_type& key) const noexcept; 133 | // template 135 | // iterator lower_bound(const K& k) noexcept; 136 | // template 138 | // const_iterator lower_bound(const K& k) const noexcept; 139 | 140 | // std::pair equal_range(const key_type& key) noexcept; 141 | // std::pair equal_range(const key_type& key) const noexcept; 142 | // template 144 | // std::pair equal_range(const K& key) noexcept; 145 | // template 147 | // std::pair equal_range(const key_type& key) const noexcept; 148 | 149 | // iterator upper_bound(const key_type& key) noexcept; 150 | // const_iterator upper_bound(const key_type& key) const noexcept; 151 | // template 153 | // iterator upper_bound(const K& k) noexcept; 154 | // template 156 | // const_iterator upper_bound(const K& k) const noexcept; 157 | 158 | void swap(FlatMap& other) noexcept(std::is_nothrow_swappable<_Compare>::value) 159 | { 160 | std::swap(_keys, other._keys); 161 | std::swap(_vals, other._vals); 162 | std::swap(_size, other._size); 163 | std::swap(_capacity, other._capacity); 164 | std::swap(static_cast<_Compare&>(*this), static_cast<_Compare&>(other)); 165 | } 166 | 167 | constexpr key_compare key_comp() const noexcept { return *this; } 168 | 169 | private: 170 | iterator _lower_bound_linear(const key_type& key) noexcept 171 | { 172 | auto comp = key_compare(); 173 | const key_type* b = &_keys[0]; 174 | const key_type* e = &_keys[_size]; 175 | const key_type* k; 176 | for (k = b; k != e; ++k) { 177 | if (!comp(key, *k)) 178 | break; 179 | } 180 | return _make_iterator(k); 181 | } 182 | 183 | iterator _make_iterator(const key_type* k) noexcept 184 | { 185 | auto pos = k - &_keys[0]; 186 | return iterator{k, &_vals[pos]}; 187 | } 188 | 189 | // const key_type* _keys = nullptr; 190 | // const key_type* _keyend = nullptr; 191 | // mapped_type* _vals = nullptr; 192 | // size_type _capacity = 0; 193 | 194 | // TODO: use u32 for size and capacity? 195 | key_type* _keys = nullptr; 196 | mapped_type* _vals = nullptr; 197 | size_type _size = 0; 198 | size_type _capacity = 0; 199 | }; 200 | 201 | template 202 | struct FlatMap::PairPtr : std::pair { 203 | constexpr PairPtr(const Key& k, T& v) noexcept 204 | : std::pair{k, v} {} 205 | 206 | const std::pair* operator->() const noexcept 207 | { 208 | return this; 209 | } 210 | }; 211 | 212 | template 213 | struct FlatMap::Iterator { 214 | constexpr Iterator() noexcept = default; 215 | constexpr Iterator(const key_type* key, mapped_type* val) noexcept 216 | : _key{key}, _val{val} 217 | {} 218 | 219 | std::pair operator*() noexcept 220 | { 221 | return std::make_pair(*_key, *_val); 222 | } 223 | 224 | std:: operator->() noexcept 225 | { 226 | return &operator*(); 227 | } 228 | 229 | iterator& operator++() noexcept 230 | { 231 | ++_key; ++_val; 232 | return *this; 233 | } 234 | 235 | iterator operator++(int) noexcept 236 | { 237 | iterator tmp{*this}; 238 | ++(*this); 239 | return tmp; 240 | } 241 | 242 | iterator& operator+=(difference_type n) noexcept 243 | { 244 | _key += n; 245 | _val += n; 246 | return *this; 247 | } 248 | 249 | iterator& operator-=(difference_type n) noexcept 250 | { 251 | _key -= n; 252 | _val -= n; 253 | } 254 | 255 | iterator operator+(difference_type n) noexcept 256 | { 257 | *this += n; 258 | return *this; 259 | } 260 | 261 | iterator operator-(difference_type n) noexcept 262 | { 263 | *this += n; 264 | return *this; 265 | } 266 | 267 | difference_type operator-(iterator other) noexcept 268 | { 269 | return _key - other._key; 270 | } 271 | 272 | friend bool operator==(Iterator a, Iterator b) noexcept 273 | { 274 | return a._key == b._key; 275 | } 276 | 277 | friend bool operator!=(Iterator a, Iterator b) noexcept 278 | { 279 | return a._key != b._key; 280 | } 281 | 282 | friend bool operator<(Iterator a, Iterator b) noexcept 283 | { 284 | return a._key < b._key; 285 | } 286 | 287 | friend bool operator>(Iterator a, Iterator b) noexcept 288 | { 289 | return a._key > b._key; 290 | } 291 | 292 | friend bool operator<=(Iterator a, Iterator b) noexcept 293 | { 294 | return a._key <= b._key; 295 | } 296 | 297 | friend bool operator>=(Iterator a, Iterator b) noexcept 298 | { 299 | return a._key >= b._key; 300 | } 301 | 302 | private: 303 | const key_type* _key; 304 | mapped_type* _val; 305 | }; 306 | 307 | namespace std { 308 | 309 | template 310 | void swap(FlatMap& x, FlatMap& y) noexcept 311 | { 312 | x.swap(y); 313 | } 314 | 315 | } // ~std 316 | --------------------------------------------------------------------------------