├── .github └── workflows │ ├── ubuntu-ci.yml │ └── vs17-ci.yml ├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── benchmark ├── CMakeLists.txt └── b.cpp ├── include └── radix_cpp.h └── tests └── test.cpp /.github/workflows/ubuntu-ci.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | 3 | name: Ubuntu-CI 4 | 5 | # Controls when the action will run. 6 | on: 7 | # Triggers the workflow on push or pull request events but only for the main branch 8 | push: 9 | branches: [ main ] 10 | pull_request: 11 | branches: [ main ] 12 | 13 | # Allows you to run this workflow manually from the Actions tab 14 | workflow_dispatch: 15 | 16 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 17 | jobs: 18 | # This workflow contains a single job called "build" 19 | build: 20 | # The type of runner that the job will run on 21 | runs-on: ubuntu-latest 22 | 23 | # Steps represent a sequence of tasks that will be executed as part of the job 24 | steps: 25 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 26 | - uses: actions/checkout@v4 27 | 28 | # Build 29 | - name: Build 30 | run: | 31 | mkdir build 32 | cd build 33 | cmake .. 34 | make 35 | 36 | # Test 37 | - name: Test 38 | run: | 39 | cd build 40 | ./tests 41 | -------------------------------------------------------------------------------- /.github/workflows/vs17-ci.yml: -------------------------------------------------------------------------------- 1 | name: VS17-CI 2 | 3 | on: [push, pull_request] 4 | 5 | permissions: 6 | contents: read 7 | 8 | jobs: 9 | ci: 10 | name: windows-vs17 11 | runs-on: windows-latest 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | include: 16 | - {gen: Visual Studio 17 2022, arch: x64} 17 | steps: 18 | - name: checkout 19 | uses: actions/checkout@v4 20 | with: 21 | submodules: 'recursive' 22 | - name: Configure 23 | working-directory: ${{github.workspace}} 24 | run: | 25 | mkdir build 26 | cd build && cmake -G "${{matrix.gen}}" -A ${{matrix.arch}} .. 27 | - name: Build 28 | working-directory: ${{github.workspace}} 29 | run: cmake --build build 30 | - name: Testing 31 | run: | 32 | build\Debug\tests.exe 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | 3 | project(tests) 4 | 5 | set(CMAKE_CXX_STANDARD 17) 6 | set(CMAKE_CXX_STANDARD_REQUIRED True) 7 | 8 | # Set default build mode 9 | if(NOT CMAKE_BUILD_TYPE) 10 | message(WARNING "CMAKE_BUILD_TYPE not set; setting to Release") 11 | set(CMAKE_BUILD_TYPE "Release") 12 | endif() 13 | 14 | IF (MSVC) 15 | 16 | else() 17 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=native -O3 -Wall -Wsuggest-override -Werror=return-local-addr -Werror=multichar -Werror=enum-compare -Werror=return-type -Werror=conversion-null -Werror=parentheses -Werror=address -Werror=trigraphs -Werror=pointer-arith -Werror=write-strings -Werror=pessimizing-move -Wuninitialized -Wno-unknown-pragmas -Werror=switch -Werror=format -Werror=non-virtual-dtor -Werror=cast-qual -Wconversion -Wsign-conversion -Wsign-promo") 18 | # set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pg") 19 | # set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -pg") 20 | endif() 21 | 22 | include(FetchContent) 23 | FetchContent_Declare( 24 | Catch2 25 | GIT_REPOSITORY https://github.com/catchorg/Catch2.git 26 | GIT_TAG v3.4.0 # or a later release 27 | ) 28 | FetchContent_MakeAvailable(Catch2) 29 | 30 | add_executable(tests tests/test.cpp) 31 | target_link_libraries(tests PRIVATE Catch2::Catch2WithMain) 32 | target_include_directories(tests PRIVATE include) 33 | 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Mikael Rekola 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # radix-cpp 2 | 3 | [![GitHub License](https://img.shields.io/github/license/rekola/radix-cpp?logo=github&logoColor=lightgrey&color=yellow)](https://github.com/rekola/radix-cpp/blob/main/LICENSE) 4 | [![CI](https://github.com/rekola/radix-cpp/workflows/Ubuntu-CI/badge.svg)]() 5 | [![VS17-CI](https://github.com/rekola/radix-cpp/workflows/VS17-CI/badge.svg)]() 6 | [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](http://makeapullrequest.com) 7 | 8 | ## Radix set and map implementation for C++ 9 | 10 | radix-cpp is an experimental flat implementation of ordered set and 11 | map. It uses a hash table with open addressing to implement a form of 12 | radix sort combined with prefix trees, thus providing Θ(1) search time 13 | as is usual for hash tables, but also quick in order traversal of the 14 | keys. Ordinarily hash tables are not ordered, and while in theory, an 15 | order preserving hash function could be used, it would lead to large 16 | number of collisions. In this implementation the key is divided into 17 | multiple 8-bit digits, and each digit is inserted separately in to the 18 | hash table. The prefix key is also stored along with each digit to 19 | construct the prefix tree. According to benchmarks (given uint32_t 20 | keys) the radix-cpp set construction is much faster than that of 21 | std::set, and also faster than sorting the keys using std::sort. Both 22 | std::sort and std::set use comparison sorthing so they have time 23 | complexity of O(n) = n log n. 24 | 25 | Iterators are automatically repaired if the underlying table changes, 26 | so they are stable. 27 | 28 | Currently only integers, floats, doubles and strings are supported as 29 | keys, but more support is forthcoming. 30 | 31 | ### Time Complexity 32 | 33 | | Operation | Average | Worst Case | 34 | | - | - | - | 35 | | Search | Θ(1) | O(n) | 36 | | Insert | Θ(w) | O(w*n) | 37 | | Delete | Θ(w) | O(w*n) | 38 | | upper_bound() | ? | ? | 39 | 40 | * w is the key length in bytes 41 | 42 | Iterating over nodes in order can be somewhat expensive if the next 43 | node has different prefix. Also, it's unclear what the time complexity 44 | of the iteration operation is. 45 | 46 | ### Benchmarks 47 | 48 | In these initial benchmarks, radix_cpp::set has been compared with 49 | std::set using uint32_t as the key type. For comparison, a test with 50 | std::sort and a std::vector is also done, and they have similar speed 51 | as std::flat_set. The test consists of constructing a set out of 52 | shuffled array of N consecutive integers and doing an ordered 53 | iteration over the entire set. Search speed comparison has been 54 | intentionally left out, since it would not be very useful given that 55 | radix-cpp has avarage complexity of Θ(1). 56 | 57 | ![Ordered Set Construction Time](https://github.com/rekola/radix-cpp/assets/6755525/ec1adb25-52dc-407c-86c5-af1b2d97eca9 "Ordered Set Construction Time") 58 | ![Ordered Set Iteration Time](https://github.com/rekola/radix-cpp/assets/6755525/fe83baa4-7b15-4642-8f5e-1efed45f17a7 "Ordered Set Iteration Time") 59 | 60 | ## Implementation 61 | 62 | radix-cpp uses Murmur3 as the hash function. The keys can be of 63 | arbitrary size. 64 | 65 | ### Node 66 | 67 | A Node contains the following data: 68 | 69 | | Datum | Description | 70 | | - | - | 71 | | payload | pointer to the key or the key/value pair, or 1 for tomb stones | 72 | | prefix key | The prefix key of the node | 73 | | ordinal | The ordinal of the node (0-255) | 74 | | depth | The least-significant-byte of the depth of the node in the prefix tree (0 = empty key) | 75 | | value count | The number of entries stored in the tree under this node | 76 | 77 | ### Inserting 78 | 79 | When inserting a key, each 8-bit digit is inserted along with its 80 | prefix. A prefix tree is thus created inside the hash table. 81 | 82 | ### Search 83 | 84 | When searching for a known key, only the Node for the last digit needs 85 | to be found. The input key is split into a prefix of n-1 bytes and 1 86 | byte ordinal, where n is the size of the key. If a Node with the 87 | prefix and ordinal is found, it is returned. 88 | 89 | ### Deletion 90 | 91 | Deletion works by using tombstones. 92 | 93 | ### Iteration 94 | 95 | An iterator has four variables: the depth (in the prefix tree), the 96 | unordered prefix, the 8-bit ordinal value, and the offset. While the 97 | ordinal is smaller than 255, we know that there are still nodes 98 | available in the ordered range, and when advancing to the next stored 99 | value, we can check them all in order. When the range runs out, we 100 | fall back to the previous digit and advance that one. If the new node 101 | is not a final node, we go upwards in the tree and find the smallest 102 | final node. The offset is used for probing in case of collisions. 103 | 104 | ### Limitations and Future Plans 105 | 106 | - Maximum number of elements on 64-bit system is is 2^56 107 | - NaNs are sorted as they were larger than any other value 108 | - How to sort std::any? 109 | - Unordered iteration is needed (e.g. for set union and intersection) 110 | 111 | ## Extending types 112 | 113 | To implement set and map for custom type, the following free functions must be defined: 114 | 115 | | Function | Description | 116 | | - | - | 117 | | key_type append(key_type key, size_t digit) | Returns a new key with digit appended as the new least significant digit | 118 | | std::pair deconstruct(key_type key) | Returns a pair with the numeric value of the least significant digit of the key and the key with the least significant digit removed | 119 | | size_t keysize(key_type key) | Returns the number of digits in the key | 120 | 121 | Additionally, there must exist a specialization of std::hash for 122 | key_type. Signed integers and floating point numbers are not naturally 123 | ascending, and in such case the initial deconstruct also converts the 124 | data to ordered binary type. 125 | -------------------------------------------------------------------------------- /benchmark/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | 3 | # set the project name 4 | project(benchmark VERSION 0.1.0) 5 | 6 | set(CMAKE_CXX_STANDARD 17) 7 | set(CMAKE_CXX_STANDARD_REQUIRED True) 8 | 9 | # Set default build mode 10 | if(NOT CMAKE_BUILD_TYPE) 11 | message(WARNING "CMAKE_BUILD_TYPE not set; setting to Release") 12 | set(CMAKE_BUILD_TYPE "Release") 13 | endif() 14 | 15 | IF (MSVC) 16 | 17 | else() 18 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=native -O3 -Wall -Wsuggest-override -Werror=return-local-addr -Werror=multichar -Werror=enum-compare -Werror=return-type -Werror=conversion-null -Werror=parentheses -Werror=address -Werror=trigraphs -Werror=pointer-arith -Werror=write-strings -Werror=pessimizing-move -Wuninitialized -Wno-unknown-pragmas -Werror=switch -Werror=format -Werror=non-virtual-dtor -Werror=cast-qual -Wconversion -Wsign-conversion -Wsign-promo") 19 | # set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pg") 20 | # set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -pg") 21 | endif() 22 | 23 | # add the executable 24 | add_executable(b b.cpp) 25 | 26 | target_include_directories(b PRIVATE ../include) 27 | -------------------------------------------------------------------------------- /benchmark/b.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | 14 | static std::vector make_test_data(int n) { 15 | std::vector v; 16 | for (int i = 0; i < n; i++) v.push_back(static_cast(i)); 17 | 18 | auto rng = std::default_random_engine {}; 19 | std::shuffle(std::begin(v), std::end(v), rng); 20 | 21 | return v; 22 | } 23 | 24 | static double get_wall_time() { 25 | struct timeval time; 26 | if (gettimeofday(&time,NULL)){ 27 | // Handle error 28 | return 0; 29 | } 30 | return (double)time.tv_sec + (double)time.tv_usec * .000001; 31 | } 32 | 33 | int main() { 34 | std::map > results; 35 | 36 | int runs = 5; 37 | for (int run = 0; run < runs; run++) { 38 | std::cerr << "run " << run << "\n"; 39 | for (int n = 1000000; n <= 10000000; n += 500000) { 40 | std::cerr << " test " << n << "\n"; 41 | auto v = make_test_data(n); 42 | 43 | double t_a0, t_a1, t_a2; 44 | double t_b0, t_b1, t_b2; 45 | double t_c0, t_c1, t_c2; 46 | double sum_a = 0, sum_b = 0, sum_c = 0; 47 | 48 | { 49 | std::set S1; 50 | t_a0 = get_wall_time(); 51 | for (auto & a : v) { 52 | S1.insert(a); 53 | } 54 | t_a1 = get_wall_time(); 55 | for (auto & a : S1) { 56 | sum_a += a; 57 | } 58 | t_a2 = get_wall_time(); 59 | } 60 | 61 | { 62 | std::vector S2; 63 | t_b0 = get_wall_time(); 64 | for (auto & a : v) { 65 | S2.push_back(a); 66 | } 67 | std::sort(S2.begin(), S2.end()); 68 | t_b1 = get_wall_time(); 69 | for (auto & a : S2) { 70 | sum_b += a; 71 | } 72 | t_b2 = get_wall_time(); 73 | } 74 | 75 | { 76 | radix_cpp::set S3; 77 | t_c0 = get_wall_time(); 78 | for (auto & a : v) { 79 | S3.insert(a); 80 | } 81 | t_c1 = get_wall_time(); 82 | for (auto & a : S3) { 83 | sum_c += a; 84 | } 85 | t_c2 = get_wall_time(); 86 | } 87 | 88 | auto & r = results[n]; 89 | std::get<0>(r) += t_a1 - t_a0; 90 | std::get<1>(r) += t_a2 - t_a1; 91 | std::get<2>(r) += sum_a; 92 | 93 | std::get<3>(r) += t_b1 - t_b0; 94 | std::get<4>(r) += t_b2 - t_b1; 95 | std::get<5>(r) += sum_b; 96 | 97 | std::get<6>(r) += t_c1 - t_c0; 98 | std::get<7>(r) += t_c2 - t_c1; 99 | std::get<8>(r) += sum_c; 100 | } 101 | } 102 | 103 | for (auto & [ n, d ] : results) { 104 | double da0 = std::get<0>(d) / runs; 105 | double da1 = std::get<1>(d) / runs; 106 | double Sa = std::get<2>(d) / runs; 107 | 108 | double db0 = std::get<3>(d) / runs; 109 | double db1 = std::get<4>(d) / runs; 110 | double Sb = std::get<5>(d) / runs; 111 | 112 | double dc0 = std::get<6>(d) / runs; 113 | double dc1 = std::get<7>(d) / runs; 114 | double Sc = std::get<8>(d) / runs; 115 | 116 | std::cout << n << ";" << da0 << ";" << da1 << ";" << db0 << ";" << db1 << ";" << dc0 << ";" << dc1 << ";" << Sa << ";" << Sb << ";" << Sc << "\n"; 117 | } 118 | 119 | return 0; 120 | } 121 | -------------------------------------------------------------------------------- /include/radix_cpp.h: -------------------------------------------------------------------------------- 1 | #ifndef _RADIXCPP_H_ 2 | #define _RADIXCPP_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #ifdef DEBUG 11 | #include 12 | #endif 13 | 14 | namespace radix_cpp { 15 | // floating point numbers 16 | 17 | inline std::pair deconstruct(float key) { 18 | union { 19 | float f; 20 | uint32_t i; 21 | } u; 22 | u.f = key; 23 | if (u.i & 0x80000000) { 24 | u.i ^= 0xffffffff; 25 | } else { 26 | u.i ^= 0x80000000; 27 | } 28 | return std::pair(u.i & 0xff, u.i >> 8); 29 | } 30 | 31 | inline std::pair deconstruct(double key) noexcept { 32 | union { 33 | double f; 34 | uint64_t i; 35 | } u; 36 | u.f = key; 37 | if (u.i & 0x8000000000000000) { 38 | u.i ^= 0xffffffffffffffff; 39 | } else { 40 | u.i ^= 0x8000000000000000; 41 | } 42 | return std::pair(u.i & 0xff, u.i >> 8); 43 | } 44 | 45 | // signed integers 46 | 47 | inline std::pair deconstruct(int8_t key) { 48 | uint8_t i = static_cast(key) ^ 0x80; 49 | return std::pair(i & 0xff, i >> 8); 50 | } 51 | 52 | inline std::pair deconstruct(int16_t key) { 53 | uint16_t i = static_cast(key) ^ 0x8000; 54 | return std::pair(i & 0xff, i >> 8); 55 | } 56 | 57 | inline std::pair deconstruct(int32_t key) { 58 | uint32_t i = static_cast(key) ^ 0x80000000; 59 | return std::pair(i & 0xff, i >> 8); 60 | } 61 | 62 | inline std::pair deconstruct(int64_t key) { 63 | uint64_t i = static_cast(key) ^ 0x8000000000000000; 64 | return std::pair(i & 0xff, i >> 8); 65 | } 66 | 67 | // string 68 | 69 | inline std::string append(std::string key, size_t digit) { 70 | key += static_cast(static_cast(digit)); 71 | return key; 72 | } 73 | 74 | inline std::pair deconstruct(std::string key) noexcept { 75 | if (key.empty()) { 76 | return std::pair(0, std::move(key)); 77 | } else { 78 | auto ord = static_cast(key.back()); 79 | key.pop_back(); 80 | return std::pair(ord, std::move(key)); 81 | } 82 | } 83 | 84 | inline size_t keysize(const std::string & key) noexcept { 85 | return key.size(); 86 | } 87 | 88 | // uint8_t 89 | 90 | inline uint8_t append(uint8_t key, size_t digit) noexcept { 91 | return static_cast(digit); 92 | } 93 | 94 | inline std::pair deconstruct(uint8_t key) noexcept { 95 | return std::pair(key, 0); 96 | } 97 | 98 | // Fallback 99 | 100 | template 101 | T append(T key, size_t digit) noexcept { 102 | return static_cast((key << 8) | digit); 103 | } 104 | 105 | template 106 | std::pair deconstruct(T key) noexcept { 107 | return std::pair(key & 0xff, key >> 8); 108 | } 109 | 110 | template 111 | size_t keysize(T key) noexcept { 112 | return sizeof(key); 113 | } 114 | 115 | /* MurmurHash3 was written by Austin Appleby, and is placed in the public domain. 116 | The author(s) hereby disclaim copyright to the MurmurHash3 source code. 117 | */ 118 | 119 | template::type* = nullptr> 120 | inline T rotl(T x, int_fast8_t r) noexcept { 121 | return (x << r) | (x >> (32 - r)); 122 | } 123 | 124 | template::type* = nullptr> 125 | inline T rotl(T x, int_fast8_t r) noexcept { 126 | return (x << r) | (x >> (64 - r)); 127 | } 128 | 129 | template::type* = nullptr> 130 | inline T murmur3_mix_k1(T k1) noexcept { 131 | k1 *= 0xcc9e2d51; 132 | k1 = rotl(k1, 15); 133 | k1 *= 0x1b873593; 134 | return k1; 135 | } 136 | 137 | template::type* = nullptr> 138 | inline T murmur3_mix_k1(T k1) noexcept { 139 | k1 *= UINT64_C(0x87c37b91114253d5); 140 | k1 = rotl(k1, 31); 141 | k1 *= UINT64_C(0x4cf5ad432745937f); 142 | return k1; 143 | } 144 | 145 | template::type* = nullptr> 146 | inline T murmur3_mix_h1(T h1, T k1) noexcept { 147 | h1 ^= k1; 148 | h1 = rotl(h1, 13); 149 | h1 = h1 * 5 + 0xe6546b64; 150 | return h1; 151 | } 152 | 153 | template::type* = nullptr> 154 | inline T murmur3_mix_h1(T h1, T k1) noexcept { 155 | h1 ^= k1; 156 | h1 = rotl(h1, 27); 157 | h1 = h1 * 5 + 0x52dce729; 158 | return h1; 159 | } 160 | 161 | template::type* = nullptr> 162 | inline T murmur3_fmix(T h1) noexcept { 163 | h1 ^= h1 >> 16; 164 | h1 *= 0x85ebca6b; 165 | h1 ^= h1 >> 13; 166 | h1 *= 0xc2b2ae35; 167 | h1 ^= h1 >> 16; 168 | return h1; 169 | } 170 | 171 | template::type* = nullptr> 172 | inline T murmur3_fmix(T h1) noexcept { 173 | h1 ^= h1 >> 33; 174 | h1 *= UINT64_C(0xff51afd7ed558ccd); 175 | h1 ^= h1 >> 33; 176 | h1 *= UINT64_C(0xc4ceb9fe1a85ec53); 177 | h1 ^= h1 >> 33; 178 | return h1; 179 | } 180 | 181 | template 182 | class Table { 183 | public: 184 | static constexpr bool is_map = !std::is_void::value; 185 | static constexpr bool is_set = !is_map; 186 | static constexpr size_t bucket_count = 256; // bucket count for the ordered portion of the key 187 | static constexpr size_t min_load_factor100 = 15; 188 | static constexpr size_t max_load_factor100 = 60; 189 | 190 | using key_type = Key; 191 | using internal_key_type = decltype(deconstruct(Key{}).second); 192 | using mapped_type = T; 193 | using value_type = typename std::conditional>::type; 194 | using size_type = size_t; 195 | using Self = Table; 196 | 197 | private: 198 | struct Node { 199 | void set_payload(value_type * payload) { payload_ = payload; } 200 | value_type * get_payload() { return payload_; } 201 | const value_type * get_payload() const { return payload_; } 202 | bool is_assigned() const { return combined_ != 0; } 203 | bool is_tombstone() const { return payload_ == reinterpret_cast(1); } 204 | size_t get_depth_lsb() const { return (combined_ >> 8) & 0xff; } 205 | const internal_key_type & get_prefix_key() const { return prefix_key_; } 206 | size_t get_ordinal() const { return combined_ & 0xff; } 207 | size_t get_value_count() const { return combined_ >> 16; } 208 | 209 | void reset() { 210 | combined_ = 0; 211 | payload_ = 0; 212 | } 213 | 214 | void assign(size_t depth, internal_key_type prefix_key, size_t ordinal) { 215 | new (static_cast(&(prefix_key_))) internal_key_type(std::move(prefix_key)); 216 | combined_ = (1 << 16) | ((depth & 0xff) << 8) | ordinal; 217 | payload_ = nullptr; 218 | } 219 | 220 | void inc_value_count() { 221 | combined_ += 65536; 222 | } 223 | 224 | bool dec_value_count() { 225 | combined_ -= 65536; 226 | if (combined_ < 65536) { 227 | combined_ = 0; 228 | payload_ = reinterpret_cast(1); // mark as tombstone 229 | return true; 230 | } else { 231 | return false; 232 | } 233 | } 234 | 235 | bool equals(size_t depth, const internal_key_type & prefix_key, size_t ordinal) const { 236 | return (combined_ & 0xffff) == (((depth & 0xff) << 8) | ordinal) && prefix_key == prefix_key_; 237 | } 238 | 239 | private: 240 | uint64_t combined_; // from low to high, bits 1-8 = ordinal, 9-16 = lsb of depth, the rest = value count 241 | value_type * payload_; 242 | internal_key_type prefix_key_; 243 | }; 244 | 245 | public: 246 | 247 | template 248 | struct Iterator 249 | { 250 | using value_type = typename Self::value_type; 251 | using TablePtr = typename std::conditional const*, Table*>::type; 252 | using PayloadPtr = typename std::conditional::type; 253 | using iterator_category = std::forward_iterator_tag; 254 | using difference_type = std::ptrdiff_t; 255 | using reference = typename std::conditional::type; 256 | using pointer = typename std::conditional::type; 257 | 258 | // end iterator 259 | Iterator(TablePtr table) noexcept 260 | : table_(table), 261 | ptr_(nullptr), 262 | depth_(0), 263 | ordinal_(0), 264 | offset_(0), 265 | hash0_(0), 266 | hash_(0), 267 | prefix_key_() 268 | { } 269 | 270 | Iterator(TablePtr table, PayloadPtr ptr, size_t depth, internal_key_type prefix_key, size_t ordinal, size_t offset, size_t hash0, size_t hash) noexcept 271 | : table_(table), 272 | ptr_(ptr), 273 | depth_(depth), 274 | ordinal_(ordinal), 275 | offset_(offset), 276 | hash0_(hash0), 277 | hash_(hash), 278 | prefix_key_(std::move(prefix_key)) 279 | { } 280 | 281 | reference operator*() const noexcept { 282 | return *ptr_; 283 | } 284 | 285 | pointer operator->() noexcept { 286 | return ptr_; 287 | } 288 | 289 | Iterator& operator++() noexcept { 290 | if (!ptr_) { 291 | return *this; // already ended 292 | } 293 | 294 | // go to the next direct Node 295 | if (depth_ == 0) { 296 | // empty key 297 | depth_++; 298 | ordinal_ = offset_ = 0; 299 | hash0_ = calc_unordered_hash(depth_, prefix_key_); 300 | } else { 301 | auto node = repair_and_get_node(); 302 | if (node->get_value_count() > 1) { 303 | depth_++; 304 | prefix_key_ = append(std::move(prefix_key_), ordinal_); 305 | ordinal_ = 0; 306 | hash0_ = calc_unordered_hash(depth_, prefix_key_); 307 | } else { 308 | ordinal_++; 309 | } 310 | offset_ = 0; 311 | } 312 | 313 | // iterate until a final Node is found 314 | hash_ = calc_final_hash(hash0_, ordinal_); 315 | auto node = table_->read_node(hash_); 316 | auto nodes_start = table_->get_nodes_start(), nodes_end = table_->get_nodes_end(); 317 | 318 | while ( 1 ) { 319 | if (ordinal_ == bucket_count) { 320 | // we have run through the whole range => go down the tree 321 | if (depth_ <= 1) { 322 | clear(); // become an end iterator 323 | return *this; 324 | } else { 325 | auto [ parent_ordinal, parent_prefix_key ] = deconstruct(std::move(prefix_key_)); 326 | depth_--; 327 | prefix_key_ = std::move(parent_prefix_key); 328 | ordinal_ = parent_ordinal + 1; 329 | offset_ = 0; 330 | hash0_ = calc_unordered_hash(depth_, prefix_key_); 331 | hash_ = calc_final_hash(hash0_, ordinal_); 332 | node = table_->read_node(hash_); 333 | } 334 | } else if (!node->is_assigned()) { 335 | if (node->is_tombstone()) { 336 | // collision 337 | if (++node == nodes_end) node = nodes_start; 338 | offset_++; 339 | } else { 340 | // Node is not assigned 341 | ordinal_++; 342 | offset_ = 0; 343 | hash_ = calc_final_hash(hash0_, ordinal_); 344 | node = table_->read_node(hash_); 345 | } 346 | } else if (!node->equals(depth_, prefix_key_, ordinal_)) { 347 | // collision 348 | if (++node == nodes_end) node = nodes_start; 349 | offset_++; 350 | } else if (node->get_payload()) { 351 | // a final Node was found 352 | set_ptr(node->get_payload()); 353 | return *this; 354 | } else { 355 | // non-final node => go up the tree 356 | depth_++; 357 | prefix_key_ = append(std::move(prefix_key_), ordinal_); 358 | ordinal_ = offset_ = 0; 359 | offset_ = 0; 360 | hash0_ = calc_unordered_hash(depth_, prefix_key_); 361 | hash_ = calc_final_hash(hash0_, ordinal_); 362 | node = table_->read_node(hash_); 363 | } 364 | } 365 | } 366 | 367 | Iterator operator++(int) noexcept { 368 | Iterator tmp = *this; 369 | ++(*this); 370 | return tmp; 371 | } 372 | 373 | template 374 | bool operator== (const Iterator& o) const noexcept { 375 | return ptr_ == o.ptr_; 376 | } 377 | 378 | template 379 | bool operator!= (const Iterator& o) const noexcept { 380 | return ptr_ != o.ptr_; 381 | } 382 | 383 | void fast_forward() noexcept { 384 | if (depth_ == 0) { 385 | hash0_ = calc_unordered_hash(depth_, prefix_key_); 386 | hash_ = calc_final_hash(hash0_, ordinal_); 387 | 388 | // first look for 0-length node (depth = ordinal = 0) 389 | while (1) { 390 | auto node = table_->read_node(hash_, offset_); 391 | if (node->is_assigned()) { 392 | if (!node->equals(depth_, prefix_key_, 0)) { 393 | // collision 394 | offset_++; 395 | continue; 396 | } else if (node->get_payload()) { 397 | set_ptr(node->get_payload()); 398 | return; 399 | } 400 | } else if (node->is_tombstone()) { 401 | // collision 402 | offset_++; 403 | } 404 | break; 405 | } 406 | 407 | depth_ = 1; 408 | offset_ = 0; 409 | hash0_ = calc_unordered_hash(depth_, prefix_key_); 410 | hash_ = calc_final_hash(hash0_, ordinal_); 411 | } 412 | 413 | while ( 1 ) { 414 | if (ordinal_ == bucket_count) { 415 | if (depth_ <= 1) { 416 | clear(); // become an end iterator 417 | break; 418 | } else { 419 | depth_--; 420 | auto [ parent_ordinal, parent_prefix_key ] = deconstruct(std::move(prefix_key_)); 421 | prefix_key_ = std::move(parent_prefix_key); 422 | ordinal_ = parent_ordinal + 1; 423 | offset_ = 0; 424 | ptr_ = nullptr; 425 | hash0_ = calc_unordered_hash(depth_, prefix_key_); 426 | hash_ = calc_final_hash(hash0_, ordinal_); 427 | } 428 | } else { 429 | auto node = table_->read_node(hash_, offset_); 430 | if (!node->is_assigned()) { 431 | if (node->is_tombstone()) { 432 | // collision 433 | offset_++; 434 | } else { 435 | // unassigned 436 | ordinal_++; 437 | offset_ = 0; 438 | hash_ = calc_final_hash(hash0_, ordinal_); 439 | } 440 | } else if (!node->equals(depth_, prefix_key_, ordinal_)) { 441 | // collision 442 | offset_++; 443 | } else if (node->get_payload()) { 444 | set_ptr(node->get_payload()); 445 | return; 446 | } else { 447 | depth_++; 448 | prefix_key_ = append(std::move(prefix_key_), ordinal_); 449 | ordinal_ = 0; 450 | hash0_ = calc_unordered_hash(depth_, prefix_key_); 451 | hash_ = calc_final_hash(hash0_, ordinal_); 452 | } 453 | } 454 | } 455 | } 456 | 457 | void down() { 458 | if (depth_ <= 1) { 459 | clear(); // become an end iterator 460 | } else { 461 | depth_--; 462 | auto [ parent_ordinal, parent_prefix_key ] = deconstruct(std::move(prefix_key_)); 463 | prefix_key_ = std::move(parent_prefix_key); 464 | ordinal_ = parent_ordinal; 465 | offset_ = 0; 466 | ptr_ = nullptr; 467 | hash0_ = calc_unordered_hash(depth_, prefix_key_); 468 | hash_ = calc_final_hash(hash0_, ordinal_); 469 | while ( 1 ) { 470 | auto node = table_->read_node(hash_, offset_); 471 | if (!node->is_assigned()) { 472 | if (node->is_tombstone()) { 473 | offset_++; 474 | } else { 475 | #ifdef DEBUG 476 | std::cerr << "down() failed\n"; 477 | #endif 478 | abort(); 479 | } 480 | } else if (node->equals(depth_, prefix_key_, ordinal_)) { 481 | break; 482 | } else { 483 | offset_++; 484 | } 485 | } 486 | } 487 | } 488 | 489 | Node * repair_and_get_node() { 490 | auto node0 = table_->read_node(hash_, offset_); 491 | if (ptr_ == node0->get_payload()) return node0; 492 | auto node = table_->read_node(hash_); 493 | offset_ = 0; 494 | while ( 1 ) { 495 | auto node = table_->read_node(hash_, offset_); 496 | if (!node->is_assigned()) { 497 | if (!node->is_tombstone()) { 498 | #ifdef DEBUG 499 | std::cerr << "repair failed\n"; 500 | #endif 501 | abort(); 502 | } 503 | } else if (ptr_ == node->get_payload()) { 504 | return node; 505 | } 506 | offset_++; 507 | } 508 | return node; 509 | } 510 | 511 | size_t get_depth() const noexcept { return depth_; } 512 | const internal_key_type & get_prefix_key() const noexcept { return prefix_key_; } 513 | size_t get_ordinal() const noexcept { return ordinal_; } 514 | size_t get_offset() const noexcept { return offset_; } 515 | size_t get_hash() const noexcept { return hash_; } 516 | 517 | void set_ptr(PayloadPtr ptr) { ptr_ = ptr; } 518 | 519 | private: 520 | void clear() { 521 | ptr_ = nullptr; 522 | depth_ = 0; 523 | prefix_key_ = internal_key_type{}; 524 | ordinal_ = 0; 525 | offset_ = 0; 526 | hash0_ = 0; 527 | hash_ = 0; 528 | } 529 | 530 | TablePtr table_; 531 | PayloadPtr ptr_; 532 | 533 | // * cached values 534 | // they are all obtainable from ptr_, but it's faster to cache them 535 | // only temporarily can an iterator might point to a non-final Node (a node that has no ptr_) 536 | size_t depth_, ordinal_, offset_, hash0_, hash_; 537 | internal_key_type prefix_key_; 538 | }; 539 | 540 | using iterator = Iterator; 541 | using const_iterator = Iterator; 542 | 543 | Table() noexcept { } 544 | Table(Table && other) noexcept 545 | : num_entries_(std::exchange(other.num_entries_, 0)), 546 | num_final_entries_(std::exchange(other.num_final_entries_, 0)), 547 | num_inserts_(std::exchange(other.num_inserts_, 0)), 548 | num_insert_collisions_(std::exchange(other.num_insert_collisions_, 0)), 549 | table_size_(std::exchange(other.table_size_, 0)), 550 | table_mask_(std::exchange(other.table_mask_, 0)), 551 | nodes_(std::exchange(other.nodes_, nullptr)), 552 | arena_(std::move(other.arena_)) { } 553 | 554 | Table & operator=(Table && other) noexcept { 555 | std::swap(num_entries_, other.num_entries); 556 | std::swap(num_final_entries_, other.num_final_entries_); 557 | std::swap(num_inserts_, other.num_inserts_); 558 | std::swap(num_insert_collisions_, other.num_insert_collisons_); 559 | std::swap(table_size_, other.table_size_); 560 | std::swap(table_mask_, other.table_mask_); 561 | std::swap(nodes_, other.nodes_); 562 | std::swap(arena_, other.arena_); 563 | return *this; 564 | } 565 | 566 | Table(const Table & other) = delete; 567 | Table& operator=(const Table & other) = delete; 568 | 569 | ~Table() noexcept { 570 | clear(); 571 | } 572 | 573 | void clear() noexcept { 574 | for (size_t i = 0; i < table_size_; i++) { 575 | auto & node = nodes_[i]; 576 | if (node.is_assigned()) { 577 | node.get_prefix_key().~internal_key_type(); 578 | if (node.get_payload()) { 579 | node.get_payload()->~value_type(); 580 | } 581 | } 582 | } 583 | std::free(nodes_); 584 | num_entries_ = num_final_entries_ = num_inserts_ = num_insert_collisions_ = table_size_ = table_mask_ = 0; 585 | nodes_ = nullptr; 586 | arena_.clear(); 587 | } 588 | 589 | iterator find(const key_type & key) noexcept { 590 | if (!table_size_) return end(); 591 | auto [ ordinal, prefix_key ] = deconstruct(key); 592 | auto depth = keysize(key); 593 | auto hash0 = calc_unordered_hash(depth, prefix_key); 594 | auto hash = calc_final_hash(hash0, ordinal); 595 | auto node_initial = read_node(hash); 596 | auto nodes_start = get_nodes_start(), nodes_end = get_nodes_end(); 597 | 598 | auto node = node_initial; 599 | while ( 1 ) { 600 | if (!node->is_assigned()) { 601 | if (node->is_tombstone()) { 602 | // collision 603 | if (++node == nodes_end) node = nodes_start; 604 | } else { 605 | break; // not found 606 | } 607 | } else if (!node->equals(depth, prefix_key, ordinal)) { 608 | // collision 609 | if (++node == nodes_end) node = nodes_start; 610 | } else if (node->get_payload()) { 611 | return iterator(this, node->get_payload(), depth, prefix_key, ordinal, static_cast(node - node_initial), hash0, hash); 612 | } else { 613 | break; // not final / wrong key 614 | } 615 | } 616 | return end(); 617 | } 618 | 619 | const_iterator find(const key_type & key) const noexcept { 620 | if (!table_size_) return cend(); 621 | auto [ ordinal, prefix_key ] = deconstruct(key); 622 | auto depth = keysize(prefix_key); 623 | auto hash0 = calc_unordered_hash(depth, prefix_key); 624 | auto hash = calc_final_hash(hash0, ordinal); 625 | auto node_initial = read_node(hash); 626 | auto nodes_start = get_nodes_start(), nodes_end = get_nodes_end(); 627 | 628 | auto node = node_initial; 629 | while ( 1 ) { 630 | if (!node->is_assigned()) { 631 | if (node->is_tombstone()) { 632 | // collision 633 | if (++node == nodes_end) node = nodes_start; 634 | } else { 635 | break; // not found 636 | } 637 | } else if (!node->equals(depth, prefix_key, ordinal)) { 638 | // collision 639 | if (++node == nodes_end) node = nodes_start; 640 | } else if (node->get_payload()) { 641 | return const_iterator(this, node->get_payload(), depth, prefix_key, ordinal, static_cast(node - node_initial), hash0, hash); 642 | } else { 643 | break; // not final / wrong key 644 | } 645 | } 646 | return cend(); 647 | } 648 | 649 | iterator upper_bound(const key_type& key) { 650 | if (!table_size_) return end(); 651 | auto [ ordinal, prefix_key ] = deconstruct(key); 652 | auto depth = keysize(key); 653 | auto hash0 = calc_unordered_hash(depth, prefix_key); 654 | auto hash = calc_final_hash(hash0, ordinal); 655 | auto node_initial = read_node(hash); 656 | auto nodes_start = get_nodes_start(), nodes_end = get_nodes_end(); 657 | 658 | auto node = node_initial; 659 | while ( 1 ) { 660 | if (node->is_tombstone() || (node->is_assigned() && !node->equals(depth, prefix_key, ordinal))) { 661 | // collision 662 | if (++node == nodes_end) node = nodes_start; 663 | } else { 664 | iterator it(this, node->get_payload(), depth, prefix_key, ordinal, static_cast(node - node_initial), hash0, hash); 665 | if (node->is_assigned() && node->get_payload()) { 666 | it++; 667 | } else { 668 | it.fast_forward(); 669 | } 670 | return it; 671 | } 672 | } 673 | } 674 | 675 | size_t count(const key_type & key) const noexcept { 676 | return find(key) == cend() ? 0 : 1; 677 | } 678 | 679 | template 680 | typename std::enable_if::value, std::pair>::type insert_or_assign(const Key& k, Q && obj) { 681 | auto [ node, it ] = create_nodes_for_key(k); 682 | bool is_new = true; 683 | if (!node->get_payload()) { 684 | node->set_payload(arena_.alloc()); 685 | it.set_ptr(node->get_payload()); 686 | new (static_cast(node->get_payload())) value_type(k, std::move(obj)); 687 | num_final_entries_++; 688 | } else { 689 | *(node->get_payload()) = value_type(k, std::move(obj)); 690 | is_new = false; 691 | } 692 | return std::make_pair(it, is_new); 693 | } 694 | 695 | template 696 | std::pair emplace(Args&&... args) { 697 | value_type vt{std::forward(args)...}; 698 | auto [ node, it ] = create_nodes_for_key(getFirstConst(vt)); 699 | bool is_new = true; 700 | if (!node->get_payload()) { 701 | node->set_payload(arena_.alloc()); 702 | it.set_ptr(node->get_payload()); 703 | new (static_cast(node->get_payload())) value_type(std::move(vt)); 704 | num_final_entries_++; 705 | } else { 706 | is_new = false; 707 | } 708 | return std::make_pair(it, is_new); 709 | } 710 | 711 | std::pair insert(const value_type& keyval) { 712 | return emplace(keyval); 713 | } 714 | 715 | std::pair insert(value_type&& keyval) { 716 | return emplace(std::move(keyval)); 717 | } 718 | 719 | iterator insert(const_iterator hint, const value_type& keyval) { 720 | (void)hint; 721 | return emplace(keyval).first; 722 | } 723 | 724 | template 725 | void insert(InputIt first, InputIt last) { 726 | while (first != last) { 727 | insert(*first); 728 | first++; 729 | } 730 | } 731 | 732 | template 733 | typename std::enable_if::value, Q&>::type operator[](const key_type& key) noexcept { 734 | auto it = find(key); 735 | if (it != end()) { 736 | return it->second; 737 | } else { 738 | auto [ it, is_new ] = insert(std::pair(key, mapped_type())); 739 | return it->second; 740 | } 741 | } 742 | 743 | template 744 | typename std::enable_if::value, Q&>::type at(const key_type& key) { 745 | auto it = find(key); 746 | if (it != end()) { 747 | return it->second; 748 | } else { 749 | throw std::out_of_range("key not found"); 750 | } 751 | } 752 | 753 | iterator erase(iterator pos) { 754 | auto node = pos.repair_and_get_node(); 755 | if (!node->is_assigned() || !node->get_payload()) { 756 | #ifdef DEBUG 757 | std::cerr << "invalid parameter to erase()\n"; 758 | #endif 759 | abort(); 760 | } 761 | auto next_pos = pos; 762 | ++next_pos; 763 | 764 | node->get_payload()->~value_type(); 765 | arena_.dealloc(node->get_payload()); 766 | node->set_payload(nullptr); 767 | num_final_entries_--; 768 | 769 | if (node->dec_value_count()) { 770 | node->get_prefix_key().~internal_key_type(); 771 | num_entries_--; 772 | inserts_remaining_++; 773 | } 774 | 775 | pos.down(); 776 | while ( pos.get_depth() ) { 777 | auto node = read_node(pos.get_hash(), pos.get_offset()); 778 | if (node->dec_value_count()) { 779 | node->get_prefix_key().~internal_key_type(); 780 | num_entries_--; 781 | inserts_remaining_++; 782 | } 783 | pos.down(); 784 | } 785 | 786 | if (table_size_ > bucket_count && get_load_factor() < min_load_factor100) { // Check the load factor 787 | resize(table_size_ >> 1); 788 | } 789 | 790 | return next_pos; 791 | } 792 | 793 | iterator erase(iterator first, iterator last) { 794 | while ( first != last ) { 795 | first = erase(first); 796 | } 797 | return first; 798 | } 799 | 800 | size_t erase(const key_type & key) { 801 | auto it = find(key); 802 | if (it != end()) { 803 | erase(it); 804 | return 1; 805 | } else { 806 | return 0; 807 | } 808 | } 809 | 810 | iterator begin() noexcept { 811 | if (size()) { 812 | iterator it(this); 813 | it.fast_forward(); 814 | return it; 815 | } else { 816 | return end(); 817 | } 818 | } 819 | iterator end() noexcept { 820 | // iterator is by default and end iterator (ptr is nil) 821 | return iterator(this); 822 | } 823 | 824 | const_iterator cbegin() noexcept { 825 | if (size()) { 826 | const_iterator it(this); 827 | it.fast_forward(); 828 | return it; 829 | } else { 830 | return end(); 831 | } 832 | } 833 | const_iterator cend() const noexcept { 834 | // iterator is by default and end iterator (the depth is zero) 835 | return const_iterator(this); 836 | } 837 | 838 | bool empty() const noexcept { return num_final_entries_ == 0; } 839 | size_t size() const noexcept { return num_final_entries_; } 840 | size_t num_inserts() const noexcept { return num_inserts_; } 841 | size_t num_insert_collisions() const noexcept { return num_insert_collisions_; } 842 | 843 | private: 844 | class Arena { 845 | private: 846 | static constexpr size_t page_size = 4096; 847 | 848 | public: 849 | Arena() { } 850 | Arena(Arena && other) noexcept 851 | : n_(std::exchange(other.n_, 0)), 852 | pages_(std::move(other.pages_)) { } 853 | ~Arena() noexcept { 854 | clear(); 855 | } 856 | 857 | Arena & operator=(Arena && other) noexcept { 858 | std::swap(n_, other.n); 859 | std::swap(pages_, other.pages_); 860 | return *this; 861 | } 862 | 863 | Arena(const Arena & other) = delete; 864 | Arena& operator=(const Arena & other) = delete; 865 | 866 | value_type * alloc() { 867 | if (!free_list_.empty()) { 868 | value_type * ptr = free_list_.back(); 869 | free_list_.pop_back(); 870 | return ptr; 871 | } else { 872 | if (pages_.empty() || n_ == page_size) { 873 | auto p = reinterpret_cast(std::malloc(page_size * sizeof(value_type))); 874 | if (!p) throw std::bad_alloc(); 875 | pages_.push_back(p); 876 | n_ = 0; 877 | } 878 | return pages_.back() + n_++; 879 | } 880 | } 881 | 882 | void dealloc(value_type * ptr) { 883 | free_list_.push_back(ptr); 884 | } 885 | 886 | void clear() noexcept { 887 | for (size_t i = 0; i < pages_.size(); i++) { 888 | std::free(pages_[i]); 889 | } 890 | n_ = 0; 891 | pages_.clear(); 892 | } 893 | 894 | private: 895 | size_t n_ = 0; 896 | std::vector pages_; 897 | std::vector free_list_; 898 | }; 899 | 900 | size_t get_load_factor() const noexcept { return 100 * num_entries_ / table_size_; } 901 | size_t get_inserts_until_rehash() const noexcept { 902 | size_t max_entries = max_load_factor100 * table_size_ / 100; 903 | if (num_entries_ > max_entries) return 0; 904 | else return max_entries - num_entries_; 905 | } 906 | 907 | std::tuple create_node(size_t depth, const internal_key_type & prefix_key, size_t ordinal) { 908 | if (!inserts_remaining_) { 909 | resize(table_size_ * 2); 910 | } 911 | 912 | auto hash0 = calc_unordered_hash(depth, prefix_key); 913 | auto hash = calc_final_hash(hash0, ordinal); 914 | auto node_initial = read_node(hash); 915 | auto nodes_start = get_nodes_start(), nodes_end = get_nodes_end(); 916 | 917 | auto node = node_initial; 918 | while ( 1 ) { 919 | if (!node->is_assigned()) { // unassigned or tombstone 920 | node->assign(depth, prefix_key, ordinal); 921 | num_entries_++; 922 | inserts_remaining_--; 923 | } else if (!node->equals(depth, prefix_key, ordinal)) { 924 | // collision 925 | if (++node == nodes_end) node = nodes_start; 926 | num_insert_collisions_++; 927 | continue; 928 | } else { 929 | node->inc_value_count(); 930 | } 931 | break; 932 | } 933 | return std::tuple(node, hash0, hash, static_cast(node - node_initial)); 934 | } 935 | 936 | std::pair create_nodes_for_key(key_type key0) { 937 | if (!nodes_) { 938 | init(bucket_count); 939 | } 940 | auto n = keysize(key0); 941 | auto [ ordinal, prefix_key ] = deconstruct(std::move(key0)); 942 | 943 | num_inserts_++; 944 | 945 | auto depth = n; 946 | 947 | auto first_prefix_key = prefix_key; 948 | auto first_ordinal = ordinal; 949 | 950 | // first insert the tail from least significant digit to most significant 951 | for ( size_t i = 1; i < n; i++) { 952 | auto [ next_ordinal, next_prefix_key ] = deconstruct(std::move(prefix_key)); 953 | ordinal = next_ordinal; 954 | prefix_key = std::move(next_prefix_key); 955 | depth--; 956 | 957 | create_node(depth, prefix_key, ordinal); 958 | } 959 | 960 | // then insert the head 961 | auto [ node, hash0, hash, offset ] = create_node(n, first_prefix_key, first_ordinal); 962 | auto it = iterator(this, node->get_payload(), n, std::move(first_prefix_key), first_ordinal, offset, hash0, hash); 963 | return std::pair(node, it); 964 | } 965 | // getFirstConst returns the key from value_type for either set or map 966 | // This version is for sets, where value_type == key_type 967 | static key_type const& getFirstConst(key_type const& k) noexcept { 968 | return k; 969 | } 970 | // this one is for maps 971 | template 972 | static typename std::enable_if::value, key_type const&>::type 973 | getFirstConst(value_type const& vt) noexcept { 974 | return vt.first; 975 | } 976 | 977 | // mk_value_from_key creates a value_type from key_type. 978 | // This version is for sets, where value_type == key_type 979 | static value_type mk_value_from_key(value_type k) noexcept { 980 | return k; 981 | } 982 | // this one is for maps 983 | template 984 | static typename std::enable_if::value, value_type>::type 985 | mk_value_from_key(key_type const& k) noexcept { 986 | return std::make_pair(k, mapped_type()); 987 | } 988 | 989 | // size must be a power of two 990 | void init(size_t s) { 991 | if (nodes_) std::free(nodes_); 992 | table_size_ = inserts_remaining_ = s; 993 | table_mask_ = s - 1; 994 | nodes_ = alloc_nodes(s); 995 | } 996 | 997 | static Node * alloc_nodes(size_t s) { 998 | auto nodes = reinterpret_cast(std::malloc(s * sizeof(Node))); 999 | if (!nodes) throw std::bad_alloc(); 1000 | for (size_t i = 0; i < s; i++) { 1001 | nodes[i].reset(); 1002 | } 1003 | return nodes; 1004 | } 1005 | 1006 | void resize(size_t new_size) { 1007 | auto new_mask = new_size - 1; 1008 | auto new_nodes = alloc_nodes(new_size); 1009 | auto new_nodes_end = new_nodes + new_size; 1010 | 1011 | auto node = nodes_; 1012 | auto end = nodes_ + table_size_; 1013 | for (; node != end; node++) { 1014 | if (node->is_assigned()) { 1015 | // get the least significant byte of depth from node, and the other bytes from the prefix key 1016 | size_t depth = ((keysize(node->get_prefix_key()) + 1) & ~UINT64_C(0xff)) | node->get_depth_lsb(); 1017 | auto hash0 = calc_unordered_hash(depth, node->get_prefix_key()); 1018 | auto hash = calc_final_hash(hash0, node->get_ordinal()); 1019 | auto new_node = new_nodes + (hash & new_mask); 1020 | 1021 | while ( 1 ) { 1022 | if (new_node->is_assigned()) { 1023 | if (++new_node == new_nodes_end) new_node = new_nodes; 1024 | num_insert_collisions_++; 1025 | } else { 1026 | new (static_cast(new_node)) Node(std::move(*node)); 1027 | node->~Node(); 1028 | break; 1029 | } 1030 | } 1031 | } 1032 | } 1033 | 1034 | std::free(nodes_); 1035 | nodes_ = new_nodes; 1036 | table_size_ = new_size; 1037 | table_mask_ = new_mask; 1038 | inserts_remaining_ = get_inserts_until_rehash(); 1039 | } 1040 | 1041 | Node * read_node(size_t h, size_t offset) noexcept {return nodes_ + ((h + offset) & table_mask_); } 1042 | const Node * read_node(size_t h, size_t offset) const noexcept { return nodes_ + (h + offset) & table_mask_; } 1043 | 1044 | Node * read_node(size_t h) noexcept { return nodes_ + (h & table_mask_); } 1045 | const Node * read_node(size_t h) const noexcept { return nodes_ + (h & table_mask_); } 1046 | 1047 | const Node * get_nodes_start() const noexcept { return nodes_; } 1048 | const Node * get_nodes_end() const noexcept { return nodes_ + table_size_; } 1049 | Node * get_nodes_start() noexcept { return nodes_; } 1050 | Node * get_nodes_end() noexcept { return nodes_ + table_size_; } 1051 | 1052 | // hash calculation functions use Murmur3 to calculate hash for a Node. 1053 | // Murmur3 operations are specialized for both 32 bit and 64 bit size_t 1054 | static inline size_t calc_unordered_hash(size_t depth, const internal_key_type & prefix_key) noexcept { 1055 | auto k1 = murmur3_mix_k1(std::hash{}(prefix_key)); 1056 | return murmur3_mix_h1(depth, k1); 1057 | } 1058 | 1059 | static inline size_t calc_final_hash(size_t h1, size_t ordinal) noexcept { 1060 | auto k1 = murmur3_mix_k1(ordinal); 1061 | h1 = murmur3_mix_h1(h1, k1); 1062 | return murmur3_fmix(h1); 1063 | } 1064 | 1065 | size_t num_entries_ = 0, num_final_entries_ = 0; 1066 | size_t num_inserts_ = 0, num_insert_collisions_ = 0; 1067 | size_t table_size_ = 0, table_mask_ = 0; 1068 | size_t inserts_remaining_ = 0; 1069 | Node* nodes_ = nullptr; 1070 | Arena arena_; 1071 | }; 1072 | 1073 | template 1074 | using set = Table; 1075 | 1076 | template 1077 | using map = Table; 1078 | }; 1079 | 1080 | #endif 1081 | -------------------------------------------------------------------------------- /tests/test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "radix_cpp.h" 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | TEST_CASE( "simple integer sets can be created", "[int_set]" ) { 10 | radix_cpp::set S0; 11 | S0.insert(0); 12 | S0.insert(255); 13 | auto it0 = S0.begin(); 14 | REQUIRE(*it0++ == 0); 15 | REQUIRE(*it0++ == 255); 16 | REQUIRE(it0 == S0.end()); 17 | 18 | radix_cpp::set S1; 19 | S1.insert(0); 20 | S1.insert(1); 21 | S1.insert(2); 22 | auto it1 = S1.begin(); 23 | REQUIRE(*it1++ == 0); 24 | REQUIRE(*it1++ == 1); 25 | REQUIRE(*it1++ == 2); 26 | REQUIRE(it1 == S1.end()); 27 | 28 | radix_cpp::set S2; 29 | S2.insert(1000000000); 30 | S2.insert(100000000); 31 | S2.insert(10000000); 32 | S2.insert(1000000); 33 | S2.insert(100000); 34 | S2.insert(10000); 35 | S2.insert(1000); 36 | S2.insert(100); 37 | S2.insert(10); 38 | S2.insert(1); 39 | S2.insert(0); 40 | 41 | auto it2 = S2.begin(); 42 | REQUIRE(*it2++ == 0); 43 | REQUIRE(*it2++ == 1); 44 | REQUIRE(*it2++ == 10); 45 | REQUIRE(*it2++ == 100); 46 | REQUIRE(*it2++ == 1000); 47 | REQUIRE(*it2++ == 10000); 48 | REQUIRE(*it2++ == 100000); 49 | REQUIRE(*it2++ == 1000000); 50 | REQUIRE(*it2++ == 10000000); 51 | REQUIRE(*it2++ == 100000000); 52 | REQUIRE(*it2++ == 1000000000); 53 | REQUIRE(it2 == S2.end()); 54 | } 55 | 56 | TEST_CASE( "large integer sets work and iterators are stable", "[large_set]") { 57 | radix_cpp::set S; 58 | S.insert(1000); 59 | auto it0 = S.find(1000); 60 | 61 | constexpr uint32_t n_vals = 10000000; 62 | 63 | for (uint32_t i = 0; i < n_vals; i++) { 64 | S.insert(i); 65 | } 66 | REQUIRE(*it0 == 1000); 67 | 68 | #if 0 69 | std::cerr << "collisions per insert: " 70 | << (static_cast(S.num_insert_collisions()) / static_cast(S.num_inserts())) 71 | << "\n"; 72 | #endif 73 | 74 | uint32_t next_consecutive_element = 0; 75 | for (auto & v : S) { 76 | if (v != next_consecutive_element) { 77 | break; 78 | } 79 | next_consecutive_element = v + 1; 80 | } 81 | 82 | REQUIRE(next_consecutive_element == n_vals); 83 | } 84 | 85 | TEST_CASE( "simple string sets can be created", "[string_set]" ) { 86 | radix_cpp::set S; 87 | S.insert("a"); 88 | S.insert("bc"); 89 | S.insert("def"); 90 | auto it = S.begin(); 91 | REQUIRE(*it++ == "a"); 92 | REQUIRE(*it++ == "bc"); 93 | REQUIRE(*it++ == "def"); 94 | REQUIRE(it == S.end()); 95 | } 96 | 97 | TEST_CASE( "empty string can be added to set and found", "[empty_string_set]" ) { 98 | radix_cpp::set S; 99 | S.insert("abc"); 100 | S.insert(""); 101 | S.insert("gamma"); 102 | auto it = S.begin(); 103 | REQUIRE(*it++ == ""); 104 | REQUIRE(*it++ == "abc"); 105 | REQUIRE(*it++ == "gamma"); 106 | REQUIRE(it == S.end()); 107 | 108 | it = S.find(""); 109 | REQUIRE(*it == ""); 110 | } 111 | 112 | TEST_CASE( "shared prefixes work with strings", "[string_set_shared_prefix]" ) { 113 | radix_cpp::set S; 114 | S.insert("@"); 115 | S.insert("abc"); 116 | S.insert("ab"); 117 | S.insert("a"); 118 | S.insert(""); 119 | REQUIRE(S.size() == 5); 120 | auto it = S.begin(); 121 | REQUIRE(*it++ == ""); 122 | REQUIRE(*it++ == "@"); 123 | REQUIRE(*it++ == "a"); 124 | REQUIRE(*it++ == "ab"); 125 | REQUIRE(*it++ == "abc"); 126 | REQUIRE(it == S.end()); 127 | } 128 | 129 | TEST_CASE( "float set", "[float_set]") { 130 | radix_cpp::set S; 131 | S.insert(std::numeric_limits::quiet_NaN()); 132 | S.insert(std::numeric_limits::infinity()); 133 | S.insert(0.0f); 134 | S.insert(-0.0f); 135 | S.insert(1.1f); 136 | S.insert(1000.0f); 137 | S.insert(-1000.0f); 138 | S.insert(4.2f); 139 | S.insert(-std::numeric_limits::infinity()); 140 | S.insert(30000000.f); 141 | S.insert(0); 142 | auto it = S.begin(); 143 | REQUIRE(*it++ == -std::numeric_limits::infinity()); 144 | REQUIRE(*it++ == -1000.0f); 145 | REQUIRE(*it++ == -0.0f); 146 | REQUIRE(*it++ == 0.0f); 147 | REQUIRE(*it++ == 1.1f); 148 | REQUIRE(*it++ == 4.2f); 149 | REQUIRE(*it++ == 1000.0f); 150 | REQUIRE(*it++ == 30000000.0f); 151 | REQUIRE(*it++ == std::numeric_limits::infinity()); 152 | REQUIRE(std::isnan(*it++)); 153 | REQUIRE(it == S.end()); 154 | } 155 | 156 | TEST_CASE( "empty set or map iteration doesn't fail", "[empty]") { 157 | radix_cpp::set S; 158 | radix_cpp::map M; 159 | auto it1 = S.begin(); 160 | auto it2 = M.begin(); 161 | REQUIRE(it1 == S.end()); 162 | REQUIRE(it2 == M.end()); 163 | } 164 | 165 | TEST_CASE( "the iterator returned by insert is valid", "[inser_iterator]" ) { 166 | radix_cpp::set S; 167 | S.insert(1000); 168 | S.insert(3000); 169 | auto [ it, is_new ] = S.insert(2000); 170 | REQUIRE(*it++ == 2000); 171 | REQUIRE(*it++ == 3000); 172 | } 173 | 174 | TEST_CASE( "empty map find", "[empty_find]" ) { 175 | radix_cpp::map M; 176 | auto it = M.find("Hello"); 177 | REQUIRE(it == M.end()); 178 | } 179 | 180 | TEST_CASE( "updating iterators", "[iter_update]" ) { 181 | radix_cpp::map M; 182 | M["A"] = 1; 183 | auto it = M.find("A"); 184 | REQUIRE(it->second == 1); 185 | it->second = 2; 186 | REQUIRE(M["A"] == 2); 187 | } 188 | 189 | TEST_CASE( "updating inserted", "[inserted_update]" ) { 190 | radix_cpp::map M; 191 | auto [ it, is_new ] = M.insert(std::pair("Hello", 10)); 192 | REQUIRE(M["Hello"] == 10); 193 | it->second = 11; 194 | REQUIRE(M["Hello"] == 11); 195 | } 196 | 197 | TEST_CASE( "map assignment", "[assignment]" ) { 198 | radix_cpp::map M; 199 | M["Hello"] = true; 200 | REQUIRE(M["Hello"] == true); 201 | REQUIRE(M.size() == 1); 202 | } 203 | 204 | TEST_CASE( "move construction and assign", "[move_construct_assign]" ) { 205 | radix_cpp::set S0; 206 | S0.insert("AB"); 207 | S0.insert("ABC"); 208 | REQUIRE(S0.size() == 2); 209 | auto S1(std::move(S0)); 210 | auto S2 = std::move(S1); 211 | REQUIRE(S0.size() == 0); 212 | REQUIRE(S1.size() == 0); 213 | REQUIRE(S2.size() == 2); 214 | } 215 | 216 | TEST_CASE( "insert doesn't overwrite", "[insert_no_overwrite]" ) { 217 | radix_cpp::map M; 218 | auto [ it1, inserted1 ] = M.insert(std::pair("abc", 1)); 219 | auto [ it2, inserted2 ] = M.insert(std::pair("ab", 1)); 220 | auto [ it3, inserted3 ] = M.insert(std::pair("ab", 2)); 221 | REQUIRE(it1->second == 1); 222 | REQUIRE(inserted1 == true); 223 | REQUIRE(it2->second == 1); 224 | REQUIRE(inserted2 == true); 225 | REQUIRE(it3->second == 1); 226 | REQUIRE(inserted3 == false); 227 | REQUIRE(M.size() == 2); 228 | } 229 | 230 | TEST_CASE( "insert with move semantics", "[insert_move]") { 231 | radix_cpp::set S; 232 | std::string s = "This string is very long!!!!!!!!!!"; 233 | S.insert(std::move(s)); 234 | REQUIRE(s.empty()); 235 | REQUIRE(S.size() == 1); 236 | auto it = S.begin(); 237 | REQUIRE(*it++ == "This string is very long!!!!!!!!!!"); 238 | REQUIRE(it == S.end()); 239 | } 240 | 241 | TEST_CASE( "emplace with set", "[emplace_set]") { 242 | radix_cpp::set S; 243 | S.emplace("a string"); 244 | S.emplace("another string"); 245 | auto it = S.begin(); 246 | REQUIRE(*it++ == "a string"); 247 | REQUIRE(*it++ == "another string"); 248 | REQUIRE(it == S.end()); 249 | } 250 | 251 | TEST_CASE( "emplace with map", "[emplace_map]") { 252 | radix_cpp::map M; 253 | M.emplace("a string", true); 254 | M.emplace("another string", true); 255 | REQUIRE(M["a string"] == true); 256 | REQUIRE(M["another string"] == true); 257 | } 258 | 259 | TEST_CASE( "at works correctly", "[at]") { 260 | radix_cpp::map M; 261 | M.emplace("s1", 1); 262 | REQUIRE(M.at("s1") == 1); 263 | REQUIRE_THROWS_AS( M.at("s2"), std::out_of_range); 264 | } 265 | 266 | TEST_CASE( "insert_or_assign works", "[insert_or_assign]") { 267 | radix_cpp::map M; 268 | M.insert_or_assign("key", 1); 269 | REQUIRE(M["key"] == 1); 270 | M.insert_or_assign("key", 2); 271 | REQUIRE(M["key"] == 2); 272 | } 273 | 274 | TEST_CASE( "iterator comparison works", "[iterator_cmp]") { 275 | radix_cpp::set S; 276 | S.insert(1); 277 | S.insert(2); 278 | S.insert(3); 279 | auto it = S.begin(); 280 | REQUIRE(it == S.begin()); 281 | REQUIRE(it != S.end()); 282 | REQUIRE(it == S.find(1)); 283 | REQUIRE(it != S.find(2)); 284 | REQUIRE(it != S.find(3)); 285 | 286 | it++; 287 | 288 | REQUIRE(it != S.begin()); 289 | REQUIRE(it != S.end()); 290 | REQUIRE(it != S.find(1)); 291 | REQUIRE(it == S.find(2)); 292 | REQUIRE(it != S.find(3)); 293 | 294 | it++; 295 | 296 | REQUIRE(it != S.begin()); 297 | REQUIRE(it != S.end()); 298 | REQUIRE(it != S.find(1)); 299 | REQUIRE(it != S.find(2)); 300 | REQUIRE(it == S.find(3)); 301 | 302 | it++; 303 | 304 | REQUIRE(it == S.end()); 305 | } 306 | 307 | TEST_CASE( "utf8 strings work", "[utf8]" ) { 308 | radix_cpp::set S; 309 | S.insert("Ångström"); 310 | S.insert("Å"); 311 | S.insert("A"); 312 | auto it = S.begin(); 313 | REQUIRE(*it++ == "A"); 314 | REQUIRE(*it++ == "Å"); 315 | REQUIRE(*it++ == "Ångström"); 316 | } 317 | 318 | TEST_CASE( "erase with set", "[erase_set]" ) { 319 | radix_cpp::set S; 320 | S.insert("k1"); 321 | S.insert("k2"); 322 | 323 | REQUIRE(S.size() == 2); 324 | REQUIRE(S.find("k1") != S.end()); 325 | REQUIRE(S.find("k2") != S.end()); 326 | 327 | auto it = S.begin(); 328 | REQUIRE(*it == "k1"); 329 | it = S.erase(it); 330 | REQUIRE(S.size() == 1); 331 | REQUIRE(*it == "k2"); 332 | it = S.erase(it); 333 | REQUIRE(S.size() == 0); 334 | REQUIRE(it == S.end()); 335 | REQUIRE(S.begin() == S.end()); 336 | 337 | REQUIRE(S.find("k1") == S.end()); 338 | REQUIRE(S.find("k2") == S.end()); 339 | } 340 | 341 | TEST_CASE( "erase empty", "[erase_empty]" ) { 342 | radix_cpp::set S; 343 | S.insert(""); 344 | S.insert("k1"); 345 | S.insert("k2"); 346 | auto it = S.begin(); 347 | it = S.erase(it); 348 | REQUIRE(it == S.begin()); 349 | REQUIRE(*it == "k1"); 350 | } 351 | 352 | TEST_CASE( "erase shared prefix", "[erase_shared_prefix]") { 353 | radix_cpp::set S; 354 | S.insert("a"); 355 | S.insert("ab"); 356 | auto it = S.begin(); 357 | S.erase(it); 358 | it = S.find("ab"); 359 | REQUIRE(it != S.end()); 360 | REQUIRE(*it == "ab"); 361 | } 362 | 363 | TEST_CASE( "erase iterates to next", "[erase_next]") { 364 | radix_cpp::set S; 365 | S.insert(1); 366 | S.insert(2); 367 | auto it = S.begin(); 368 | it = S.erase(it); 369 | REQUIRE(it != S.end()); 370 | REQUIRE(*it == 2); 371 | } 372 | 373 | TEST_CASE( "erasing prefix iterates to next", "[erase_prefix_next]") { 374 | radix_cpp::set S; 375 | S.insert("a"); 376 | S.insert("ab"); 377 | auto it = S.begin(); 378 | it = S.erase(it); 379 | REQUIRE(it != S.end()); 380 | REQUIRE(*it == "ab"); 381 | } 382 | 383 | TEST_CASE( "erase by value", "[erase_by_value]") { 384 | radix_cpp::set S; 385 | S.insert("abba"); 386 | REQUIRE(S.erase("no-such-key") == 0); 387 | REQUIRE(S.erase("abba") == 1); 388 | REQUIRE(S.empty()); 389 | } 390 | 391 | TEST_CASE( "count", "[set_count]") { 392 | radix_cpp::set S; 393 | REQUIRE(S.count(1) == 0); 394 | S.insert(1); 395 | REQUIRE(S.count(1) == 1); 396 | } 397 | 398 | TEST_CASE( "long keys", "[long_keys]") { 399 | std::string k1, k2, k3; 400 | for (size_t i = 0; i < 400; i++) k1 += 'a'; 401 | for (size_t i = 0; i < 800; i++) k2 += 'a'; 402 | for (size_t i = 0; i < 1000; i++) k3 += 'z'; 403 | radix_cpp::set S; 404 | S.insert(k2); 405 | S.insert(k1); 406 | S.insert(""); 407 | S.insert(k3); 408 | S.insert("abc"); 409 | REQUIRE(S.size() == 5); 410 | auto it = S.begin(); 411 | REQUIRE(*it++ == ""); 412 | REQUIRE(*it++ == k1); 413 | REQUIRE(*it++ == k2); 414 | REQUIRE(*it++ == "abc"); 415 | REQUIRE(*it++ == k3); 416 | REQUIRE(it == S.end()); 417 | S.erase(k1); 418 | S.erase(k2); 419 | S.erase(k3); 420 | REQUIRE(S.size() == 2); 421 | it = S.begin(); 422 | #if 0 423 | REQUIRE(*it++ == ""); 424 | REQUIRE(*it++ == "abc"); 425 | REQUIRE(it == S.end()); 426 | #endif 427 | } 428 | 429 | TEST_CASE( "erase multiple", "[erase_multiple]") { 430 | radix_cpp::set S; 431 | for (uint32_t i = 0; i < 1000; i++) { 432 | S.insert(i); 433 | } 434 | auto it = S.begin(); 435 | while (1) { 436 | auto e = *it; 437 | it = S.erase(it); 438 | if (it == S.end()) break; 439 | REQUIRE(e + 1 == *it); 440 | } 441 | REQUIRE(S.empty()); 442 | REQUIRE(S.begin() == S.end()); 443 | } 444 | 445 | TEST_CASE( "erase range", "[erase_range]") { 446 | radix_cpp::set S; 447 | for (uint32_t i = 0; i < 1000; i++) { 448 | S.insert(i); 449 | } 450 | auto it = S.erase(S.begin(), S.end()); 451 | REQUIRE(it == S.end()); 452 | REQUIRE(S.begin() == S.end()); 453 | REQUIRE(S.size() == 0); 454 | } 455 | 456 | TEST_CASE( "integer set upper_bound", "[integer_set_upper_bound]" ) { 457 | radix_cpp::set S; 458 | S.insert(0); 459 | S.insert(10); 460 | S.insert(1000); 461 | S.insert(10000); 462 | S.insert(1000000000); 463 | REQUIRE(S.upper_bound(0) == S.find(10)); 464 | REQUIRE(S.upper_bound(1) == S.find(10)); 465 | REQUIRE(S.upper_bound(999) == S.find(1000)); 466 | REQUIRE(S.upper_bound(1000) == S.find(10000)); 467 | REQUIRE(S.upper_bound(20000) == S.find(1000000000)); 468 | REQUIRE(S.upper_bound(2000000000) == S.end()); 469 | } 470 | 471 | TEST_CASE( "string set upper_bound", "[string_set_upper_bound]" ) { 472 | radix_cpp::set S; 473 | S.insert("abc"); 474 | S.insert("abcd"); 475 | S.insert("abcde"); 476 | S.insert("fff"); 477 | 478 | REQUIRE(S.upper_bound("") == S.find("abc")); 479 | 480 | S.insert(""); 481 | 482 | REQUIRE(S.upper_bound("") == S.find("abc")); 483 | 484 | REQUIRE(S.upper_bound("ab") == S.find("abc")); 485 | REQUIRE(S.upper_bound("abcd") == S.find("abcde")); 486 | REQUIRE(S.upper_bound("ccccccccccccc") == S.find("fff")); 487 | REQUIRE(S.upper_bound("fff") == S.end()); 488 | } 489 | 490 | TEST_CASE( "signed integers in set", "[signed_integer_set]") { 491 | radix_cpp::set S; 492 | S.insert(-10000); 493 | S.insert(0); 494 | S.insert(10); 495 | auto it = S.begin(); 496 | REQUIRE(*it++ == -10000); 497 | REQUIRE(*it++ == 0); 498 | REQUIRE(*it++ == 10); 499 | REQUIRE(it == S.end()); 500 | } 501 | 502 | TEST_CASE( "insert range", "[insert_range]") { 503 | std::vector V; 504 | for (size_t i = 0; i < 1000; i++) { 505 | V.push_back(0.1 * static_cast(i)); 506 | } 507 | radix_cpp::set S; 508 | S.insert(V.begin(), V.end()); 509 | REQUIRE(S.size() == 1000); 510 | auto it = S.begin(); 511 | REQUIRE(*it++ == 0.0); 512 | REQUIRE(*it++ == 0.1); 513 | REQUIRE(*it++ == 0.2); 514 | it = S.find(0.1 * static_cast(998)); 515 | REQUIRE(it != S.end()); 516 | REQUIRE(*it++ == 0.1 * static_cast(998)); 517 | REQUIRE(*it++ == 0.1 * static_cast(999)); 518 | REQUIRE(it == S.end()); 519 | } 520 | 521 | TEST_CASE( "subnormal numbers", "[subnormals]") { 522 | radix_cpp::set S; 523 | S.insert(std::numeric_limits::min() / 2.0); 524 | S.insert(std::numeric_limits::min() / -2.0); 525 | S.insert(0); 526 | S.insert(-1); 527 | S.insert(1); 528 | auto it = S.begin(); 529 | REQUIRE(*it++ == -1.0); 530 | REQUIRE(*it++ < 0.0); 531 | REQUIRE(*it++ == 0.0); 532 | REQUIRE(*it++ > 0.0); 533 | REQUIRE(*it++ == 1.0); 534 | REQUIRE(it == S.end()); 535 | } 536 | --------------------------------------------------------------------------------