├── .github └── workflows │ ├── docs.yml │ └── workflow.yml ├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── LICENSE ├── benchmarks ├── CMakeLists.txt └── benchmark.cpp ├── codecov.yml ├── docs ├── Doxyfile.in ├── Makefile ├── README.rst ├── api.rst ├── bpp_tree_detail.rst ├── builder.rst ├── conf.py ├── index.rst ├── indexed.rst ├── make.bat ├── max.rst ├── min.rst ├── mixin_builder.rst ├── ordered.rst └── summed.rst ├── examples └── inverted_index.hpp ├── include └── bpptree │ ├── bpptree.hpp │ ├── detail │ ├── common.hpp │ ├── helpers.hpp │ ├── indexed_detail.hpp │ ├── internalnodebase.hpp │ ├── iterator.hpp │ ├── leafnodebase.hpp │ ├── minmax.hpp │ ├── minmax2.ipp │ ├── modify.hpp │ ├── nodeptr.hpp │ ├── nodetypes.hpp │ ├── operations.hpp │ ├── ordered_detail.hpp │ ├── proxy_operators.hpp │ ├── sandwich.hpp │ ├── summed_detail.hpp │ └── uninitialized_array.hpp │ ├── indexed.hpp │ ├── max.hpp │ ├── min.hpp │ ├── ordered.hpp │ └── summed.hpp └── tests ├── test_common.hpp ├── test_deque.cpp ├── test_inverted_index.cpp ├── test_iterator_insert_erase.cpp ├── test_iterators.cpp ├── test_min.cpp ├── test_ordered.cpp ├── test_random_modifications_indexed.cpp ├── test_random_modifications_ordered.cpp ├── test_reference_wrapper.cpp ├── test_sum_lower_bound.cpp ├── test_summed_indexed.cpp └── test_uninitialized_array.cpp /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: Docs 2 | 3 | on: 4 | #push: 5 | # branches-ignore: 6 | # - '**' 7 | push: 8 | branches: [ main ] 9 | pull_request: 10 | branches: [ main ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - name: Requirements 19 | run: sudo apt-get install doxygen 20 | && sudo apt-get install python3-sphinx 21 | && pip3 install sphinx-rtd-theme 22 | && pip3 install breathe 23 | && pip3 install sphinx-sitemap 24 | - name: Checkout repo 25 | uses: actions/checkout@1.0.0 26 | - name: Build docs 27 | run: cd docs 28 | && make clean html 29 | && cd _build/html 30 | && touch .nojekyll 31 | - name: Deploy 32 | uses: JamesIves/github-pages-deploy-action@releases/v3 33 | with: 34 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 35 | BRANCH: gh-pages # The branch the action should deploy to. 36 | FOLDER: docs/_build/html # The folder the action should deploy. -------------------------------------------------------------------------------- /.github/workflows/workflow.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | #push: 5 | # branches-ignore: 6 | # - '**' 7 | push: 8 | branches: [ main ] 9 | pull_request: 10 | branches: [ main ] 11 | 12 | env: 13 | CTEST_OUTPUT_ON_FAILURE: 1 14 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} 15 | 16 | jobs: 17 | build: 18 | 19 | runs-on: ubuntu-latest 20 | timeout-minutes: 30 21 | 22 | steps: 23 | - uses: actions/checkout@v3 24 | with: 25 | submodules: recursive 26 | 27 | - name: configure 28 | run: cmake -S . -Bbuild -DENABLE_TEST_COVERAGE=1 -DCMAKE_BUILD_TYPE=Debug 29 | 30 | - name: build 31 | run: cmake --build build --target btree_test -j2 32 | 33 | - name: test 34 | run: | 35 | cd build 36 | ctest --build-config Debug 37 | - name: collect code coverage 38 | run: bash <(curl -s https://codecov.io/bash) || echo "Codecov did not collect coverage reports" 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /build* 2 | /.vscode 3 | /.vs 4 | /out 5 | .DS_Store 6 | .idea 7 | /cmake-build-* 8 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "benchmarks/immer"] 2 | path = benchmarks/immer 3 | url = https://github.com/arximboldi/immer.git 4 | [submodule "benchmarks/abseil-cpp"] 5 | path = benchmarks/abseil-cpp 6 | url = https://github.com/abseil/abseil-cpp.git 7 | [submodule "benchmarks/tlx"] 8 | path = benchmarks/tlx 9 | url = https://github.com/tlx/tlx.git 10 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.23) 2 | project(BppTree) 3 | 4 | add_subdirectory(benchmarks) 5 | 6 | set(CMAKE_CXX_STANDARD 17) 7 | 8 | option(ENABLE_TEST_COVERAGE "Enable test coverage" OFF) 9 | option(ENABLE_UBSAN "Enable undefined behavior sanitizer" OFF) 10 | 11 | include(FetchContent) 12 | FetchContent_Declare( 13 | googletest 14 | GIT_REPOSITORY https://github.com/google/googletest.git 15 | GIT_TAG release-1.12.1 16 | ) 17 | # For Windows: Prevent overriding the parent project's compiler/linker settings 18 | set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) 19 | FetchContent_MakeAvailable(googletest) 20 | 21 | enable_testing() 22 | 23 | add_executable( 24 | btree_test 25 | tests/test_summed_indexed.cpp 26 | tests/test_iterators.cpp 27 | tests/test_min.cpp 28 | tests/test_ordered.cpp 29 | tests/test_uninitialized_array.cpp 30 | tests/test_reference_wrapper.cpp 31 | tests/test_iterator_insert_erase.cpp 32 | tests/test_deque.cpp 33 | tests/test_random_modifications_indexed.cpp 34 | tests/test_sum_lower_bound.cpp 35 | tests/test_inverted_index.cpp 36 | tests/test_random_modifications_ordered.cpp) 37 | 38 | target_include_directories(btree_test PRIVATE include) 39 | target_include_directories(btree_test PRIVATE examples) 40 | 41 | target_link_libraries(btree_test GTest::gtest_main) 42 | 43 | include(GoogleTest) 44 | gtest_discover_tests(btree_test) 45 | 46 | target_compile_options(btree_test PRIVATE "$<$:-Og>") 47 | target_compile_options(btree_test PRIVATE -Wall) 48 | target_compile_options(btree_test PRIVATE -Wextra) 49 | target_compile_options(btree_test PRIVATE -Wconversion) 50 | target_compile_options(btree_test PRIVATE -Wsign-conversion) 51 | target_compile_options(btree_test PRIVATE -Wfloat-conversion) 52 | target_compile_options(btree_test PRIVATE -Wstrict-aliasing) 53 | target_compile_options(btree_test PRIVATE -Wno-unknown-pragmas) 54 | target_compile_options(btree_test PRIVATE -Werror) 55 | target_compile_options(btree_test PRIVATE -Wfatal-errors) 56 | target_compile_definitions(btree_test PRIVATE BPPTREE_TEST_COUNT_ALLOCATIONS) 57 | target_compile_definitions(btree_test PRIVATE BPPTREE_SAFETY_CHECKS) 58 | 59 | if (ENABLE_UBSAN) 60 | target_compile_options(btree_test PRIVATE -fsanitize=undefined) 61 | target_link_options(btree_test PRIVATE -fsanitize=undefined) 62 | endif() 63 | 64 | #target_compile_options(btree_test PRIVATE -ftime-trace) 65 | #target_link_options(btree_test PRIVATE -ftime-trace) 66 | 67 | if(ENABLE_TEST_COVERAGE) 68 | target_compile_options(btree_test PRIVATE -g -fprofile-arcs -ftest-coverage) 69 | target_link_options(btree_test PRIVATE -fprofile-arcs -ftest-coverage) 70 | endif() 71 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Boost Software License - Version 1.0 - August 17th, 2003 2 | 3 | Permission is hereby granted, free of charge, to any person or organization 4 | obtaining a copy of the software and accompanying documentation covered by 5 | this license (the "Software") to use, reproduce, display, distribute, 6 | execute, and transmit the Software, and to prepare derivative works of the 7 | Software, and to permit third-parties to whom the Software is furnished to 8 | do so, all subject to the following: 9 | 10 | The copyright notices in the Software and this entire statement, including 11 | the above license grant, this restriction and the following disclaimer, 12 | must be included in all copies of the Software, in whole or in part, and 13 | all derivative works of the Software, unless such copies or derivative 14 | works are solely in the form of machine-executable object code generated by 15 | a source language processor. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 20 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 21 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 22 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /benchmarks/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(CMAKE_CXX_STANDARD 17) 2 | 3 | add_executable(benchmarks benchmark.cpp) 4 | 5 | target_include_directories(benchmarks PRIVATE ../include) 6 | target_include_directories(benchmarks PRIVATE ../tests) 7 | target_include_directories(benchmarks PRIVATE immer) 8 | target_include_directories(benchmarks PRIVATE abseil-cpp) 9 | target_include_directories(benchmarks PRIVATE tlx) 10 | target_compile_options(benchmarks PRIVATE -fno-exceptions) 11 | -------------------------------------------------------------------------------- /benchmarks/benchmark.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "test_common.hpp" 5 | #include "immer/flex_vector.hpp" 6 | #include "absl/container/btree_map.h" 7 | #include "tlx/container/btree_map.hpp" 8 | 9 | using namespace std; 10 | 11 | template 12 | inline void random_benchmark(T&& tree, std::string const& message) { 13 | Vector const& rand_ints = RandInts::ints; 14 | cout << "Running random benchmark using " << message << " with size " << n << endl; 15 | cout << "=============================================================" << endl; 16 | auto startTime = std::chrono::steady_clock::now(); 17 | for (int i = 0; i < n; i++) { 18 | tree[rand_ints[i]] = i; 19 | } 20 | auto endTime = std::chrono::steady_clock::now(); 21 | std::chrono::duration elapsed = endTime - startTime; 22 | cout << elapsed.count() << 's' << endl; 23 | 24 | int64_t sum = 0; 25 | startTime = std::chrono::steady_clock::now(); 26 | for (int i = 0; i < n; i++) { 27 | sum += tree[rand_ints[i]]; 28 | } 29 | endTime = std::chrono::steady_clock::now(); 30 | elapsed = endTime - startTime; 31 | cout << elapsed.count() << 's' << endl; 32 | cout << sum << endl << endl; 33 | } 34 | 35 | template 36 | inline void random_benchmarks(std::integral_constant) { 37 | Vector const& rand_ints = RandInts::ints; 38 | for (int j = 0; j < 5; ++j) { 39 | random_benchmark(absl::btree_map(), "absl::btree_map"); 40 | random_benchmark(tlx::btree_map(), "tlx::btree_map"); 41 | random_benchmark(BppTreeMap::Transient(), "BppTreeMap::Transient"); 42 | random_benchmark(std::map(), "std::map"); 43 | if constexpr (true) { 44 | BppTreeMap::Transient tree{}; 45 | cout << "BppTreeMap::Transient (no operators)" << " : " << n << endl; 46 | cout << "=============================================================" << endl; 47 | auto startTime = std::chrono::steady_clock::now(); 48 | for (int i = 0; i < n; i++) { 49 | tree.insert_or_assign(rand_ints[i], i); 50 | } 51 | auto endTime = std::chrono::steady_clock::now(); 52 | std::chrono::duration elapsed = endTime - startTime; 53 | cout << elapsed.count() << 's' << endl; 54 | 55 | int64_t sum = 0; 56 | startTime = std::chrono::steady_clock::now(); 57 | for (int i = 0; i < n; i++) { 58 | sum += tree.at_key(rand_ints[i]); 59 | } 60 | endTime = std::chrono::steady_clock::now(); 61 | elapsed = endTime - startTime; 62 | cout << elapsed.count() << 's' << endl; 63 | cout << sum << endl << endl; 64 | } 65 | } 66 | if constexpr (true) { 67 | using TreeType = BppTreeMap::Persistent; 68 | TreeType tree{}; 69 | cout << "BppTreeMap::Persistent : " << n << endl; 70 | cout << "=============================================================" << endl; 71 | auto startTime = std::chrono::steady_clock::now(); 72 | for (int i = 0; i < n; i++) { 73 | tree = tree.insert_v(rand_ints[i], i); 74 | } 75 | auto endTime = std::chrono::steady_clock::now(); 76 | std::chrono::duration elapsed = endTime - startTime; 77 | cout << elapsed.count() << 's' << endl; 78 | 79 | int64_t sum = 0; 80 | startTime = std::chrono::steady_clock::now(); 81 | for (int i = 0; i < n; i++) { 82 | sum += tree[rand_ints[i]]; 83 | } 84 | endTime = std::chrono::steady_clock::now(); 85 | elapsed = endTime - startTime; 86 | cout << elapsed.count() << 's' << endl; 87 | cout << sum << endl << endl; 88 | } 89 | if constexpr (true) { 90 | immer::flex_vector> vec{}; 91 | cout << "immer::flex_vector : " << n << endl; 92 | cout << "=============================================================" << endl; 93 | auto startTime = std::chrono::steady_clock::now(); 94 | for (int i = 0; i < n; i++) { 95 | vec = vec.insert(std::lower_bound(vec.begin(), vec.end(), std::make_pair(rand_ints[i], 0)) - vec.begin(), std::make_pair(rand_ints[i], i)); 96 | } 97 | auto endTime = std::chrono::steady_clock::now(); 98 | std::chrono::duration elapsed = endTime - startTime; 99 | cout << elapsed.count() << 's' << endl; 100 | 101 | int64_t sum = 0; 102 | startTime = std::chrono::steady_clock::now(); 103 | for (int i = 0; i < n; i++) { 104 | sum += std::lower_bound(vec.begin(), vec.end(), std::make_pair(rand_ints[i], 0))->second; 105 | } 106 | endTime = std::chrono::steady_clock::now(); 107 | elapsed = endTime - startTime; 108 | cout << elapsed.count() << 's' << endl; 109 | cout << sum << endl << endl; 110 | } 111 | if constexpr (n <= 1000*1000) { 112 | Vector> vec{}; 113 | cout << "std::vector : " << n << endl; 114 | cout << "=============================================================" << endl; 115 | auto startTime = std::chrono::steady_clock::now(); 116 | for (int i = 0; i < n; i++) { 117 | vec.insert(std::lower_bound(vec.begin(), vec.end(), std::make_pair(rand_ints[i], 0)), std::make_pair(rand_ints[i], i)); 118 | } 119 | auto endTime = std::chrono::steady_clock::now(); 120 | std::chrono::duration elapsed = endTime - startTime; 121 | cout << elapsed.count() << 's' << endl; 122 | 123 | int64_t sum = 0; 124 | startTime = std::chrono::steady_clock::now(); 125 | for (int i = 0; i < n; i++) { 126 | sum += std::lower_bound(vec.begin(), vec.end(), std::make_pair(rand_ints[i], 0))->second; 127 | } 128 | endTime = std::chrono::steady_clock::now(); 129 | elapsed = endTime - startTime; 130 | cout << elapsed.count() << 's' << endl; 131 | cout << sum << endl << endl; 132 | } 133 | } 134 | 135 | template 136 | inline void sequential_benchmark(T&& tree, std::string const& message) { 137 | cout << "Running sequential benchmark using " << message << " with size " << n << endl; 138 | cout << "=============================================================" << endl; 139 | auto startTime = std::chrono::steady_clock::now(); 140 | for (int i = 0; i < n; i++) { 141 | tree[i] = i; 142 | } 143 | auto endTime = std::chrono::steady_clock::now(); 144 | std::chrono::duration elapsed = endTime - startTime; 145 | cout << elapsed.count() << 's' << endl; 146 | 147 | int64_t sum = 0; 148 | startTime = std::chrono::steady_clock::now(); 149 | for (int i = 0; i < n; i++) { 150 | sum += tree[i]; 151 | } 152 | endTime = std::chrono::steady_clock::now(); 153 | elapsed = endTime - startTime; 154 | cout << elapsed.count() << 's' << endl; 155 | cout << sum << endl << endl; 156 | } 157 | 158 | template 159 | struct HasPushBack : std::false_type {}; 160 | 161 | template 162 | struct HasPushBack().push_back(std::make_pair(0, 0)))>> : std::true_type {}; 163 | 164 | template 165 | inline void iterator_benchmark(T&& tree, std::string const& message) { 166 | cout << "Running iterator benchmark using " << message << " with size " << n << endl; 167 | cout << "=============================================================" << endl; 168 | auto startTime = std::chrono::steady_clock::now(); 169 | for (int i = 0; i < n; i++) { 170 | if constexpr (HasPushBack::value) { 171 | tree.push_back(std::make_pair(i, i)); 172 | } else { 173 | tree[i] = i; 174 | } 175 | } 176 | auto endTime = std::chrono::steady_clock::now(); 177 | std::chrono::duration elapsed = endTime - startTime; 178 | cout << elapsed.count() << 's' << endl; 179 | 180 | int64_t sum = 0; 181 | startTime = std::chrono::steady_clock::now(); 182 | for (auto const& p: std::as_const(tree)) { 183 | sum += p.second; 184 | } 185 | endTime = std::chrono::steady_clock::now(); 186 | elapsed = endTime - startTime; 187 | cout << elapsed.count() << 's' << endl; 188 | cout << sum << endl << endl; 189 | } 190 | 191 | template 192 | inline void sequential_benchmarks(std::integral_constant) { 193 | for (int j = 0; j < 5; ++j) { 194 | sequential_benchmark(absl::btree_map(), "absl::btree_map"); 195 | sequential_benchmark(tlx::btree_map(), "tlx::btree_map"); 196 | sequential_benchmark(BppTreeMap::Transient(), "BppTreeMap::Transient"); 197 | sequential_benchmark(std::map(), "std::map"); 198 | 199 | iterator_benchmark(absl::btree_map(), "absl::btree_map"); 200 | iterator_benchmark(tlx::btree_map(), "tlx::btree_map"); 201 | iterator_benchmark(BppTreeMap::Transient(), "BppTreeMap::Transient"); 202 | iterator_benchmark(std::map(), "std::map"); 203 | } 204 | if constexpr (true) { 205 | using TreeType = BppTreeMap::internal_node_bytes<256>::leaf_node_bytes<1024>::Persistent; 206 | TreeType tree{}; 207 | cout << "BppTreeMap::Persistent : " << n << endl; 208 | cout << "=============================================================" << endl; 209 | auto startTime = std::chrono::steady_clock::now(); 210 | for (int i = 0; i < n; i++) { 211 | tree = tree.insert_v(i, i); 212 | } 213 | auto endTime = std::chrono::steady_clock::now(); 214 | std::chrono::duration elapsed = endTime - startTime; 215 | cout << elapsed.count() << 's' << endl; 216 | 217 | int64_t sum = 0; 218 | startTime = std::chrono::steady_clock::now(); 219 | for (int i = 0; i < n; i++) { 220 | sum += tree[i]; 221 | } 222 | endTime = std::chrono::steady_clock::now(); 223 | elapsed = endTime - startTime; 224 | cout << elapsed.count() << 's' << endl; 225 | cout << sum << endl << endl; 226 | } 227 | if constexpr (true) { 228 | immer::flex_vector> vec{}; 229 | cout << "immer::flex_vector : " << n << endl; 230 | cout << "=============================================================" << endl; 231 | auto startTime = std::chrono::steady_clock::now(); 232 | for (int i = 0; i < n; i++) { 233 | vec = vec.insert(std::lower_bound(vec.begin(), vec.end(), std::make_pair(i, 0)) - vec.begin(), std::make_pair(i, i)); 234 | } 235 | auto endTime = std::chrono::steady_clock::now(); 236 | std::chrono::duration elapsed = endTime - startTime; 237 | cout << elapsed.count() << 's' << endl; 238 | 239 | int64_t sum = 0; 240 | startTime = std::chrono::steady_clock::now(); 241 | for (int i = 0; i < n; i++) { 242 | sum += std::lower_bound(vec.begin(), vec.end(), std::make_pair(i, 0))->second; 243 | } 244 | endTime = std::chrono::steady_clock::now(); 245 | elapsed = endTime - startTime; 246 | cout << elapsed.count() << 's' << endl; 247 | cout << sum << endl << endl; 248 | } 249 | if constexpr (true) { 250 | Vector> vec{}; 251 | cout << "std::vector : " << n << endl; 252 | cout << "=============================================================" << endl; 253 | auto startTime = std::chrono::steady_clock::now(); 254 | for (int i = 0; i < n; i++) { 255 | vec.insert(std::lower_bound(vec.begin(), vec.end(), std::make_pair(i, 0)), std::make_pair(i, i)); 256 | } 257 | auto endTime = std::chrono::steady_clock::now(); 258 | std::chrono::duration elapsed = endTime - startTime; 259 | cout << elapsed.count() << 's' << endl; 260 | 261 | int64_t sum = 0; 262 | startTime = std::chrono::steady_clock::now(); 263 | for (int i = 0; i < n; i++) { 264 | sum += std::lower_bound(vec.begin(), vec.end(), std::make_pair(i, 0))->second; 265 | } 266 | endTime = std::chrono::steady_clock::now(); 267 | elapsed = endTime - startTime; 268 | cout << elapsed.count() << 's' << endl; 269 | cout << sum << endl << endl; 270 | } 271 | } 272 | 273 | int main() { 274 | auto sizes = std::make_tuple( 275 | integral_constant(), 276 | integral_constant(), 277 | integral_constant(), 278 | integral_constant(), 279 | integral_constant()); 280 | apply([](auto... n){ (sequential_benchmarks(n), ...); }, sizes); 281 | apply([](auto... n){ (random_benchmarks(n), ...); }, sizes); 282 | } 283 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | ignore: 2 | - "tests" 3 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /docs/README.rst: -------------------------------------------------------------------------------- 1 | index.rst -------------------------------------------------------------------------------- /docs/api.rst: -------------------------------------------------------------------------------- 1 | .. _api: 2 | 3 | API 4 | === 5 | .. toctree:: 6 | :maxdepth: 2 7 | 8 | builder 9 | mixin_builder 10 | bpp_tree_detail 11 | indexed 12 | ordered 13 | summed 14 | min 15 | max 16 | -------------------------------------------------------------------------------- /docs/bpp_tree_detail.rst: -------------------------------------------------------------------------------- 1 | .. _api_bpptree: 2 | 3 | BppTreeDetail 4 | ============= 5 | .. doxygenclass:: bpptree::detail::BppTreeDetail 6 | :project: B++ Tree 7 | -------------------------------------------------------------------------------- /docs/builder.rst: -------------------------------------------------------------------------------- 1 | .. _api_builder: 2 | 3 | Tree Builders 4 | ============= 5 | 6 | BppTree 7 | ------------- 8 | .. doxygenstruct:: bpptree::detail::BppTree 9 | :project: B++ Tree 10 | 11 | BppTreeVector 12 | ---------------- 13 | .. doxygenstruct:: bpptree::detail::BppTreeVector 14 | :project: B++ Tree 15 | 16 | BppTreeMap 17 | ------------ 18 | .. doxygenstruct:: bpptree::detail::BppTreeMap 19 | :project: B++ Tree 20 | 21 | BppTreeSet 22 | ------------- 23 | .. doxygenstruct:: bpptree::detail::BppTreeSet 24 | :project: B++ Tree 25 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # For the full list of built-in configuration values, see the documentation: 4 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 5 | 6 | 7 | # Configuration file for the Sphinx documentation builder. 8 | # 9 | # This file only contains a selection of the most common options. For a full 10 | # list see the documentation: 11 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 12 | 13 | # -- Path setup -------------------------------------------------------------- 14 | 15 | # If extensions (or modules to document with autodoc) are in another directory, 16 | # add these directories to sys.path here. If the directory is relative to the 17 | # documentation root, use os.path.abspath to make it absolute, like shown here. 18 | # 19 | # import os 20 | # import sys 21 | # sys.path.insert(0, os.path.abspath('.')) 22 | from sphinx.builders.html import StandaloneHTMLBuilder 23 | import subprocess, os 24 | 25 | # Doxygen 26 | subprocess.call('doxygen Doxyfile.in', shell=True) 27 | 28 | # -- Project information ----------------------------------------------------- 29 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information 30 | 31 | project = 'B++ Tree' 32 | copyright = '2023, Jeff Plaisance' 33 | author = 'Jeff Plaisance' 34 | release = '0.1.0' 35 | 36 | # -- General configuration --------------------------------------------------- 37 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration 38 | 39 | # Add any Sphinx extension module names here, as strings. They can be 40 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 41 | # ones. 42 | extensions = [ 43 | 'sphinx.ext.autodoc', 44 | 'sphinx.ext.intersphinx', 45 | 'sphinx.ext.autosectionlabel', 46 | 'sphinx.ext.todo', 47 | 'sphinx.ext.coverage', 48 | 'sphinx.ext.mathjax', 49 | 'sphinx.ext.ifconfig', 50 | 'sphinx.ext.viewcode', 51 | 'sphinx_sitemap', 52 | 'sphinx.ext.inheritance_diagram', 53 | 'breathe' 54 | ] 55 | 56 | templates_path = ['_templates'] 57 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 58 | 59 | highlight_language = 'c++' 60 | 61 | # -- Options for HTML output ------------------------------------------------- 62 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output 63 | 64 | html_theme = 'sphinx_rtd_theme' 65 | html_theme_options = { 66 | 'canonical_url': '', 67 | 'analytics_id': '', 68 | 'display_version': True, 69 | 'prev_next_buttons_location': 'bottom', 70 | 'style_external_links': False, 71 | 72 | 'logo_only': False, 73 | 74 | # Toc options 75 | 'collapse_navigation': True, 76 | 'sticky_navigation': True, 77 | 'navigation_depth': 4, 78 | 'includehidden': True, 79 | 'titles_only': False 80 | } 81 | # html_logo = '' 82 | github_url = 'https://www.github.com/jeffplaisance/BppTree' 83 | html_baseurl = '' 84 | 85 | # Add any paths that contain custom static files (such as style sheets) here, 86 | # relative to this directory. They are copied after the builtin static files, 87 | # so a file named "default.css" will overwrite the builtin "default.css". 88 | html_static_path = ['_static'] 89 | 90 | # -- Breathe configuration ------------------------------------------------- 91 | 92 | breathe_projects = { 93 | "B++ Tree": "_build/xml/" 94 | } 95 | breathe_default_project = "C++ Sphinx Doxygen Breathe" 96 | breathe_default_members = ('members', 'undoc-members') 97 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. B++ Tree documentation master file, created by 2 | sphinx-quickstart on Thu Apr 20 21:24:04 2023. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | B++ Tree 7 | ============= 8 | 9 | .. image:: https://github.com/jeffplaisance/BppTree/actions/workflows/workflow.yml/badge.svg 10 | :target: https://github.com/jeffplaisance/BppTree/actions?query=workflow%3ATests+branch%3Amain 11 | :alt: GitHub Actions Badge 12 | 13 | .. image:: https://codecov.io/gh/jeffplaisance/BppTree/branch/main/graph/badge.svg 14 | :target: https://codecov.io/gh/jeffplaisance/BppTree 15 | :alt: CodeCov Badge 16 | 17 | `See full documentation here `_ 18 | 19 | B++ Tree is a header only B+ tree library written in C++. B++ Trees can be mutable or immutable, and you can easily 20 | convert between the two. B++ Trees also support a variety of mixins, each of which adds additional functionality to the 21 | tree. You can add as many mixins to a B++ Tree as you want, enabling you to create things such as a map that can be 22 | accessed by key or by index, or a vector that supports calculating the sum between any two indexes in O(log N) time. 23 | A priority queue that can be accessed both by priority order and insertion order? Yep, B++ Trees can do that too! 24 | 25 | B++ Trees have all the usual cache locality benefits of B+ trees that make them faster than balanced binary search 26 | trees for most use cases. I have experimentally determined that 512 bytes is a good size for the nodes in a B++ Tree, so 27 | that is what I have selected as the default, but the node size is parameterized and you can set it to whatever you'd 28 | like. 29 | 30 | The B++ Tree library requires C++17 or later. 31 | 32 | Mutability 33 | -------------- 34 | 35 | All B++ Trees are easily convertible between their mutable and immutable representations. In this library, the mutable 36 | form of a B++ Tree is called Transient and the immutable form of a B++ Tree is called 37 | `Persistent `_. Every Transient tree 38 | has a .persistent() method which returns a Persistent version of the tree, and every Persistent tree has a .transient() 39 | method that returns a Transient version of the tree. Persistent trees are always copy on write. Any time the 40 | .persistent() method is called on a Transient tree, the entire Transient tree will be marked copy on write, and future 41 | modifications to the Transient tree will copy the nodes they would otherwise have to modify. The same applies when a 42 | Transient tree is created by calling the .transient() method on a Persistent tree, any nodes that would be modified by 43 | the Transient tree will first make a copy. In the worst case, making a Persistent tree from a Transient tree by calling 44 | .persistent() is O(N) because it must mark every node copy on write. If you have a Persistent tree which you turn 45 | into a Transient tree, modify, and then turn back into a Persistent tree, this will only be O(M log M) where M is the 46 | number of modifications that were made. 47 | 48 | Mixins 49 | -------------------- 50 | 51 | The internal nodes of a B++ Tree with no mixins only contain pointers to their children. Without mixins, a B++ tree can 52 | be used as a mutable or immutable replacement for ``std::deque`` (with O(log N) ``emplace_front`` and ``emplace_back``), 53 | but that is about all it can do. Almost all of the functionality comes from the mixins, and as they are all optional, 54 | you only have to pay for what you choose to use. The only restriction is that each mixin can only be added once to a 55 | tree, otherwise the method names will conflict and you will only be able to call the methods for one of them. 56 | 57 | Ordered 58 | ^^^^^^^^ 59 | 60 | :ref:`api_ordered` adds lookup by key into a B++ tree. Values in the B++ tree must be ordered by key to use Ordered. 61 | Supports lookup, lower_bound, upper_bound, assign, insert, update, and erase by key in O(log N) time. 62 | 63 | Indexed 64 | ^^^^^^^^ 65 | 66 | :ref:`api_indexed` adds support for indexing into a B++ tree by an integer index. Supports lookup, assign, insert, 67 | update, and erase by index in O(log N) time. Also supports getting the index of an element from an iterator. Given two 68 | iterators a and b from an indexed tree, ``a-b`` is O(log N) (it is O(N) for non-indexed trees) 69 | 70 | Summed 71 | ^^^^^^^^^ 72 | 73 | :ref:`api_summed` adds prefix sum support to a B++ tree in O(log N) time. ``sum()`` returns the sum over the entire 74 | tree. ``tree.sum_inclusive(it)`` returns the sum up to and including the element pointed to by the iterator 'it'. 75 | ``tree.sum_exclusive(it)`` returns the sum up to but not including the element pointed to by the iterator 'it'. If you 76 | want to sum between two iterators, just do ``tree.sum_exclusive(it2)-tree.sum_exclusive(it1)``. ``tree.sum_lower_bound(target)`` 77 | returns an iterator pointing to the first element for which ``tree.sum_inclusive(it) >= target``. 78 | 79 | Min 80 | ^^^^^ 81 | 82 | :ref:`api_min` adds support for finding the minimum value in a B++ tree in O(log N) time. You can find just the min 83 | value using the ``tree.min()`` method or get an iterator pointing to the min element with ``tree.min_element()``. You 84 | can also find the min value/element in a subrange of the tree (identified by two iterators) with ``tree.min(it1, it2)`` 85 | or ``tree.min_element(it1, it2)``. 86 | 87 | Max 88 | ^^^^^ 89 | 90 | :ref:`api_max` adds support for finding the maximum value in a B++ tree in O(log N) time. You can find just the max 91 | value using the ``tree.max()`` method or get an iterator pointing to the max element with ``tree.max_element()``. You 92 | can also find the max value/element in a subrange of the tree (identified by two iterators) with ``tree.max(it1, it2)`` 93 | or ``tree.max_element(it1, it2)``. You may be wondering why there are separate mixins for min and max when the only 94 | difference is the comparison function: it is so that you can use both in the same B++ Tree without creating method name 95 | conflicts! 96 | 97 | Iterator Stability 98 | --------------------- 99 | 100 | Iterators into Persistent B++ Trees are never invalidated. Iterators into Transient B++ Trees are not invalidated when 101 | modifying an existing element in the tree, i.e. the assign and update methods do not invalidate iterators. **ALL** iterators 102 | into a Transient B++ Tree are invalidated whenever an insert or erase is performed, with the exception that the methods 103 | ``tree.erase(it)`` and ``tree.insert(it, value)`` do not invalidate the iterator that they are called with (but all other 104 | iterators into tree would still be invalidated). 105 | 106 | Proxy References 107 | -------------------- 108 | The only way to allow assignment through ``operator[]`` on Ordered and Indexed is with proxy references, because the 109 | metadata in the internal nodes of the B++ Tree has to be updated on all modifications. As a result, you should not use 110 | auto to declare the type of a variable to which the result of ``operator[]`` is assigned. The non-const iterators also 111 | use proxy references for ``operator*()`` for the same reason. If you do not like proxy references, there are named 112 | methods (``at_*``, ``assign_*``, ``update_*``) which do the same things without using proxy references. 113 | 114 | Builders 115 | ---------- 116 | 117 | Ok, enough about how B++ Trees work, how do I use one? The BppTree template is used to specify the value type 118 | (required), and optionally the leaf and internal node sizes in bytes, as well as a maximum depth for the tree, which can 119 | reduce compile times and code size if set to a smaller number at the expense of limiting the maximum tree size. The 120 | BppTree template has a member called ``mixins``, which accepts any number of mixin builders. The mixin builders have 121 | reasonable defaults for the most part, and you can override these defaults to customize them for your use case. Most 122 | commonly, you might want to override the extractors, which are responsible for extracting a field from the value to use 123 | with the mixin, so that for example you can have an Ordered and Summed B++ Tree with std::pair values that uses the 124 | first element of the pair as the key and calculates sums over the second element (see SummingMap example below). If you 125 | implement your own extractor, note that extractors must have a constructor that takes no arguments and must have no 126 | mutable state. 127 | 128 | Here is how to make a B++ Tree with no mixins: 129 | 130 | .. code-block:: cpp 131 | 132 | using NoMixins = BppTree::Transient; 133 | NoMixins no_mixins{}; 134 | no_mixins.emplace_back(5); 135 | no_mixins.emplace_front(2); 136 | assert(no_mixins.front() == 2); 137 | assert(no_mixins.back() == 5); 138 | 139 | Here is how to make a B++ Tree that can be used like a ``std::map``: 140 | 141 | .. code-block:: cpp 142 | 143 | using Map = BppTree>::mixins>::Transient; 144 | Map bpptree_map{}; 145 | bpptree_map[5] = 8; 146 | if (bpptree_map[5] == 8) { 147 | ++bpptree_map[5]; 148 | } 149 | assert(bpptree_map[5] == 9); 150 | 151 | Because Ordered B++ trees where the value is a key value pair are incredibly common, there is a convenience helper 152 | which makes the above a little bit simpler: 153 | 154 | .. code-block:: cpp 155 | 156 | using Map = BppTreeMap::Transient; 157 | 158 | Here is how to make a B++ Tree that can be used like a ``std::vector``: 159 | 160 | .. code-block:: cpp 161 | 162 | using Vector = BppTree::mixins>::Transient; 163 | Vector bpptree_vec{}; 164 | bpptree_vec.emplace_back(5); 165 | if (bpptree_vec[0] == 5) { 166 | ++bpptree_vec[0]; 167 | } 168 | assert(bpptree_vec[0] == 6); 169 | 170 | As with the Ordered example above, there is also a convenience helper for making Indexed B++ trees: 171 | 172 | .. code-block:: cpp 173 | 174 | using Vector = BppTreeVector::Transient; 175 | 176 | Here is how to make a B++ Tree that can be used like a ``std::vector`` and that can calculate prefix sums in O(log N) time: 177 | 178 | .. code-block:: cpp 179 | 180 | using SummingVector = BppTreeVector::mixins>::Transient; 181 | SummingVector bpptree_vec2{}; 182 | bpptree_vec2.emplace_back(5); 183 | bpptree_vec2.emplace_back(2); 184 | if (bpptree_vec2[0] == 5) { 185 | ++bpptree_vec2[0]; 186 | } 187 | assert(bpptree_vec2.sum() == 8); 188 | 189 | Here is how to make a B++ Tree that can be used like a ``std::deque`` and a ``std::priority_queue`` at the same time: 190 | 191 | .. code-block:: cpp 192 | 193 | using Queue = BppTree::mixins>::Transient; 194 | Queue bpptree_queue{}; 195 | bpptree_queue.emplace_back(5); 196 | bpptree_queue.emplace_back(2); 197 | bpptree_queue.emplace_back(3); 198 | assert(bpptree_queue.min() == 2); 199 | assert(bpptree_queue.front() == 5); 200 | assert(bpptree_queue.back() == 3); 201 | 202 | Here is how to make a B++ Tree that can be used like a ``std::map`` and also calculate prefix sums in O(log N) time: 203 | 204 | .. code-block:: cpp 205 | 206 | using SummingMap = BppTreeMap::mixins>>::Transient; 207 | SummingMap bpptree_map{}; 208 | bpptree_map[5] = 8; 209 | if (bpptree_map[5] == 8) { 210 | ++bpptree_map[5]; 211 | } 212 | bpptree_map[8] = 4; 213 | bpptree_map[6] = 2; 214 | assert(bpptree_map.sum() == 15); 215 | assert(bpptree_map.sum_inclusive(bpptree_map.begin() + 1) == 11); 216 | 217 | Here is how to make a B++ Tree that can be used like a ``std::set`` 218 | 219 | .. code-block:: cpp 220 | 221 | using TreeSet = BppTree::mixins::extractor>::Transient; 222 | TreeSet tree_set{}; 223 | tree_set.insert_or_assign(5); 224 | tree_set.insert_or_assign(8); 225 | tree_set.insert_or_assign(5); 226 | assert(tree_set.contains(5)); 227 | assert(tree_set.contains(8)); 228 | assert(tree_set.size == 2); 229 | 230 | Like BppTreeMap and BppTreeVector, there is also a convenience helper for making set-like B++ trees: 231 | 232 | .. code-block:: cpp 233 | 234 | using TreeSet = BppTreeSet::Transient; 235 | 236 | Take a look at examples/inverted_index.hpp for a neat implementation of an inverted index, complete with Transient and 237 | Persistent variations, implemented using B++ Trees. 238 | 239 | Interface Stability 240 | ------------------- 241 | 242 | Long term, I would like to maintain a stable API for B++ Trees, but as this project is quite new, I reserve the 243 | right to change the API in order to make the B++ Tree library better. That said, I am fairly happy with the API as it 244 | stands today and have no current plans to change it. 245 | 246 | Related Projects 247 | ------------------ 248 | 249 | `Immer `_ is an excellent library that provides many more persistent data 250 | structures such as: 251 | 252 | * Immutable arrays 253 | * Vectors and flex vectors implemented as Relaxed Radix Balanced Trees 254 | * Unordered sets, maps, and tables implemented as Hash Array Mapped Tries 255 | 256 | Immer and B++ Trees complement each other nicely. You might want to use B++ Trees if you need an ordered map or set, or 257 | if you need mixins for ordered statistics, prefix sums, or min/max. Indexed B++ Trees are faster than immer's 258 | flex_vector when inserting in the middle of a vector, but are slower for lookups. If you don't need ordering, mixins, or 259 | maximum performance for random inserts, the immer containers are probably a better choice. 260 | 261 | Table of Contents 262 | ================== 263 | .. toctree:: 264 | :maxdepth: 2 265 | 266 | self 267 | api 268 | -------------------------------------------------------------------------------- /docs/indexed.rst: -------------------------------------------------------------------------------- 1 | .. _api_indexed: 2 | 3 | Indexed 4 | ============= 5 | .. doxygenstruct:: bpptree::detail::Indexed 6 | :project: B++ Tree 7 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | 13 | %SPHINXBUILD% >NUL 2>NUL 14 | if errorlevel 9009 ( 15 | echo. 16 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 17 | echo.installed, then set the SPHINXBUILD environment variable to point 18 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 19 | echo.may add the Sphinx directory to PATH. 20 | echo. 21 | echo.If you don't have Sphinx installed, grab it from 22 | echo.https://www.sphinx-doc.org/ 23 | exit /b 1 24 | ) 25 | 26 | if "%1" == "" goto help 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /docs/max.rst: -------------------------------------------------------------------------------- 1 | .. _api_max: 2 | 3 | Max 4 | ============= 5 | .. doxygenstruct:: bpptree::detail::Max 6 | :project: B++ Tree 7 | -------------------------------------------------------------------------------- /docs/min.rst: -------------------------------------------------------------------------------- 1 | .. _api_min: 2 | 3 | Min 4 | ============= 5 | .. doxygenstruct:: bpptree::detail::Min 6 | :project: B++ Tree 7 | -------------------------------------------------------------------------------- /docs/mixin_builder.rst: -------------------------------------------------------------------------------- 1 | .. _api_mixin_builder: 2 | 3 | Mixin Builders 4 | =============== 5 | 6 | OrderedBuilder 7 | ---------------- 8 | .. doxygenstruct:: bpptree::detail::OrderedBuilder 9 | :project: B++ Tree 10 | 11 | IndexedBuilder 12 | ---------------- 13 | .. doxygenstruct:: bpptree::detail::IndexedBuilder 14 | :project: B++ Tree 15 | 16 | SummedBuilder 17 | ---------------- 18 | .. doxygenstruct:: bpptree::detail::SummedBuilder 19 | :project: B++ Tree 20 | 21 | MinBuilder 22 | ---------------- 23 | .. doxygenstruct:: bpptree::detail::MinBuilder 24 | :project: B++ Tree 25 | 26 | MaxBuilder 27 | ---------------- 28 | .. doxygenstruct:: bpptree::detail::MaxBuilder 29 | :project: B++ Tree 30 | -------------------------------------------------------------------------------- /docs/ordered.rst: -------------------------------------------------------------------------------- 1 | .. _api_ordered: 2 | 3 | Ordered 4 | ============= 5 | 6 | Most of the time when using Ordered you will want the value type to be a std::pair. Ordered can support other value 7 | types, such as std::tuple (using KeyValueExtractor = TupleExtractor) and even arbitrary user defined classes, but they 8 | require a carefully crafted KeyValueExtractor to work properly. 9 | 10 | .. doxygenstruct:: bpptree::detail::Ordered 11 | :project: B++ Tree 12 | -------------------------------------------------------------------------------- /docs/summed.rst: -------------------------------------------------------------------------------- 1 | .. _api_summed: 2 | 3 | Summed 4 | ============= 5 | .. doxygenstruct:: bpptree::detail::Summed 6 | :project: B++ Tree 7 | -------------------------------------------------------------------------------- /examples/inverted_index.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include "bpptree/bpptree.hpp" 6 | #include "bpptree/ordered.hpp" 7 | #include "bpptree/summed.hpp" 8 | #include "bpptree/indexed.hpp" 9 | 10 | template 11 | struct InvertedIndex { 12 | 13 | using TermList = typename bpptree::BppTreeMap 14 | ::template leaf_node_bytes 15 | ::template internal_node_bytes 16 | ::template depth_limit 17 | ::template mixins>>; 18 | 19 | using DocList = typename bpptree::BppTreeVector 20 | ::size_type 21 | ::leaf_node_bytes 22 | ::template internal_node_bytes 23 | ::template depth_limit; 24 | 25 | template 26 | struct Shared { 27 | 28 | [[nodiscard]] Derived& self() { 29 | return *static_cast(this); 30 | } 31 | 32 | [[nodiscard]] Derived const& self() const { 33 | return *static_cast(this); 34 | } 35 | 36 | template ::value, bool> = true> 37 | [[nodiscard]] auto term_doc_iterator(It const& it) const { 38 | auto measure = self().term_list().sum_inclusive(it); 39 | return std::make_pair( 40 | self().doc_list().find_index(measure - it->second), 41 | self().doc_list().find_index(measure) 42 | ); 43 | } 44 | 45 | [[nodiscard]] auto term_doc_iterator(Term const& term) const { 46 | auto it = self().term_list().find(term); 47 | return term_doc_iterator2(it); 48 | } 49 | }; 50 | 51 | class Persistent; 52 | 53 | class Transient : public Shared { 54 | friend struct Shared; 55 | 56 | typename TermList::Transient term_list_{}; 57 | typename DocList::Transient doc_list_{}; 58 | public: 59 | Transient() = default; 60 | private: 61 | Transient(typename TermList::Transient&& term_list, typename DocList::Transient&& doc_list) : 62 | Shared(), term_list_(std::move(term_list)), doc_list_(std::move(doc_list)) {} 63 | 64 | [[nodiscard]] typename DocList::Transient const& doc_list() const { 65 | return doc_list_; 66 | } 67 | 68 | public: 69 | [[nodiscard]] Persistent persistent() & { 70 | return Persistent(term_list_.persistent(), doc_list_.persistent()); 71 | } 72 | 73 | [[nodiscard]] Persistent persistent() && { 74 | return Persistent(std::move(term_list_).persistent(), std::move(doc_list_).persistent()); 75 | } 76 | 77 | [[nodiscard]] typename TermList::Transient const& term_list() const { 78 | return term_list_; 79 | } 80 | 81 | template 82 | void insert(T&& term, uint32_t doc_id) { 83 | auto it = term_list_.lower_bound(term); 84 | if (it->first != term) { 85 | term_list_.insert(it, std::forward(term), 0u); 86 | } 87 | auto [begin, end] = this->term_doc_iterator(it); 88 | auto doc_it = std::lower_bound(begin, end, doc_id); 89 | if (doc_it == end || *doc_it != doc_id) { 90 | doc_list_.insert(doc_it, doc_id); 91 | term_list_.update(it, [](auto const& p){ return std::make_pair(p.first, p.second + 1); }); 92 | } 93 | } 94 | }; 95 | 96 | class Persistent : public Shared { 97 | friend struct Shared; 98 | 99 | typename TermList::Persistent term_list_{}; 100 | typename DocList::Persistent doc_list_{}; 101 | public: 102 | Persistent() = default; 103 | private: 104 | Persistent(typename TermList::Persistent&& term_list, typename DocList::Persistent&& doc_list) : 105 | Shared(), term_list_(std::move(term_list)), doc_list_(std::move(doc_list)) {} 106 | 107 | [[nodiscard]] typename DocList::Persistent const& doc_list() const { 108 | return doc_list_; 109 | } 110 | 111 | public: 112 | [[nodiscard]] Transient transient() const& { 113 | return Transient(term_list_.transient(), doc_list_.transient()); 114 | } 115 | 116 | [[nodiscard]] Transient transient() && { 117 | return Transient(std::move(term_list_).transient(), std::move(doc_list_).transient()); 118 | } 119 | 120 | [[nodiscard]] typename TermList::Persistent const& term_list() const { 121 | return term_list_; 122 | } 123 | 124 | template 125 | [[nodiscard]] Persistent insert(T&& term, uint32_t doc_id) const { 126 | Transient transient(transient()); 127 | transient.insert(std::forward(term), doc_id); 128 | return std::move(transient).persistent(); 129 | } 130 | }; 131 | }; 132 | -------------------------------------------------------------------------------- /include/bpptree/detail/common.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // B++ Tree: A B+ Tree library written in C++ 3 | // Copyright (C) 2023 Jeff Plaisance 4 | // 5 | // This software is distributed under the Boost Software License, Version 1.0. 6 | // See accompanying file LICENSE or copy at http://boost.org/LICENSE_1_0.txt 7 | // 8 | 9 | #pragma once 10 | 11 | namespace bpptree::detail { 12 | 13 | using ssize = std::make_signed_t; 14 | 15 | } //end namespace bpptree::detail 16 | -------------------------------------------------------------------------------- /include/bpptree/detail/helpers.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // B++ Tree: A B+ Tree library written in C++ 3 | // Copyright (C) 2023 Jeff Plaisance 4 | // 5 | // This software is distributed under the Boost Software License, Version 1.0. 6 | // See accompanying file LICENSE or copy at http://boost.org/LICENSE_1_0.txt 7 | // 8 | 9 | #pragma once 10 | 11 | #include 12 | #include 13 | #include "nodeptr.hpp" 14 | 15 | namespace bpptree { 16 | namespace detail { 17 | 18 | using IndexType = std::int_fast32_t; 19 | 20 | // Stack unwinding code generated by apple clang is enormous, so by default a few methods are marked noexcept in order 21 | // to reduce the binary size. If you want to use exceptions with apple clang, define BPPTREE_DISABLE_EXCEPTIONS to be 22 | // false. 23 | static constexpr bool default_disable_exceptions = 24 | #ifndef BPPTREE_DISABLE_EXCEPTIONS 25 | #if defined(__clang__) && defined(__apple_build_version__) 26 | true; 27 | #else 28 | false; 29 | #endif 30 | #else 31 | BPPTREE_DISABLE_EXCEPTIONS; 32 | #endif 33 | 34 | template 35 | struct TupleExtractor { 36 | template 37 | auto const& operator()(std::tuple const& t) const { 38 | return std::get(t); 39 | } 40 | 41 | template 42 | auto const& operator()(Ts const&... ts) const { 43 | return std::get(std::forward_as_tuple(ts...)); 44 | } 45 | 46 | template 47 | auto const& get_key(std::tuple const& t) const { 48 | return std::get(t); 49 | } 50 | 51 | template 52 | auto const& get_key(Ts const&... ts) const { 53 | return std::get(std::forward_as_tuple(ts...)); 54 | } 55 | 56 | template 57 | decltype(auto) apply(F&& f, Key const& key, V&& value) const { 58 | static_assert(index == 0 || index == 1); 59 | if constexpr (index == 0) { 60 | return f(key, std::forward(value)); 61 | } else { 62 | return f(std::forward(value), key); 63 | } 64 | } 65 | 66 | template 67 | auto const& get_value(std::tuple const& t) const { 68 | return t; 69 | } 70 | 71 | template 72 | auto const& get_value(std::tuple const& t) const { 73 | static_assert(index == 0 || index == 1); 74 | return std::get<1-index>(t); 75 | } 76 | }; 77 | 78 | template 79 | struct PairExtractor { 80 | static_assert(index == 0 || index == 1); 81 | 82 | template 83 | auto const& operator()(std::pair const& t) const { 84 | return std::get(t); 85 | } 86 | 87 | template 88 | auto const& operator()(First const& first, Second const& second) const { 89 | if constexpr (index == 0) { 90 | return first; 91 | } else { 92 | return second; 93 | } 94 | } 95 | 96 | template 97 | auto const& operator()(std::piecewise_construct_t, std::tuple const& first_args, std::tuple const& second_args) const { 98 | if constexpr (index == 0) { 99 | static_assert(sizeof...(Args1) == 1); 100 | return std::get<0>(first_args); 101 | } else { 102 | static_assert(sizeof...(Args2) == 1); 103 | return std::get<0>(second_args); 104 | } 105 | } 106 | 107 | template 108 | auto const& get_key(std::pair const& t) const { 109 | return operator()(t); 110 | } 111 | 112 | template 113 | auto const& get_key(First const& first, Second const& second) const { 114 | return operator()(first, second); 115 | } 116 | 117 | template 118 | auto const& get_key(std::piecewise_construct_t, std::tuple const& first_args, std::tuple const& second_args) const { 119 | return operator()(std::piecewise_construct, first_args, second_args); 120 | } 121 | 122 | template 123 | decltype(auto) apply(F&& f, Key const& key, V&& value) const { 124 | if constexpr (index == 0) { 125 | return f(key, std::forward(value)); 126 | } else { 127 | return f(std::forward(value), key); 128 | } 129 | } 130 | 131 | template 132 | auto const& get_value(std::pair const& p) const { 133 | return std::get<1-index>(p); 134 | } 135 | }; 136 | 137 | struct MinComparator { 138 | template 139 | bool operator()(T const& t, U const& u) const { 140 | return t < u; 141 | } 142 | }; 143 | 144 | struct MaxComparator { 145 | template 146 | bool operator()(T const& t, U const& u) const { 147 | return u < t; 148 | } 149 | }; 150 | 151 | enum struct Empty { 152 | empty 153 | }; 154 | 155 | struct ValueExtractor { 156 | template 157 | V const& operator()(V const& v) const { 158 | return v; 159 | } 160 | 161 | template 162 | V const& get_key(V const& v) const { 163 | return v; 164 | } 165 | 166 | template 167 | V const& get_value(V const& v) const { 168 | return v; 169 | } 170 | 171 | template 172 | decltype(auto) apply(F&& f, Key const&, V&& value) const { 173 | return f(std::forward(value)); 174 | } 175 | }; 176 | 177 | template 178 | struct CastingExtractor { 179 | template 180 | T operator()(Value const& value) const { 181 | return static_cast(value); 182 | } 183 | }; 184 | 185 | template 186 | struct WrappedCastingExtractor { 187 | static constexpr Extractor extractor{}; 188 | 189 | template 190 | T operator()(Args const&... args) const { 191 | return static_cast(extractor(args...)); 192 | } 193 | }; 194 | 195 | template 196 | struct Replace { 197 | NodeInfoType delta{}; 198 | // this is only used for fixing the iterator when erasing an element 199 | // if carry is true, the last element in the child was erased and the iterator should point at the first element of 200 | // the next child 201 | bool carry = false; 202 | }; 203 | 204 | template 205 | struct Split { 206 | NodeInfoType left; 207 | NodeInfoType right; 208 | // this is only used for fixing the iterator when inserting an element 209 | // if new_element_left is true then the element that was inserted is in the left child, otherwise it is in the right 210 | bool new_element_left; 211 | 212 | template 213 | Split(LP&& left_ptr, NodePtr&& right_ptr, bool left_changed, bool new_element_left) : 214 | left(std::forward(left_ptr), left_changed), 215 | right(std::move(right_ptr), true), 216 | new_element_left(new_element_left) {} 217 | }; 218 | 219 | template 220 | inline constexpr int bits_required2() { 221 | if constexpr ((1ULL << attempt) - 1 >= i) { 222 | return attempt; 223 | } else { 224 | return bits_required2(); 225 | } 226 | } 227 | 228 | template 229 | inline constexpr int bits_required() { 230 | return bits_required2(); 231 | } 232 | 233 | template 234 | struct IsTreeIterator : std::false_type {}; 235 | 236 | template 237 | struct IsTreeIterator().iter), decltype(std::declval().leaf)>> : std::true_type {}; 238 | 239 | template 240 | struct IsIndexedTree : std::false_type {}; 241 | 242 | template 243 | struct IsIndexedTree().order(std::declval()))>> : std::true_type {}; 244 | 245 | template 246 | struct IsTransientTree : std::true_type {}; 247 | 248 | template 249 | struct IsTransientTree().transient())>> : std::false_type {}; 250 | 251 | enum struct DuplicatePolicy { 252 | replace, 253 | ignore, 254 | insert 255 | }; 256 | 257 | } //end namespace detail 258 | using detail::TupleExtractor; 259 | using detail::PairExtractor; 260 | using detail::ValueExtractor; 261 | } //end namespace bpptree 262 | -------------------------------------------------------------------------------- /include/bpptree/detail/indexed_detail.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // B++ Tree: A B+ Tree library written in C++ 3 | // Copyright (C) 2023 Jeff Plaisance 4 | // 5 | // This software is distributed under the Boost Software License, Version 1.0. 6 | // See accompanying file LICENSE or copy at http://boost.org/LICENSE_1_0.txt 7 | // 8 | 9 | #pragma once 10 | 11 | #include 12 | #include 13 | #include "helpers.hpp" 14 | 15 | namespace bpptree::detail { 16 | 17 | template 18 | struct IndexedLeafNode : public Parent { 19 | 20 | using InfoType = typename Parent::InfoType; 21 | 22 | auto find_index(SizeType search_val) const { 23 | return std::tuple(search_val - 1, 0); 24 | } 25 | 26 | auto insertion_index(SizeType search_val) const { 27 | return std::tuple(search_val, 0); 28 | } 29 | 30 | Value const& at_index(SizeType search_val) const { 31 | return this->values[search_val - 1]; 32 | } 33 | 34 | template 35 | void seek_index( 36 | I& it, 37 | SizeType index 38 | ) const { 39 | this->set_index(it.iter, index - 1); 40 | it.leaf = &this->self(); 41 | } 42 | 43 | template 44 | void compute_delta_insert2(IndexType index, InfoType& node_info, Args const&... args) const { 45 | node_info.children = 1; 46 | Parent::compute_delta_insert2(index, node_info, args...); 47 | } 48 | 49 | template 50 | void compute_delta_set2(IndexType index, InfoType& node_info, Args const&... args) const { 51 | Parent::compute_delta_set2(index, node_info, args...); 52 | } 53 | 54 | void compute_delta_erase2(IndexType index, InfoType& node_info) const { 55 | if constexpr (std::is_unsigned_v) { 56 | node_info.children = std::numeric_limits::max(); 57 | } else { 58 | node_info.children = -1; 59 | } 60 | Parent::compute_delta_erase2(index, node_info); 61 | } 62 | 63 | SizeType children() const { 64 | return this->length; 65 | } 66 | 67 | void order(uint64_t it, SizeType& size) const { 68 | size += static_cast(this->get_index(it)); 69 | } 70 | }; 71 | 72 | template 73 | struct IndexedInternalNode : public Parent { 74 | 75 | using NodeType = typename Parent::NodeType; 76 | 77 | using ChildType = typename Parent::ChildType; 78 | 79 | template 80 | using InfoType = typename Parent::template InfoType; 81 | 82 | template 83 | using SplitType = typename Parent::template SplitType; 84 | 85 | SizeType child_counts[internal_size]{}; 86 | 87 | void move_element2(IndexType dest_index, NodeType& source, IndexType source_index) { 88 | child_counts[dest_index] = std::move(source.child_counts[source_index]); 89 | Parent::move_element2(dest_index, source, source_index); 90 | } 91 | 92 | void copy_element2(IndexType dest_index, NodeType const& source, IndexType source_index) { 93 | child_counts[dest_index] = source.child_counts[source_index]; 94 | Parent::copy_element2(dest_index, source, source_index); 95 | } 96 | 97 | void replace_element2(IndexType index, InfoType& t) { 98 | child_counts[index] += t.children; 99 | Parent::replace_element2(index, t); 100 | } 101 | 102 | void set_element2(IndexType index, InfoType& t) { 103 | child_counts[index] = t.children; 104 | Parent::set_element2(index, t); 105 | } 106 | 107 | void compute_delta_split2(SplitType const& split, InfoType& node_info, IndexType index) const { 108 | node_info.children = split.left.children + split.right.children - child_counts[index]; 109 | Parent::compute_delta_split2(split, node_info, index); 110 | } 111 | 112 | void compute_delta_replace2(InfoType const& update, InfoType& node_info, IndexType index) const { 113 | node_info.children = update.children; 114 | Parent::compute_delta_replace2(update, node_info, index); 115 | } 116 | 117 | void compute_delta_erase2(IndexType index, InfoType& node_info) const { 118 | if constexpr (std::is_unsigned_v) { 119 | node_info.children = ~child_counts[index] + 1; 120 | } else { 121 | node_info.children = -child_counts[index]; 122 | } 123 | Parent::compute_delta_erase2(index, node_info); 124 | } 125 | 126 | auto find_index(SizeType search_val) const { 127 | std::tuple ret(0, search_val); 128 | auto& [index, remainder] = ret; 129 | while (index < this->length - 1) { 130 | if (child_counts[index] < remainder) { 131 | remainder -= child_counts[index]; 132 | ++index; 133 | } else { 134 | break; 135 | } 136 | } 137 | return ret; 138 | } 139 | 140 | auto insertion_index(SizeType search_val) const { 141 | return find_index(search_val); 142 | } 143 | 144 | Value const& at_index(SizeType search_val) const { 145 | auto [index, remainder] = find_index(search_val); 146 | return this->pointers[index]->at_index(remainder); 147 | } 148 | 149 | template 150 | void seek_index( 151 | I& it, 152 | SizeType search_val 153 | ) const { 154 | auto [index, remainder] = find_index(search_val); 155 | this->set_index(it.iter, index); 156 | this->pointers[this->get_index(it.iter)]->seek_index(it, remainder); 157 | } 158 | 159 | SizeType children() const { 160 | SizeType ret = 0; 161 | for (IndexType i = 0; i < this->length; ++i) { 162 | ret += child_counts[i]; 163 | } 164 | return ret; 165 | } 166 | 167 | void order(uint64_t it, SizeType& size) const { 168 | IndexType index = this->get_index(it); 169 | for (IndexType i = 0; i < index; ++i) { 170 | size += child_counts[i]; 171 | } 172 | this->pointers[index]->order(it, size); 173 | } 174 | 175 | template 176 | ssize advance(L const*& leaf, uint64_t& it, ssize n) const { 177 | start: 178 | n = this->pointers[this->get_index(it)]->advance(leaf, it, n); 179 | if (n > 0) { 180 | while (this->get_index(it) < this->length - 1) { 181 | this->inc_index(it); 182 | if (static_cast(child_counts[this->get_index(it)]) < n) { 183 | n -= static_cast(child_counts[this->get_index(it)]); 184 | } else { 185 | this->pointers[this->get_index(it)]->seek_first(it); 186 | --n; 187 | goto start; 188 | } 189 | } 190 | return n; 191 | } 192 | if (n < 0) { 193 | while (this->get_index(it) > 0) { 194 | this->dec_index(it); 195 | if (static_cast(child_counts[this->get_index(it)]) < -n) { 196 | n += static_cast(child_counts[this->get_index(it)]); 197 | } else { 198 | this->pointers[this->get_index(it)]->seek_last(it); 199 | ++n; 200 | goto start; 201 | } 202 | } 203 | return n; 204 | } 205 | return 0; 206 | } 207 | }; 208 | 209 | template 210 | struct IndexedNodeInfo : public Parent { 211 | SizeType children{}; 212 | 213 | IndexedNodeInfo() = default; 214 | 215 | template 216 | IndexedNodeInfo(P const& p, const bool changed) : Parent(p, changed), children(p->children()) {} 217 | }; 218 | } 219 | -------------------------------------------------------------------------------- /include/bpptree/detail/iterator.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // B++ Tree: A B+ Tree library written in C++ 3 | // Copyright (C) 2023 Jeff Plaisance 4 | // 5 | // This software is distributed under the Boost Software License, Version 1.0. 6 | // See accompanying file LICENSE or copy at http://boost.org/LICENSE_1_0.txt 7 | // 8 | 9 | #pragma once 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include "common.hpp" 16 | #include "helpers.hpp" 17 | #include "proxy_operators.hpp" 18 | 19 | namespace bpptree::detail { 20 | 21 | template 22 | struct IteratorBase { 23 | explicit IteratorBase(std::conditional_t&) {} 24 | }; 25 | 26 | template 27 | struct IteratorBase::value>> { 28 | mutable uint64_t mod_count; 29 | 30 | explicit IteratorBase(std::conditional_t& tree) : mod_count(tree.mod_count) {} 31 | }; 32 | 33 | template 34 | struct IteratorDetail : public IteratorBase { 35 | using Parent = IteratorBase; 36 | 37 | using TreeType = std::conditional_t; 38 | 39 | static constexpr bool is_transient_tree = IsTransientTree::value; 40 | 41 | static constexpr int direction = reverse ? -1 : 1; 42 | 43 | static constexpr bool is_reversed = reverse; 44 | 45 | static constexpr uint64_t rend = std::numeric_limits::max(); 46 | 47 | uint64_t iter = 0; 48 | TreeType* tree; 49 | mutable LeafNode const* leaf; 50 | 51 | explicit IteratorDetail(TreeType& tree) : Parent(tree), tree(&tree) {} 52 | 53 | void fix_leaf() const { 54 | if constexpr (is_transient_tree) { 55 | if (this->mod_count != tree->mod_count) { 56 | std::as_const(*tree).dispatch([this](auto const& root) { 57 | // make copy of iter since this is const and iter is not mutable. 58 | // advancing by 0 won't actually change tmp_iter. 59 | uint64_t tmp_iter = iter; 60 | root->advance(leaf, tmp_iter, 0); 61 | }); 62 | this->mod_count = tree->mod_count; 63 | } 64 | } 65 | } 66 | 67 | [[nodiscard]] Value const& get() const { 68 | fix_leaf(); 69 | return leaf->get_iter(iter); 70 | } 71 | 72 | bool valid() const { 73 | if (iter == rend) { 74 | return false; 75 | } 76 | fix_leaf(); 77 | return LeafNode::get_index(iter) < leaf->length; 78 | } 79 | 80 | struct ProxyRef : public ProxyOperators { 81 | private: 82 | friend struct ProxyOperators; 83 | 84 | IteratorDetail const& it; 85 | 86 | ProxyRef(const ProxyRef& other) = default; 87 | ProxyRef(ProxyRef&& other) = default; //NOLINT 88 | ProxyRef& operator=(const ProxyRef& other) = default; //NOLINT 89 | ProxyRef& operator=(ProxyRef&& other) = default; //NOLINT 90 | 91 | template 92 | ProxyRef& invoke_compound_assignment(F&& f) { 93 | it.tree->update(it, f); 94 | return *this; 95 | } 96 | 97 | template 98 | ProxyRef& invoke_pre(F&& f) { 99 | it.tree->update(it, [&f](auto const& v){ 100 | Value copy(v); 101 | f(copy); 102 | return copy; 103 | }); 104 | return *this; 105 | } 106 | 107 | template 108 | Value invoke_post(F&& f) { 109 | Value ret; 110 | it.tree->update(it, [&f, &ret](auto const& v){ 111 | Value copy(v); 112 | ret = f(copy); 113 | return copy; 114 | }); 115 | return ret; 116 | } 117 | public: 118 | explicit ProxyRef(IteratorDetail const& it) : it(it) {} 119 | ~ProxyRef() = default; 120 | 121 | template 122 | ProxyRef& operator=(T&& t) { 123 | Value const& v = t; 124 | it.tree->assign(it, v); 125 | return *this; 126 | } 127 | 128 | operator Value const&() const { //NOLINT 129 | return it.get(); 130 | } 131 | 132 | [[nodiscard]] Value const& get() const { 133 | return it.get(); 134 | } 135 | 136 | friend void swap(ProxyRef&& a, ProxyRef&& b) { 137 | Value tmp = a; 138 | Value const& bv = b; 139 | a.it.tree->assign(a.it, bv); 140 | b.it.tree->assign(b.it, std::move(tmp)); 141 | } 142 | }; 143 | 144 | template = true> 145 | [[nodiscard]] ProxyRef operator*() { 146 | static_assert(c == is_const); 147 | return ProxyRef(*this); 148 | } 149 | 150 | template = true> 151 | [[nodiscard]] Value const& operator*() const { 152 | static_assert(c == is_const); 153 | return get(); 154 | } 155 | 156 | [[nodiscard]] Value const* operator->() const { 157 | return &get(); 158 | } 159 | 160 | void advance(ssize n) { 161 | n *= direction; 162 | ssize remainder; 163 | if (iter == rend) { 164 | if (n <= 0) { 165 | return; 166 | } 167 | iter = 0; 168 | if (n == 1) { 169 | std::as_const(*tree).dispatch([this](auto const& root) { root->seek_begin(leaf, iter); }); 170 | if constexpr (is_transient_tree) { 171 | this->mod_count = tree->mod_count; 172 | } 173 | return; 174 | } 175 | remainder = n - 1; 176 | } else if constexpr (!is_transient_tree) { //NOLINT 177 | remainder = leaf->advance(leaf, iter, n); 178 | } else if (this->mod_count == tree->mod_count) { 179 | remainder = leaf->advance(leaf, iter, n); 180 | } else { 181 | remainder = n; 182 | } 183 | if (remainder != 0) { 184 | std::as_const(*tree).dispatch([this, remainder](auto const& root) { 185 | auto r = root->advance(leaf, iter, remainder); 186 | if (r > 0) { 187 | root->seek_end(leaf, iter); 188 | } 189 | if (r < 0) { 190 | leaf = nullptr; 191 | iter = rend; 192 | } 193 | }); 194 | if constexpr (is_transient_tree) { 195 | this->mod_count = tree->mod_count; 196 | } 197 | } 198 | } 199 | 200 | auto& operator++() { 201 | advance(1); 202 | return *this; 203 | } 204 | 205 | auto operator++(int) & { //NOLINT 206 | auto ret = *this; 207 | advance(1); 208 | return ret; 209 | } 210 | 211 | auto& operator--() { 212 | advance(-1); 213 | return *this; 214 | } 215 | 216 | auto operator--(int) & { //NOLINT 217 | auto ret = *this; 218 | advance(-1); 219 | return ret; 220 | } 221 | 222 | auto& operator+=(ssize n) { 223 | advance(n); 224 | return *this; 225 | } 226 | 227 | auto& operator-=(ssize n) { 228 | advance(-n); 229 | return *this; 230 | } 231 | 232 | [[nodiscard]] auto operator+(ssize n) const { 233 | IteratorDetail ret(*this); 234 | ret += n; 235 | return ret; 236 | } 237 | 238 | [[nodiscard]] auto operator-(ssize n) const { 239 | IteratorDetail ret(*this); 240 | ret -= n; 241 | return ret; 242 | } 243 | 244 | [[nodiscard]] Value const& operator[](int64_t n) const { 245 | return *(*this + n); 246 | } 247 | 248 | template ::value && IsIndexedTree::value, bool> = true> 249 | [[nodiscard]] auto operator-(T const& it) const { 250 | if constexpr (T::is_reversed) { 251 | ssize ret = iter == rend ? 1 : -static_cast(tree->order(*this)); 252 | ret += it.iter == rend ? -1 : static_cast(tree->order(it)); 253 | return ret; 254 | } else { 255 | return static_cast(tree->order(*this)) - static_cast(tree->order(it)); 256 | } 257 | } 258 | 259 | template 260 | [[nodiscard]] bool operator<(RHS const& rhs) const { 261 | static_assert(reverse == RHS::is_reversed); 262 | #ifdef BPPTREE_SAFETY_CHECKS 263 | if (&tree->self() != &rhs.tree->self()) { 264 | throw std::logic_error("cannot compare iterators from different trees"); 265 | } 266 | #endif 267 | return reverse ^ (iter + 1 < rhs.iter + 1); 268 | } 269 | 270 | template 271 | [[nodiscard]] bool operator==(RHS const& rhs) const { 272 | if (&tree->self() != &rhs.tree->self()) { 273 | return false; 274 | } 275 | return iter == rhs.iter; 276 | } 277 | 278 | template 279 | [[nodiscard]] bool operator>(RHS const& rhs) const { 280 | return rhs < *this; 281 | } 282 | 283 | template 284 | [[nodiscard]] bool operator<=(RHS const& rhs) const { 285 | return !(rhs < *this); //NOLINT 286 | } 287 | 288 | template 289 | [[nodiscard]] bool operator>=(RHS const& rhs) const { 290 | return !(*this < rhs); //NOLINT 291 | } 292 | 293 | template 294 | [[nodiscard]] bool operator!=(RHS const& rhs) const { 295 | return !(*this == rhs); //NOLINT 296 | } 297 | 298 | using difference_type = ssize; 299 | 300 | using value_type = Value; 301 | 302 | using pointer = void; 303 | 304 | using reference = std::conditional_t; 305 | 306 | using iterator_category = std::conditional_t< 307 | IsIndexedTree::value, 308 | std::random_access_iterator_tag, 309 | std::bidirectional_iterator_tag>; 310 | }; 311 | } //end namespace bpptree::detail 312 | -------------------------------------------------------------------------------- /include/bpptree/detail/leafnodebase.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // B++ Tree: A B+ Tree library written in C++ 3 | // Copyright (C) 2023 Jeff Plaisance 4 | // 5 | // This software is distributed under the Boost Software License, Version 1.0. 6 | // See accompanying file LICENSE or copy at http://boost.org/LICENSE_1_0.txt 7 | // 8 | 9 | #pragma once 10 | 11 | #include "uninitialized_array.hpp" 12 | #include "common.hpp" 13 | 14 | namespace bpptree::detail { 15 | 16 | template 17 | struct LeafNodeBase : public Parent { 18 | 19 | using NodeType = typename Parent::SelfType; 20 | 21 | using InfoType = typename Parent::template NodeInfoType; 22 | 23 | using ReplaceType = Replace; 24 | 25 | using SplitType = Split; 26 | 27 | static constexpr int depth = 1; 28 | 29 | static constexpr int it_shift = 0; 30 | 31 | static constexpr int it_bits = bits_required(); 32 | 33 | static constexpr uint64_t it_mask = (1ULL << it_bits) - 1; 34 | 35 | static constexpr uint64_t it_clear = ~(it_mask << it_shift); 36 | 37 | UninitializedArray values; 38 | 39 | LeafNodeBase() = default; 40 | 41 | LeafNodeBase(LeafNodeBase const& other) : Parent(other), values(other.values, other.length) {} 42 | 43 | LeafNodeBase& operator=(LeafNodeBase const& other) = delete; 44 | 45 | ~LeafNodeBase() { 46 | for (IndexType i = 0; i < this->length; ++i) { 47 | values.destruct(i); 48 | } 49 | } 50 | 51 | static IndexType get_index(uint64_t it) { 52 | return (it >> it_shift) & it_mask; 53 | } 54 | 55 | static void clear_index(uint64_t& it) { 56 | it = it & it_clear; 57 | } 58 | 59 | template , bool> = true> 60 | static void set_index(uint64_t& it, T const index) noexcept { 61 | clear_index(it); 62 | it = it | (static_cast(index) << it_shift); 63 | } 64 | 65 | template 66 | void compute_delta_insert(IndexType index, InfoType& node_info, Args const&... args) const { 67 | this->self().compute_delta_insert2(index, node_info, args...); 68 | } 69 | 70 | template 71 | void compute_delta_insert2(IndexType, InfoType&, Args const&...) const {} 72 | 73 | template 74 | void compute_delta_set(IndexType index, InfoType& node_info, Args const&... args) const { 75 | this->self().compute_delta_set2(index, node_info, args...); 76 | } 77 | 78 | template 79 | void compute_delta_set2(IndexType, InfoType&, Args const&...) const {} 80 | 81 | void compute_delta_erase(IndexType index, InfoType& node_info) const { 82 | this->self().compute_delta_erase2(index, node_info); 83 | } 84 | 85 | void compute_delta_erase2(IndexType, InfoType&) const {} 86 | 87 | template 88 | void insert_no_split(LeafNodeBase& node, IndexType index, Args&&... args) noexcept(disable_exceptions) { 89 | for (IndexType i = this->length; i > index; --i) { 90 | if (this->persistent) { 91 | node.values.set(i, node.length, values[i - 1]); 92 | } else { 93 | node.values.set(i, node.length, values.move(i - 1)); 94 | } 95 | } 96 | node.values.emplace(index, node.length, std::forward(args)...); 97 | if (this->persistent) { 98 | for (IndexType i = 0; i < index; ++i) { 99 | node.values.set(i, node.length, values[i]); 100 | } 101 | } 102 | node.length = this->length + 1; 103 | } 104 | 105 | template 106 | void set_element(LeafNodeBase& left, LeafNodeBase& right, IndexType index, IndexType split_point, T&& t) { 107 | if (index < split_point) { 108 | left.values.set(index, left.length, std::forward(t)); 109 | } else { 110 | right.values.set(index - split_point, right.length, std::forward(t)); 111 | } 112 | } 113 | 114 | template 115 | bool insert_split(LeafNodeBase& left, LeafNodeBase& right, IndexType index, uint64_t& iter, bool right_most, Args&&... args) noexcept(disable_exceptions) { 116 | IndexType split_point = right_most && index == leaf_size ? index : (leaf_size + 1) / 2; 117 | for (IndexType i = this->length; i > index; --i) { 118 | if (this->persistent) { 119 | set_element(left, right, i, split_point, values[i - 1]); 120 | } else { 121 | set_element(left, right, i, split_point, std::move(values[i - 1])); 122 | } 123 | } 124 | if (index < split_point) { 125 | left.values.emplace(index, left.length, std::forward(args)...); 126 | } else { 127 | right.values.emplace(index - split_point, right.length, std::forward(args)...); 128 | } 129 | for (IndexType i = this->persistent ? 0 : split_point; i < index; ++i) { 130 | if (this->persistent) { 131 | set_element(left, right, i, split_point, values[i]); 132 | } else { 133 | set_element(left, right, i, split_point, std::move(values[i])); 134 | } 135 | } 136 | for (IndexType i = split_point; i < left.length; ++i) { 137 | left.values.destruct(i); 138 | } 139 | left.length = static_cast(split_point); 140 | right.length = static_cast(leaf_size + 1 - split_point); 141 | if (index >= split_point) { 142 | set_index(iter, index - split_point); 143 | return false; 144 | } 145 | set_index(iter, index); 146 | return true; 147 | } 148 | 149 | template 150 | void insert_index(IndexType index, R&& do_replace, S&& do_split, size_t& size, uint64_t& iter, bool right_most, Args&&... args) noexcept(disable_exceptions) { 151 | ++size; 152 | if (this->length != leaf_size) { 153 | set_index(iter, index); 154 | ReplaceType replace{}; 155 | compute_delta_insert(index, replace.delta, args...); 156 | if (this->persistent) { 157 | replace.delta.ptr = make_ptr(); 158 | replace.delta.ptr_changed = true; 159 | insert_no_split(*replace.delta.ptr, index, std::forward(args)...); 160 | do_replace(replace); 161 | } else { 162 | insert_no_split(this->self(), index, std::forward(args)...); 163 | do_replace(replace); 164 | } 165 | } else { 166 | auto right = make_ptr(); 167 | if (this->persistent) { 168 | NodePtr left = make_ptr(); 169 | bool new_element_left = insert_split(*left, *right, index, iter, right_most, std::forward(args)...); 170 | do_split(SplitType(std::move(left), std::move(right), true, new_element_left)); 171 | } else { 172 | bool new_element_left = insert_split(this->self(), *right, index, iter, right_most, std::forward(args)...); 173 | do_split(SplitType(&(this->self()), std::move(right), false, new_element_left)); 174 | } 175 | } 176 | } 177 | 178 | template 179 | void insert(T const& search_val, F&& finder, R&& do_replace, S&& do_split, size_t& size, uint64_t& iter, bool right_most, Args&&... args) { 180 | auto [index, remainder] = finder(this->self(), search_val); 181 | insert_index(index, do_replace, do_split, size, iter, right_most, std::forward(args)...); 182 | } 183 | 184 | template 185 | void assign2(IndexType index, R&& do_replace, uint64_t& iter, Args&&... args) { 186 | set_index(iter, index); 187 | ReplaceType replace{}; 188 | compute_delta_set(index, replace.delta, args...); 189 | if (this->persistent) { 190 | replace.delta.ptr = make_ptr(this->self()); 191 | replace.delta.ptr_changed = true; 192 | replace.delta.ptr->values.emplace(index, replace.delta.ptr->length, std::forward(args)...); 193 | do_replace(replace); 194 | } else { 195 | values.emplace_unchecked(index, std::forward(args)...); 196 | do_replace(replace); 197 | } 198 | } 199 | 200 | template 201 | void assign(T const& search_val, F&& finder, R&& do_replace, uint64_t& iter, Args&&... args) { 202 | auto [index, remainder] = finder(this->self(), search_val); 203 | assign2(index, do_replace, iter, std::forward(args)...); 204 | } 205 | 206 | bool erase(LeafNodeBase& node, IndexType index, uint64_t& iter, bool right_most) { 207 | if (this->persistent) { 208 | for (IndexType i = 0; i < index; ++i) { 209 | node.values.set(i, node.length, values[i]); 210 | } 211 | } 212 | for (IndexType i = index + 1; i < this->length; ++i) { 213 | if (this->persistent) { 214 | node.values.set(i - 1, node.length, values[i]); 215 | } else { 216 | node.values.set(i - 1, node.length, values.move(i)); 217 | } 218 | } 219 | if (node.length == this->length) { 220 | node.values.destruct(this->length - 1); 221 | } 222 | node.length = this->length - 1; 223 | bool carry = index == node.length && !right_most; 224 | set_index(iter, carry ? 0 : index); 225 | return carry; 226 | } 227 | 228 | template 229 | void erase(IndexType index, R&& do_replace, E&& do_erase, uint64_t& iter, bool right_most) { 230 | if (this->length > 1) { 231 | ReplaceType replace{}; 232 | compute_delta_erase(index, replace.delta); 233 | if (this->persistent) { 234 | replace.delta.ptr = make_ptr(); 235 | replace.delta.ptr_changed = true; 236 | replace.carry = erase(*replace.delta.ptr, index, iter, right_most); 237 | do_replace(replace); 238 | } else { 239 | replace.carry = erase(this->self(), index, iter, right_most); 240 | do_replace(replace); 241 | } 242 | } else { 243 | set_index(iter, 0); 244 | do_erase(); 245 | } 246 | } 247 | 248 | template 249 | void erase(T const& search_val, F&& finder, R&& do_replace, E&& do_erase, size_t& size, uint64_t& iter, bool right_most) { 250 | auto [index, remainder] = finder(this->self(), search_val); 251 | --size; 252 | erase(index, do_replace, do_erase, iter, right_most); 253 | } 254 | 255 | template 256 | void update(T const& search_val, F&& finder, R&& do_replace, uint64_t& iter, U&& updater) { 257 | auto [index, remainder] = finder(this->self(), search_val); 258 | assign2(index, do_replace, iter, updater(std::as_const(values[index]))); 259 | } 260 | 261 | template 262 | void update2(T const& search_val, F&& finder, R&& do_replace, uint64_t& iter, U&& updater) { 263 | auto [index, remainder] = finder(this->self(), search_val); 264 | updater( 265 | [this, index = index, &do_replace, &iter](auto&&... args) 266 | // clang incorrectly warns on unused capture without this-> before assign2 267 | { this->assign2(index, do_replace, iter, std::forward(args)...); }, 268 | std::as_const(values[index])); 269 | } 270 | 271 | Value const& get_iter(uint64_t it) const { 272 | return values[get_index(it)]; 273 | } 274 | 275 | void make_persistent() { 276 | this->persistent = true; 277 | } 278 | 279 | void seek_first(uint64_t& it) const { 280 | clear_index(it); 281 | } 282 | 283 | void seek_last(uint64_t& it) const { 284 | set_index(it, this->length - 1); 285 | } 286 | 287 | void seek_end(uint64_t& it) const { 288 | set_index(it, this->length); 289 | } 290 | 291 | void seek_begin(typename LeafNodeBase::SelfType const*& leaf, uint64_t& it) const { 292 | clear_index(it); 293 | leaf = &this->self(); 294 | } 295 | 296 | void seek_end(typename LeafNodeBase::SelfType const*& leaf, uint64_t& it) const { 297 | set_index(it, this->length); 298 | leaf = &this->self(); 299 | } 300 | 301 | Value const& front() const { 302 | return values[0]; 303 | } 304 | 305 | Value const& back() const { 306 | return values[this->length - 1]; 307 | } 308 | 309 | ssize advance(typename LeafNodeBase::SelfType const*& leaf, uint64_t& it, ssize n) const { 310 | //if n == 0 then we are just fixing the leaf pointer 311 | if (n == 0) { 312 | leaf = &this->self(); 313 | return 0; 314 | } 315 | auto sum = get_index(it) + n; 316 | if (sum >= this->length) { 317 | auto ret = sum - (this->length - 1); 318 | set_index(it, this->length - 1); 319 | return ret; 320 | } 321 | if (sum < 0) { 322 | clear_index(it); 323 | return sum; 324 | } 325 | leaf = &this->self(); 326 | set_index(it, sum); 327 | return 0; 328 | } 329 | }; 330 | } //end namespace bpptree::detail 331 | -------------------------------------------------------------------------------- /include/bpptree/detail/minmax2.ipp: -------------------------------------------------------------------------------- 1 | // 2 | // B++ Tree: A B+ Tree library written in C++ 3 | // Copyright (C) 2023 Jeff Plaisance 4 | // 5 | // This software is distributed under the Boost Software License, Version 1.0. 6 | // See accompanying file LICENSE or copy at http://boost.org/LICENSE_1_0.txt 7 | // 8 | 9 | template 10 | template 11 | typename BPPTREE_MINMAX_UPPER::KeyRef BPPTREE_MINMAX_UPPER::Shared::BPPTREE_MINMAX() const { 12 | return this->self().dispatch( 13 | [](auto const& root) -> decltype(auto) { return root->BPPTREE_MINMAX(); } 14 | ); 15 | } 16 | 17 | template 18 | template 19 | template 20 | typename BPPTREE_MINMAX_UPPER::KeyRef BPPTREE_MINMAX_UPPER::Shared::BPPTREE_MINMAX(const It& begin, const It& end) const { 21 | return this->self().dispatch( 22 | [&begin, &end](auto const& root) -> decltype(auto) { 23 | if constexpr (It::is_reversed) { 24 | return root->BPPTREE_MINMAX((end-1).iter, (begin).iter); 25 | } else { 26 | return root->BPPTREE_MINMAX(begin.iter, (end-1).iter); 27 | } 28 | } 29 | ); 30 | } 31 | 32 | template 33 | template 34 | template 35 | void BPPTREE_MINMAX_UPPER::Shared::BPPTREE_CONCAT(seek_, BPPTREE_MINMAX)(It& it) const { 36 | this->self().dispatch( 37 | [&it](auto const& root) { root->BPPTREE_MINMAX(it); } 38 | ); 39 | } 40 | 41 | template 42 | template 43 | template 44 | void BPPTREE_MINMAX_UPPER::Shared::BPPTREE_CONCAT(seek_, BPPTREE_MINMAX)(OutIt& out, const InIt& begin, const InIt& end) const { 45 | this->self().dispatch( 46 | [&out, &begin, &end](auto const& root) { 47 | if constexpr (InIt::is_reversed) { 48 | root->BPPTREE_MINMAX(out, (end-1).iter, begin.iter); 49 | } else { 50 | root->BPPTREE_MINMAX(out, begin.iter, (end-1).iter); 51 | } 52 | } 53 | ); 54 | } 55 | 56 | template 57 | template 58 | typename BPPTREE_MINMAX_UPPER::template Shared::iterator 59 | BPPTREE_MINMAX_UPPER::Shared::BPPTREE_CONCAT(BPPTREE_MINMAX, _element)() { 60 | iterator ret(this->self()); 61 | BPPTREE_CONCAT(seek_, BPPTREE_MINMAX)(ret); 62 | return ret; 63 | } 64 | 65 | template 66 | template 67 | template 68 | typename BPPTREE_MINMAX_UPPER::template Shared::iterator 69 | BPPTREE_MINMAX_UPPER::Shared::BPPTREE_CONCAT(BPPTREE_MINMAX, _element)(const It& begin, const It& end) { 70 | iterator ret(this->self()); 71 | BPPTREE_CONCAT(seek_, BPPTREE_MINMAX)(ret, begin, end); 72 | return ret; 73 | } 74 | 75 | template 76 | template 77 | typename BPPTREE_MINMAX_UPPER::template Shared::const_iterator 78 | BPPTREE_MINMAX_UPPER::Shared::BPPTREE_CONCAT(BPPTREE_MINMAX, _element_const)() const { 79 | const_iterator ret(this->self()); 80 | BPPTREE_CONCAT(seek_, BPPTREE_MINMAX)(ret); 81 | return ret; 82 | } 83 | 84 | template 85 | template 86 | template 87 | typename BPPTREE_MINMAX_UPPER::template Shared::const_iterator 88 | BPPTREE_MINMAX_UPPER::Shared::BPPTREE_CONCAT(BPPTREE_MINMAX, _element_const)(const It& begin, const It& end) const { 89 | const_iterator ret(this->self()); 90 | BPPTREE_CONCAT(seek_, BPPTREE_MINMAX)(ret, begin, end); 91 | return ret; 92 | } 93 | 94 | template 95 | template 96 | typename BPPTREE_MINMAX_UPPER::template Shared::const_iterator 97 | BPPTREE_MINMAX_UPPER::Shared::BPPTREE_CONCAT(BPPTREE_MINMAX, _element)() const { 98 | return BPPTREE_CONCAT(BPPTREE_MINMAX, _element_const)(); 99 | } 100 | 101 | template 102 | template 103 | template 104 | typename BPPTREE_MINMAX_UPPER::template Shared::const_iterator 105 | BPPTREE_MINMAX_UPPER::Shared::BPPTREE_CONCAT(BPPTREE_MINMAX, _element)(const It& begin, const It& end) const { 106 | return BPPTREE_CONCAT(BPPTREE_MINMAX, _element_const)(begin, end); 107 | } 108 | 109 | #undef BPPTREE_MINMAXS 110 | #undef BPPTREE_CONCAT 111 | #undef CONCAT_INNER 112 | -------------------------------------------------------------------------------- /include/bpptree/detail/modify.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // B++ Tree: A B+ Tree library written in C++ 3 | // Copyright (C) 2023 Jeff Plaisance 4 | // 5 | // This software is distributed under the Boost Software License, Version 1.0. 6 | // See accompanying file LICENSE or copy at http://boost.org/LICENSE_1_0.txt 7 | // 8 | 9 | #pragma once 10 | 11 | #include 12 | #include "nodeptr.hpp" 13 | #include "helpers.hpp" 14 | 15 | namespace bpptree::detail { 16 | 17 | template typename InternalNode, int max_depth> 18 | struct ModifyTypes { 19 | template 20 | static void collapse(TreeType& tree) { 21 | while(tree.dispatch([](auto& tree, auto& root){ 22 | if constexpr (std::decay_t::Type::depth > 1) { 23 | if (root->length == 1) { 24 | // have to call copy constructor here on pointer or else assignment to variant destroys root 25 | // (and therefore destroys root->pointers[0]) before copy can happen 26 | tree.root_variant = NodePtr(root->pointers[0]); 27 | return true; 28 | } 29 | } 30 | return false; 31 | })); 32 | } 33 | 34 | template 35 | struct DoReplace { 36 | TreeType& tree; 37 | NodePtr& root; 38 | 39 | DoReplace(TreeType& tree, NodePtr& root) : tree(tree), root(root) {} 40 | 41 | template 42 | void operator()(ReplaceType&& replace) { 43 | bool do_collapse = false; 44 | if (replace.delta.ptr_changed) { 45 | if constexpr (NodeType::depth > 1) { 46 | if (replace.delta.ptr->length == 1) { 47 | do_collapse = true; 48 | } 49 | } 50 | tree.root_variant = std::move(replace.delta.ptr); 51 | } else if constexpr (NodeType::depth > 1) { 52 | if (root->length == 1) { 53 | do_collapse = true; 54 | } 55 | } 56 | if (do_collapse) { 57 | collapse(tree); 58 | } 59 | } 60 | }; 61 | 62 | template 63 | struct DoSplit { 64 | TreeType& tree; 65 | NodePtr& root; 66 | uint64_t& iter; 67 | 68 | DoSplit(TreeType& tree, NodePtr& root, uint64_t& iter) : tree(tree), root(root), iter(iter) {} 69 | 70 | template 71 | void operator()([[maybe_unused]] SplitType&& split) { 72 | if constexpr (NodeType::depth < max_depth) { 73 | using NewRootType = InternalNode; 74 | auto root_node = make_ptr(); 75 | if (!split.left.ptr_changed) { 76 | split.left.ptr = std::move(root); 77 | split.left.ptr_changed = true; 78 | } 79 | root_node->set_element(0, split.left); 80 | root_node->set_element(1, split.right); 81 | root_node->length = 2; 82 | root_node->set_index(iter, split.new_element_left ? 0 : 1); 83 | tree.root_variant = std::move(root_node); 84 | } else { 85 | #ifdef BPPTREE_SAFETY_CHECKS 86 | throw std::logic_error("maximum depth exceeded"); 87 | #endif 88 | } 89 | } 90 | }; 91 | 92 | template 93 | struct DoErase { 94 | TreeType& tree; 95 | 96 | explicit DoErase(TreeType& tree) : tree(tree) {} 97 | 98 | void operator()() { 99 | tree.root_variant = make_ptr(); 100 | } 101 | }; 102 | 103 | template 104 | struct Modify { 105 | template 106 | uint64_t operator()(TreeType& tree, NodePtr& root, F&& finder, T const& search_val, Us&&... params) { 107 | uint64_t ret = 0; 108 | Operation()(*root, search_val, finder, 109 | DoReplace(tree, root), 110 | DoSplit(tree, root, ret), 111 | DoErase(tree), 112 | tree.tree_size, 113 | ret, 114 | true, 115 | std::forward(params)... 116 | ); 117 | ++tree.mod_count; 118 | return ret; 119 | } 120 | }; 121 | }; 122 | } 123 | -------------------------------------------------------------------------------- /include/bpptree/detail/nodeptr.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // B++ Tree: A B+ Tree library written in C++ 3 | // Copyright (C) 2023 Jeff Plaisance 4 | // 5 | // This software is distributed under the Boost Software License, Version 1.0. 6 | // See accompanying file LICENSE or copy at http://boost.org/LICENSE_1_0.txt 7 | // 8 | 9 | #pragma once 10 | 11 | #include 12 | 13 | namespace bpptree::detail { 14 | 15 | inline int allocations = 0; 16 | inline int deallocations = 0; 17 | inline int increments = 0; 18 | inline int decrements = 0; 19 | 20 | inline void reset_counters() { 21 | allocations = 0; 22 | deallocations = 0; 23 | increments = 0; 24 | decrements = 0; 25 | } 26 | 27 | static constexpr bool count_allocations = 28 | #ifdef BPPTREE_TEST_COUNT_ALLOCATIONS 29 | true; 30 | #else 31 | false; 32 | #endif 33 | 34 | template 35 | class NodePtr { 36 | 37 | PtrType* ptr = nullptr; 38 | 39 | void dec_ref() { 40 | if (ptr != nullptr) { 41 | if (ptr->ref_count.fetch_sub(1, std::memory_order_release) == 1) { 42 | std::atomic_thread_fence(std::memory_order_acquire); 43 | delete ptr; 44 | if constexpr (count_allocations) ++deallocations; 45 | } 46 | if constexpr (count_allocations) ++decrements; 47 | } 48 | } 49 | 50 | void inc_ref() const { 51 | if (ptr != nullptr) { 52 | ptr->ref_count.fetch_add(1, std::memory_order_relaxed); 53 | if constexpr (count_allocations) ++increments; 54 | } 55 | } 56 | 57 | public: 58 | using Type = PtrType; 59 | 60 | NodePtr() = default; 61 | 62 | NodePtr(PtrType* p) noexcept : ptr(p) {} //NOLINT 63 | 64 | NodePtr(NodePtr const& other) noexcept : ptr(other.ptr) { 65 | inc_ref(); 66 | } 67 | 68 | NodePtr(NodePtr&& other) noexcept : ptr(other.ptr) { 69 | other.ptr = nullptr; 70 | } 71 | 72 | // NOLINTNEXTLINE(bugprone-unhandled-self-assignment) 73 | NodePtr& operator=(NodePtr const& rhs) noexcept { 74 | // call inc_ref on rhs before calling dec_ref on this to ensure safe self-assignment 75 | rhs.inc_ref(); 76 | dec_ref(); 77 | ptr = rhs.ptr; 78 | return *this; 79 | } 80 | 81 | NodePtr& operator=(NodePtr&& rhs) noexcept { 82 | dec_ref(); 83 | ptr = rhs.ptr; 84 | rhs.ptr = nullptr; 85 | return *this; 86 | } 87 | 88 | PtrType& operator*() { 89 | return *ptr; 90 | } 91 | 92 | PtrType const& operator*() const { 93 | return *ptr; 94 | } 95 | 96 | PtrType* operator->() { 97 | return ptr; 98 | } 99 | 100 | PtrType const* operator->() const { 101 | return ptr; 102 | } 103 | 104 | public: 105 | ~NodePtr() { 106 | dec_ref(); 107 | } 108 | }; 109 | 110 | template 111 | NodePtr make_ptr(Ts&&... ts) noexcept(std::is_nothrow_constructible_v) { 112 | if constexpr (count_allocations) { 113 | ++allocations; 114 | ++increments; 115 | } 116 | return NodePtr(new PtrType(std::forward(ts)...)); 117 | } 118 | } //end namespace bpptree::detail 119 | -------------------------------------------------------------------------------- /include/bpptree/detail/nodetypes.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // B++ Tree: A B+ Tree library written in C++ 3 | // Copyright (C) 2023 Jeff Plaisance 4 | // 5 | // This software is distributed under the Boost Software License, Version 1.0. 6 | // See accompanying file LICENSE or copy at http://boost.org/LICENSE_1_0.txt 7 | // 8 | 9 | #pragma once 10 | 11 | #include 12 | 13 | namespace bpptree::detail { 14 | 15 | template < 16 | typename Value, 17 | ssize leaf_node_bytes = 512, 18 | ssize internal_node_bytes = 512, 19 | ssize depth_limit = 16, 20 | bool disable_exceptions = true, 21 | typename... Ts> 22 | struct NodeTypesDetail { 23 | template 24 | struct NodeBase; 25 | 26 | template 27 | struct NodeInfoBase : public Parent { 28 | NodeInfoBase() = default; 29 | 30 | template 31 | explicit constexpr NodeInfoBase(P const&, bool const) {} 32 | }; 33 | 34 | template 35 | using NodeInfoMixin = Chain; 36 | 37 | template 38 | struct NodeInfo : public NodeInfoMixin> { 39 | using Parent = NodeInfoMixin>; 40 | 41 | NodePtr ptr{}; 42 | bool ptr_changed = false; 43 | 44 | NodeInfo() = default; 45 | 46 | template 47 | NodeInfo(P&& p, const bool changed) noexcept(disable_exceptions) : Parent(p, changed), ptr(), ptr_changed(changed) { 48 | if (changed) { 49 | this->ptr = std::forward

(p); 50 | } 51 | } 52 | }; 53 | 54 | template 55 | struct LeafNodeMixin { 56 | template 57 | using LeafNodeBaseCurried = LeafNodeBase; 58 | using Type = Chain; 62 | }; 63 | 64 | template 65 | struct InternalNodeMixin { 66 | template 67 | using InternalNodeBaseCurried = InternalNodeBase; 68 | using Type = Chain::template Mixin..., 70 | InternalNodeBaseCurried, 71 | NodeBase>; 72 | }; 73 | 74 | template 75 | struct LeafNode : public LeafNodeMixin, leaf_size>::Type {}; 76 | 77 | template 78 | struct InternalNode : public InternalNodeMixin, leaf_size, internal_size, depth>::Type {}; 79 | 80 | template 81 | struct NodeBase : public Parent { 82 | template 83 | using NodeInfoType = NodeInfo; 84 | 85 | template 86 | using LeafNodeType = LeafNode; 87 | 88 | template 89 | using InternalNodeType = InternalNode; 90 | 91 | std::atomic ref_count = 1; 92 | uint16_t length = 0; 93 | bool persistent = false; 94 | 95 | NodeBase() = default; 96 | 97 | // ref_count and persistent are intentionally not copied 98 | NodeBase(NodeBase const& other) noexcept : Parent(other), length(other.length) {} 99 | 100 | // copy assignment is deleted because there is no scenario in which overwriting an existing node makes sense 101 | NodeBase& operator=(NodeBase const& other) = delete; 102 | 103 | ~NodeBase() = default; 104 | }; 105 | 106 | template 107 | static constexpr int get_leaf_node_size3() { 108 | if constexpr (sizeof(LeafNode) <= leaf_node_bytes) { 109 | return leaf_size; 110 | } else { 111 | return get_leaf_node_size3(); 112 | } 113 | } 114 | 115 | template 116 | static constexpr int get_leaf_node_size2() { 117 | constexpr ssize size = sizeof(LeafNode); 118 | if constexpr (size > leaf_node_bytes) { 119 | return get_leaf_node_size3(); 120 | } else if constexpr (size + sizeof(Value) > leaf_node_bytes) { 121 | return leaf_size; 122 | } else { 123 | return get_leaf_node_size2(); 124 | } 125 | } 126 | 127 | static constexpr int get_leaf_node_size() { 128 | constexpr int initial = (leaf_node_bytes - 8) / sizeof(Value); 129 | return get_leaf_node_size2(); 130 | } 131 | 132 | static constexpr int leaf_node_size = get_leaf_node_size(); 133 | static_assert(leaf_node_size > 0); 134 | static_assert(leaf_node_size < 65536); 135 | 136 | // each element added to an internal node increases its size by 137 | // at least this amount but potentially more due to padding 138 | static constexpr ssize internal_element_size_lower_bound = (Ts::sizeof_hint() + ... + sizeof(void*)); 139 | 140 | template 141 | static constexpr int get_internal_node_size3() { 142 | if constexpr (sizeof(InternalNode) <= internal_node_bytes) { 143 | return internal_size; 144 | } else { 145 | return get_internal_node_size3(); 146 | } 147 | } 148 | 149 | template 150 | static constexpr int get_internal_node_size2() { 151 | constexpr ssize size = sizeof(InternalNode); 152 | if constexpr (size > internal_node_bytes) { 153 | return get_internal_node_size3(); 154 | } else if constexpr (size + internal_element_size_lower_bound > internal_node_bytes) { 155 | return internal_size; 156 | } else { 157 | return get_internal_node_size2(); 158 | } 159 | } 160 | 161 | static constexpr int get_internal_node_size() { 162 | constexpr int initial = (internal_node_bytes - 8) / internal_element_size_lower_bound; 163 | return get_internal_node_size2(); 164 | } 165 | 166 | static constexpr int internal_node_size = get_internal_node_size(); 167 | static_assert(internal_node_size >= 4); 168 | static_assert(internal_node_size < 65536); 169 | 170 | template 171 | static constexpr int find_max_depth() { 172 | if constexpr (size > 64) { 173 | return depth - 1; 174 | } else if constexpr (depth == depth_limit) { 175 | return depth; 176 | } else { 177 | return find_max_depth(), depth + 1>(); 178 | } 179 | } 180 | 181 | template 182 | static constexpr size_t pow() { 183 | if constexpr (b == 0) { 184 | return 1; 185 | } else { 186 | return a * pow(); 187 | } 188 | } 189 | 190 | static constexpr int max_depth = find_max_depth(), 1>(); 191 | 192 | static constexpr size_t max_size = leaf_node_size * pow(); 193 | 194 | template 195 | struct RootVariant { 196 | using type = typename RootVariant::type; 197 | }; 198 | 199 | template 200 | struct RootVariant<1, args...> { 201 | using type = std::variant>, NodePtr>...>; 202 | }; 203 | 204 | using RootType = typename RootVariant::type; 205 | }; 206 | } //end namespace bpptree::detail 207 | -------------------------------------------------------------------------------- /include/bpptree/detail/operations.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // B++ Tree: A B+ Tree library written in C++ 3 | // Copyright (C) 2023 Jeff Plaisance 4 | // 5 | // This software is distributed under the Boost Software License, Version 1.0. 6 | // See accompanying file LICENSE or copy at http://boost.org/LICENSE_1_0.txt 7 | // 8 | 9 | #pragma once 10 | 11 | #include 12 | #include 13 | #include "helpers.hpp" 14 | 15 | namespace bpptree::detail { 16 | 17 | struct Assign { 18 | template 19 | void operator()(N& node, T const &search_val, F const& finder, R&& do_replace, S&&, E&&, size_t&, uint64_t& iter, bool, Args&&... args) { 20 | node.assign(search_val, finder, do_replace, iter, std::forward(args)...); 21 | } 22 | }; 23 | 24 | struct Erase { 25 | template 26 | void operator()(N& node, T const &search_val, F const& finder, R&& do_replace, S&&, E&& do_erase, size_t& size, uint64_t& iter, bool right_most) { 27 | node.erase(search_val, finder, do_replace, do_erase, size, iter, right_most); 28 | } 29 | }; 30 | 31 | struct Insert { 32 | template 33 | void operator()(N& node, T const &search_val, F const& finder, R&& do_replace, S&& do_split, E&&, size_t &size, uint64_t& iter, bool right_most, Args&&... args) { 34 | node.insert(search_val, finder, do_replace, do_split, size, iter, right_most, std::forward(args)...); 35 | } 36 | }; 37 | 38 | struct Update { 39 | template 40 | void operator()(N& node, T const &search_val, F const& finder, R&& do_replace, S&&, E&&, size_t&, uint64_t& iter, bool, U&& updater) { 41 | node.update(search_val, finder, do_replace, iter, updater); 42 | } 43 | }; 44 | 45 | struct Update2 { 46 | template 47 | void operator()(N& node, T const &search_val, F const& finder, R&& do_replace, S&&, E&&, size_t&, uint64_t& iter, bool, U&& updater) { 48 | node.update2(search_val, finder, do_replace, iter, updater); 49 | } 50 | }; 51 | 52 | template 53 | struct InsertOrAssign { 54 | template 55 | void operator()(N& node, T const &search_val, F const& finder, R&& do_replace, S&& do_split, E&&, 56 | size_t &size, uint64_t& iter, bool right_most, Args&&... args) { 57 | node.template insert_or_assign(search_val, finder, do_replace, do_split, 58 | size, iter, right_most, std::forward(args)...); 59 | } 60 | }; 61 | 62 | inline constexpr auto find_first = [](auto const&, Empty const&) { 63 | return std::tuple(0, Empty::empty); 64 | }; 65 | 66 | inline constexpr auto find_last = [](auto const& node, Empty const&) { 67 | if constexpr (std::remove_reference_t::depth == 1) { 68 | return std::tuple(node.length, Empty::empty); 69 | } else { 70 | return std::tuple(node.length - 1, Empty::empty); 71 | } 72 | }; 73 | 74 | inline constexpr auto find_iterator = [](auto const& node, uint64_t const& it) { 75 | return std::tuple(node.get_index(it), it); 76 | }; 77 | } //end namespace bpptree::detail 78 | -------------------------------------------------------------------------------- /include/bpptree/detail/proxy_operators.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // B++ Tree: A B+ Tree library written in C++ 3 | // Copyright (C) 2023 Jeff Plaisance 4 | // 5 | // This software is distributed under the Boost Software License, Version 1.0. 6 | // See accompanying file LICENSE or copy at http://boost.org/LICENSE_1_0.txt 7 | // 8 | 9 | #pragma once 10 | 11 | #include "helpers.hpp" 12 | 13 | namespace bpptree::detail { 14 | 15 | template 16 | struct HasEq : std::false_type { 17 | }; 18 | 19 | template 20 | struct HasEq() == std::declval())>> : std::true_type { 21 | }; 22 | 23 | template 24 | struct HasLt : std::false_type { 25 | }; 26 | 27 | template 28 | struct HasLt() < std::declval())>> : std::true_type { 29 | }; 30 | 31 | template 32 | struct HasGt : std::false_type { 33 | }; 34 | 35 | template 36 | struct HasGt() > std::declval())>> : std::true_type { 37 | }; 38 | 39 | template 40 | struct HasLte : std::false_type { 41 | }; 42 | 43 | template 44 | struct HasLte() <= std::declval())>> : std::true_type { 45 | }; 46 | 47 | template 48 | struct HasGte : std::false_type { 49 | }; 50 | 51 | template 52 | struct HasGte() >= std::declval())>> : std::true_type { 53 | }; 54 | 55 | template 56 | struct HasAdd : std::false_type { 57 | }; 58 | 59 | template 60 | struct HasAdd() + std::declval())>> : std::true_type { 61 | }; 62 | 63 | template 64 | struct HasSub : std::false_type { 65 | }; 66 | 67 | template 68 | struct HasSub() - std::declval())>> : std::true_type { 69 | }; 70 | 71 | template 72 | struct HasMul : std::false_type { 73 | }; 74 | 75 | template 76 | struct HasMul() * std::declval())>> : std::true_type { 77 | }; 78 | 79 | template 80 | struct HasDiv : std::false_type { 81 | }; 82 | 83 | template 84 | struct HasDiv() / std::declval())>> : std::true_type { 85 | }; 86 | 87 | template 88 | struct HasMod : std::false_type { 89 | }; 90 | 91 | template 92 | struct HasMod() % std::declval())>> : std::true_type { 93 | }; 94 | 95 | template 96 | struct HasXor : std::false_type { 97 | }; 98 | 99 | template 100 | struct HasXor() ^ std::declval())>> : std::true_type { 101 | }; 102 | 103 | template 104 | struct HasAnd : std::false_type { 105 | }; 106 | 107 | template 108 | struct HasAnd() & std::declval())>> : std::true_type { 109 | }; 110 | 111 | template 112 | struct HasOr : std::false_type { 113 | }; 114 | 115 | template 116 | struct HasOr() | std::declval())>> : std::true_type { 117 | }; 118 | 119 | template 120 | struct HasLeftShift : std::false_type { 121 | }; 122 | 123 | template 124 | struct HasLeftShift() << std::declval())>> : std::true_type { 125 | }; 126 | 127 | template 128 | struct HasRightShift : std::false_type { 129 | }; 130 | 131 | template 132 | struct HasRightShift() >> std::declval())>> : std::true_type { 133 | }; 134 | 135 | template 136 | struct ProxyOperators { 137 | private: 138 | T& self()& { return static_cast(*this); } 139 | 140 | T&& self()&& { return static_cast(*this); } 141 | 142 | T const& self() const& { return static_cast(*this); } 143 | 144 | T const&& self() const&& { return static_cast(*this); } 145 | 146 | public: 147 | template 148 | decltype(auto) operator+=(U const& u) { 149 | return self().invoke_compound_assignment([&u](auto const& v) { return v + u; }); 150 | } 151 | 152 | template 153 | decltype(auto) operator-=(U const& u) { 154 | return self().invoke_compound_assignment([&u](auto const& v) { return v - u; }); 155 | } 156 | 157 | template 158 | decltype(auto) operator*=(U const& u) { 159 | return self().invoke_compound_assignment([&u](auto const& v) { return v * u; }); 160 | } 161 | 162 | template 163 | decltype(auto) operator/=(U const& u) { 164 | return self().invoke_compound_assignment([&u](auto const& v) { return v / u; }); 165 | } 166 | 167 | template 168 | decltype(auto) operator%=(U const& u) { 169 | return self().invoke_compound_assignment([&u](auto const& v) { return v % u; }); 170 | } 171 | 172 | template 173 | decltype(auto) operator^=(U const& u) { 174 | return self().invoke_compound_assignment([&u](auto const& v) { return v ^ u; }); 175 | } 176 | 177 | template 178 | decltype(auto) operator&=(U const& u) { 179 | return self().invoke_compound_assignment([&u](auto const& v) { return v & u; }); 180 | } 181 | 182 | template 183 | decltype(auto) operator|=(U const& u) { 184 | return self().invoke_compound_assignment([&u](auto const& v) { return v | u; }); 185 | } 186 | 187 | template 188 | decltype(auto) operator>>=(U const& u) { 189 | return self().invoke_compound_assignment([&u](auto const& v) { return v >> u; }); 190 | } 191 | 192 | template 193 | decltype(auto) operator<<=(U const& u) { 194 | return self().invoke_compound_assignment([&u](auto const& v) { return v << u; }); 195 | } 196 | 197 | template 198 | decltype(auto) operator++() { 199 | return self().invoke_pre([](auto& v) { return ++v; }); 200 | } 201 | 202 | template 203 | decltype(auto) operator--() { 204 | return self().invoke_pre([](auto& v) { return --v; }); 205 | } 206 | 207 | template 208 | decltype(auto) operator++(int) { // NOLINT(cert-dcl21-cpp) 209 | return self().invoke_post([](auto& v) { return v++; }); 210 | } 211 | 212 | template 213 | decltype(auto) operator--(int) { // NOLINT(cert-dcl21-cpp) 214 | return self().invoke_post([](auto& v) { return v--; }); 215 | } 216 | 217 | private: 218 | template 219 | static decltype(auto) get_if_proxy(U const& u) { 220 | if constexpr (std::is_same_v) { 221 | return u.get(); 222 | } else { 223 | return u; 224 | } 225 | } 226 | 227 | public: 228 | template ::value && 231 | (std::is_same_v || std::is_same_v), bool> = true> 232 | [[nodiscard]] friend bool operator==(A const& a, B const& b) { 233 | return get_if_proxy(a) == get_if_proxy(b); 234 | } 235 | 236 | template ::value && 239 | (std::is_same_v || std::is_same_v), bool> = true> 240 | [[nodiscard]] friend bool operator<(A const& a, B const& b) { 241 | return get_if_proxy(a) < get_if_proxy(b); 242 | } 243 | 244 | template ::value && 247 | (std::is_same_v || std::is_same_v), bool> = true> 248 | [[nodiscard]] friend bool operator>(A const& a, B const& b) { 249 | return get_if_proxy(a) > get_if_proxy(b); 250 | } 251 | 252 | template ::value && 255 | (std::is_same_v || std::is_same_v), bool> = true> 256 | [[nodiscard]] friend bool operator<=(A const& a, B const& b) { 257 | return get_if_proxy(a) <= get_if_proxy(b); 258 | } 259 | 260 | template ::value && 263 | (std::is_same_v || std::is_same_v), bool> = true> 264 | [[nodiscard]] friend bool operator>=(A const& a, B const& b) { 265 | return get_if_proxy(a) >= get_if_proxy(b); 266 | } 267 | 268 | template ::value && 271 | (std::is_same_v || std::is_same_v), bool> = true> 272 | [[nodiscard]] friend decltype(auto) operator+(A const& a, B const& b) { 273 | return get_if_proxy(a) + get_if_proxy(b); 274 | } 275 | 276 | template ::value && 279 | (std::is_same_v || std::is_same_v), bool> = true> 280 | [[nodiscard]] friend decltype(auto) operator-(A const& a, B const& b) { 281 | return get_if_proxy(a) - get_if_proxy(b); 282 | } 283 | 284 | template ::value && 287 | (std::is_same_v || std::is_same_v), bool> = true> 288 | [[nodiscard]] friend decltype(auto) operator*(A const& a, B const& b) { 289 | return get_if_proxy(a) * get_if_proxy(b); 290 | } 291 | 292 | template ::value && 295 | (std::is_same_v || std::is_same_v), bool> = true> 296 | [[nodiscard]] friend decltype(auto) operator/(A const& a, B const& b) { 297 | return get_if_proxy(a) / get_if_proxy(b); 298 | } 299 | 300 | template ::value && 303 | (std::is_same_v || std::is_same_v), bool> = true> 304 | [[nodiscard]] friend decltype(auto) operator%(A const& a, B const& b) { 305 | return get_if_proxy(a) % get_if_proxy(b); 306 | } 307 | 308 | template ::value && 311 | (std::is_same_v || std::is_same_v), bool> = true> 312 | [[nodiscard]] friend decltype(auto) operator^(A const& a, B const& b) { 313 | return get_if_proxy(a) ^ get_if_proxy(b); 314 | } 315 | 316 | template ::value && 319 | (std::is_same_v || std::is_same_v), bool> = true> 320 | [[nodiscard]] friend decltype(auto) operator&(A const& a, B const& b) { 321 | return get_if_proxy(a) & get_if_proxy(b); 322 | } 323 | 324 | template ::value && 327 | (std::is_same_v || std::is_same_v), bool> = true> 328 | [[nodiscard]] friend decltype(auto) operator|(A const& a, B const& b) { 329 | return get_if_proxy(a) | get_if_proxy(b); 330 | } 331 | 332 | template ::value && 335 | (std::is_same_v || std::is_same_v), bool> = true> 336 | [[nodiscard]] friend decltype(auto) operator<<(A const& a, B const& b) { 337 | return get_if_proxy(a) << get_if_proxy(b); 338 | } 339 | 340 | template ::value && 343 | (std::is_same_v || std::is_same_v), bool> = true> 344 | [[nodiscard]] friend decltype(auto) operator>>(A const& a, B const& b) { 345 | return get_if_proxy(a) >> get_if_proxy(b); 346 | } 347 | }; 348 | } //end namespace bpptree::detail -------------------------------------------------------------------------------- /include/bpptree/detail/sandwich.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // B++ Tree: A B+ Tree library written in C++ 3 | // Copyright (C) 2023 Jeff Plaisance 4 | // 5 | // This software is distributed under the Boost Software License, Version 1.0. 6 | // See accompanying file LICENSE or copy at http://boost.org/LICENSE_1_0.txt 7 | // 8 | 9 | #pragma once 10 | 11 | namespace bpptree::detail { 12 | 13 | template 14 | struct Top { 15 | using SelfType = Derived; 16 | SelfType& self() & { return static_cast(*this); } 17 | SelfType&& self() && { return static_cast(*this); } 18 | SelfType const& self() const& { return static_cast(*this); } 19 | SelfType const&& self() const&& { return static_cast(*this); } 20 | }; 21 | 22 | template