Add 'xo-ordinaltree/' from commit 'e40dc2daab'
git-subtree-dir: xo-ordinaltree git-subtree-mainline:aaa3864229git-subtree-split:e40dc2daab
This commit is contained in:
commit
9778da2094
21 changed files with 9075 additions and 0 deletions
122
xo-ordinaltree/.github/workflows/main.yml
vendored
Normal file
122
xo-ordinaltree/.github/workflows/main.yml
vendored
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
|
||||
name: build xo-ordinaltree + xo dependencies
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
|
||||
env:
|
||||
# Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.)
|
||||
BUILD_TYPE: Release
|
||||
|
||||
jobs:
|
||||
build:
|
||||
# The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac.
|
||||
# You can convert this to a matrix build if you need cross-platform coverage.
|
||||
# See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: checkout source
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install catch2
|
||||
# install catch2. see [[https://stackoverflow.com/questions/57982945/how-to-apt-get-install-in-a-github-actions-workflow]]
|
||||
run: sudo apt-get install -y catch2
|
||||
|
||||
- name: Install libbsd-dev
|
||||
# provides arc4random_buf in randomgen
|
||||
run: sudo apt-get install -y libbsd-dev
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
|
||||
- name: Clone xo-cmake
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
repository: Rconybea/xo-cmake
|
||||
path: repo/xo-cmake
|
||||
|
||||
- name: Configure xo-cmake
|
||||
run: cmake -B ${{github.workspace}}/build_xo-cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/xo-cmake
|
||||
|
||||
- name: Build xo-cmake (trivial)
|
||||
run: cmake --build ${{github.workspace}}/build_xo-cmake --config ${{env.BUILD_TYPE}}
|
||||
|
||||
- name: Install xo-cmake
|
||||
run: cmake --install ${{github.workspace}}/build_xo-cmake
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
|
||||
- name: Clone indentlog
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
repository: Rconybea/indentlog
|
||||
path: repo/indentlog
|
||||
|
||||
- name: Configure indentlog
|
||||
# configure cmake for indentlog in dedicated build directory.
|
||||
run: cmake -B ${{github.workspace}}/build_indentlog -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/indentlog
|
||||
|
||||
- name: Build indentlog
|
||||
run: cmake --build ${{github.workspace}}/build_indentlog --config ${{env.BUILD_TYPE}}
|
||||
|
||||
- name: Install indentlog
|
||||
# install into ${{github.workspace}}/local
|
||||
run: cmake --install ${{github.workspace}}/build_indentlog
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
|
||||
- name: Clone randomgen
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
repository: Rconybea/randomgen
|
||||
path: repo/randomgen
|
||||
|
||||
- name: Configure randomgen
|
||||
# configure cmake for randomgen in dedicated build directory.
|
||||
run: cmake -B ${{github.workspace}}/build_randomgen -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/randomgen
|
||||
|
||||
- name: Build randomgen
|
||||
run: cmake --build ${{github.workspace}}/build_randomgen --config ${{env.BUILD_TYPE}}
|
||||
|
||||
- name: Install randomgen
|
||||
# install into ${{github.workspace}}/local
|
||||
run: cmake --install ${{github.workspace}}/build_randomgen
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
|
||||
- name: Clone refcnt
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
repository: Rconybea/refcnt
|
||||
path: repo/refcnt
|
||||
|
||||
- name: Configure refcnt
|
||||
# configure cmake for refcnt in dedicated build directory.
|
||||
run: cmake -B ${{github.workspace}}/build_refcnt -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/refcnt
|
||||
|
||||
- name: Build refcnt
|
||||
run: cmake --build ${{github.workspace}}/build_refcnt --config ${{env.BUILD_TYPE}}
|
||||
|
||||
- name: Install refcnt
|
||||
# install into ${{github.workspace}}/local
|
||||
run: cmake --install ${{github.workspace}}/build_refcnt
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
|
||||
- name: Configure self (xo-ordinaltree)
|
||||
# Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make.
|
||||
# See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type
|
||||
run: cmake -B ${{github.workspace}}/build_ordinaltree -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}}
|
||||
|
||||
- name: Build self (xo-ordinaltree)
|
||||
# Build your program with the given configuration
|
||||
run: cmake --build ${{github.workspace}}/build_ordinaltree --config ${{env.BUILD_TYPE}}
|
||||
|
||||
- name: Test self (xo-ordinaltree)
|
||||
working-directory: ${{github.workspace}}/build_ordinaltree
|
||||
# Execute tests defined by the CMake configuration.
|
||||
# See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail
|
||||
run: ctest -C ${{env.BUILD_TYPE}}
|
||||
6
xo-ordinaltree/.gitignore
vendored
Normal file
6
xo-ordinaltree/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
# clangd working space (see emacs+lsp)
|
||||
.cache
|
||||
# typical cmake build directory (source-tree-nephew)
|
||||
.build*
|
||||
# symlink to builddir/compile_commands.json; should be set manually in dev sandbox
|
||||
compile_commands.json
|
||||
43
xo-ordinaltree/CMakeLists.txt
Normal file
43
xo-ordinaltree/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
# xo-ordinaltree/CMakeLists.txt
|
||||
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
|
||||
project(xo_ordinaltree VERSION 0.1)
|
||||
enable_language(CXX)
|
||||
|
||||
# common XO macros (see github:Rconybea/xo-cmake)
|
||||
include(GNUInstallDirs)
|
||||
include(cmake/xo-bootstrap-macros.cmake)
|
||||
|
||||
xo_cxx_toplevel_options3()
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
# c++ settings
|
||||
|
||||
set(PROJECT_CXX_FLAGS "")
|
||||
add_definitions(${PROJECT_CXX_FLAGS})
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
# output targets
|
||||
|
||||
add_subdirectory(utest)
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
# header-only library
|
||||
|
||||
set(SELF_LIB xo_ordinaltree)
|
||||
xo_add_headeronly_library(${SELF_LIB})
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
#
|
||||
xo_install_library4(${SELF_LIB} ${PROJECT_NAME}Targets)
|
||||
# (note: ..Targets from xo_install_library2())
|
||||
xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets)
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
# input dependencies
|
||||
|
||||
# NOTE: dependency set here must be kept consistent with ordinaltree/cmake/xo_ordinaltreeConfig.cmake.in
|
||||
|
||||
# xo-ordinaltree is also header-only
|
||||
xo_headeronly_dependency(${SELF_LIB} randomgen)
|
||||
33
xo-ordinaltree/README.md
Normal file
33
xo-ordinaltree/README.md
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
# ordinal tree library
|
||||
|
||||
## Getting Started
|
||||
|
||||
### build + install dependencies
|
||||
|
||||
- see [github/Rconybea/randomgen](https://github.com/Rconybea/randomgen) -- random number generators e.g. xoshiro256ss
|
||||
- see [github/Rconybea/refcnt](https://github.com/Rconybea/refcnt) -- intrusive reference-counting
|
||||
|
||||
### build + install
|
||||
```
|
||||
$ cd xo-ordinaltree
|
||||
$ mkdir build
|
||||
$ cd build
|
||||
$ INSTALL_PREFIX=/usr/local # or wherever you prefer
|
||||
$ cmake -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} -DCMAKE_INSTALL_PREFIX=${INSTALL_PREFIX} ..
|
||||
$ make
|
||||
$ make install
|
||||
```
|
||||
|
||||
### build for unit test coverage
|
||||
```
|
||||
$ cd xo-ordinaltree
|
||||
$ mkdir build-ccov
|
||||
$ cd build-ccov
|
||||
$ cmake -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} -DCODE_COVERAGE=ON -DCMAKE_BUILD_TYPE=Debug ..
|
||||
```
|
||||
|
||||
### LSP support
|
||||
```
|
||||
$ cd xo-ordinaltree
|
||||
$ ln -s build/compile_commands.json # lsp will look for compile_commands.json in the root of the source tree
|
||||
```
|
||||
14
xo-ordinaltree/cmake/xo-bootstrap-macros.cmake
Normal file
14
xo-ordinaltree/cmake/xo-bootstrap-macros.cmake
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "prefix"))
|
||||
# default to typical install location for xo-project-macros
|
||||
set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake)
|
||||
endif()
|
||||
|
||||
if (NOT XO_SUBMODULE_BUILD)
|
||||
message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}")
|
||||
message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}")
|
||||
endif()
|
||||
|
||||
# needs to have been installed somewhere on CMAKE_MODULE_PATH,
|
||||
# (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX)
|
||||
#
|
||||
include(xo_macros/xo-project-macros)
|
||||
6
xo-ordinaltree/cmake/xo_ordinaltreeConfig.cmake.in
Normal file
6
xo-ordinaltree/cmake/xo_ordinaltreeConfig.cmake.in
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
@PACKAGE_INIT@
|
||||
|
||||
include(CMakeFindDependencyMacro)
|
||||
find_dependency(randomgen)
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake")
|
||||
check_required_components("@PROJECT_NAME@")
|
||||
1799
xo-ordinaltree/include/xo/ordinaltree/BplusTree.hpp
Normal file
1799
xo-ordinaltree/include/xo/ordinaltree/BplusTree.hpp
Normal file
File diff suppressed because it is too large
Load diff
3158
xo-ordinaltree/include/xo/ordinaltree/RedBlackTree.hpp
Normal file
3158
xo-ordinaltree/include/xo/ordinaltree/RedBlackTree.hpp
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,280 @@
|
|||
/* @file BplusTreeNode.hpp */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "IteratorUtil.hpp"
|
||||
#include "bplustree_tags.hpp"
|
||||
#include "xo/indentlog/scope.hpp"
|
||||
#include "xo/indentlog/print/tag.hpp"
|
||||
#include <memory> // for std::unique_ptr
|
||||
#include <string>
|
||||
|
||||
namespace xo {
|
||||
namespace tree {
|
||||
/* forward decl (see GenericNode.hpp) */
|
||||
template <typename Key, typename Value, typename Properties>
|
||||
class GenericNode;
|
||||
/* forward decl (see InternalNode.hpp) */
|
||||
template <typename Key, typename Value, typename Properties>
|
||||
class InternalNode;
|
||||
|
||||
namespace detail {
|
||||
/* forward decl (see Iterator.hpp) */
|
||||
template <typename Key, typename Value, typename Properties>
|
||||
class ConstIterator;
|
||||
}
|
||||
|
||||
// ----- NodeType -----
|
||||
|
||||
enum class NodeType { internal, leaf };
|
||||
|
||||
inline std::string node_type2str(NodeType x) {
|
||||
switch(x) {
|
||||
case NodeType::internal: return "internal";
|
||||
case NodeType::leaf: return "leaf";
|
||||
}
|
||||
|
||||
return "???";
|
||||
} /*node_type2str*/
|
||||
|
||||
inline std::ostream & operator<<(std::ostream & os, NodeType x) {
|
||||
os << node_type2str(x);
|
||||
return os;
|
||||
} /*operator<<*/
|
||||
|
||||
/* see bplustree/LeafNode.hpp */
|
||||
template <typename Key, typename Value, typename Properties>
|
||||
struct LeafNode;
|
||||
|
||||
/* see bplustree/InternalNode.hpp */
|
||||
template <typename Key, typename Value, typename Properties>
|
||||
struct InternalNode;
|
||||
|
||||
// ----- NodeItem + NodeItemPlaceholder -----
|
||||
|
||||
template <NodeType NType, typename Key, typename Value, typename Properties>
|
||||
struct NodeItem {};
|
||||
|
||||
/* struct with same size as NodeItem<NType, Key, Value, Properties>, but POD + with trivial ctor/dtor */
|
||||
template <NodeType NType, typename Key, typename Value, typename Properties>
|
||||
struct NodeItemPlaceholder {
|
||||
std::uint8_t mem_v_[sizeof(NodeItem<NType, Key, Value, Properties>)];
|
||||
}; /*NodeItemPlaceholder*/
|
||||
|
||||
// ----- FindResult -----
|
||||
|
||||
/* report a node, along with its location (0-based index) within parent.
|
||||
* use nullptr for .node if item/node not found
|
||||
* use 0 for .ix if node is root (i.e. has no parent)
|
||||
*
|
||||
* expect ConcreteNodeType = LeafNode<..> | InternalNode<..>
|
||||
*/
|
||||
template <typename ConcreteNodeType>
|
||||
struct FindNodeResult {
|
||||
public:
|
||||
FindNodeResult() = default;
|
||||
FindNodeResult(FindNodeResult const & x) = default;
|
||||
FindNodeResult(std::size_t ix, ConcreteNodeType * node) : ix_{ix}, node_{node} {}
|
||||
|
||||
std::size_t ix() const { return ix_; }
|
||||
ConcreteNodeType * node() const { return node_; }
|
||||
|
||||
private:
|
||||
/* 0-based index within parent */
|
||||
std::size_t ix_ = 0;
|
||||
/* a B+ tree node */
|
||||
ConcreteNodeType * node_ = nullptr;
|
||||
}; /*FindNodeResult*/
|
||||
|
||||
template <typename Key,
|
||||
typename Value,
|
||||
typename Properties, tags::ordinal_tag OrdinalTag = Properties::ordinal_tag_value()>
|
||||
struct BplusTreeUtil {
|
||||
public:
|
||||
using GenericNodeType = GenericNode<Key, Value, Properties>;
|
||||
using InternalNodeType = InternalNode<Key, Value, Properties>;
|
||||
using LeafNodeType = LeafNode<Key, Value, Properties>;
|
||||
using const_iterator = detail::ConstIterator<Key, Value, Properties>;
|
||||
|
||||
static std::size_t get_node_size(GenericNodeType const * node) {
|
||||
return node->size();
|
||||
}
|
||||
|
||||
/* only implemented for OrdinalTag = ordinal_enabled */
|
||||
static void print_node_size(std::ostream & os, GenericNodeType const * node) {
|
||||
using xo::xtag;
|
||||
|
||||
os << (node ? node->size() : 0UL);
|
||||
}
|
||||
|
||||
static const_iterator find_ith(GenericNodeType * generic_node,
|
||||
std::size_t i_tree,
|
||||
const_iterator cend) {
|
||||
using xo::xtag;
|
||||
|
||||
if (!generic_node)
|
||||
return cend;
|
||||
|
||||
std::size_t iter = 0;
|
||||
|
||||
/* 100-level B+ tree won't fit in memory -- would have at least 2^100 nodes! */
|
||||
while (iter < 100) {
|
||||
switch (generic_node->node_type()) {
|
||||
case NodeType::leaf:
|
||||
return const_iterator(detail::ID_Forward /*dirn*/,
|
||||
detail::IL_Regular /*loc*/,
|
||||
reinterpret_cast<LeafNodeType const *>(generic_node),
|
||||
i_tree /*item_ix*/);
|
||||
case NodeType::internal:
|
||||
{
|
||||
/* scan for ith member (counting from 0) */
|
||||
|
||||
InternalNodeType const * internal_node
|
||||
= reinterpret_cast<InternalNodeType const *>(generic_node);
|
||||
|
||||
std::size_t sum_z = 0;
|
||||
std::size_t z = 0;
|
||||
|
||||
std::size_t i = 0;
|
||||
std::size_t n = internal_node->n_elt();
|
||||
|
||||
for (; i<n; ++i) {
|
||||
GenericNodeType * child_node = internal_node->lookup_elt(i).child();
|
||||
|
||||
z = child_node->size();
|
||||
|
||||
if (i_tree < sum_z + z) {
|
||||
/* continue search in i'th child of internal_node;
|
||||
* accounting for the sum_z members in nodes to the left of i_child
|
||||
*/
|
||||
generic_node = child_node;
|
||||
i_tree = i_tree - sum_z;
|
||||
break;
|
||||
}
|
||||
|
||||
sum_z += z;
|
||||
}
|
||||
|
||||
if (i == n) {
|
||||
throw std::runtime_error(tostr("BplusTree::find_ith: internal index failure",
|
||||
xtag("i_tree", i_tree),
|
||||
xtag("last_z", z),
|
||||
xtag("n", internal_node->n_elt()),
|
||||
xtag("sum_z", sum_z)));
|
||||
}
|
||||
}
|
||||
break;
|
||||
} /*switch*/
|
||||
|
||||
++iter;
|
||||
} /*loop over descending internal node path*/
|
||||
|
||||
throw std::runtime_error(tostr("BplusTree::find_ith: internal loop failure",
|
||||
xtag("iter", iter)));
|
||||
|
||||
/* impossible! */
|
||||
return cend;
|
||||
} /*find_ith*/
|
||||
|
||||
static void node_clear_size(InternalNodeType * node) {
|
||||
node->clear_size();
|
||||
}
|
||||
|
||||
static void node_add_size(InternalNodeType * node, std::size_t incr_z) {
|
||||
node->add_size(incr_z);
|
||||
}
|
||||
|
||||
static void node_sub_size(InternalNodeType * node, std::size_t decr_z) {
|
||||
node->sub_size(decr_z);
|
||||
}
|
||||
|
||||
static void post_modify_add_ancestor_size(InternalNodeType * node, std::size_t incr_z, bool debug_flag) {
|
||||
using xo::scope;
|
||||
using xo::xtag;
|
||||
|
||||
scope log(XO_DEBUG(debug_flag));
|
||||
|
||||
while (node) {
|
||||
log && log(xtag("node", node),
|
||||
xtag("old_z", node->size()),
|
||||
xtag("incr_z", incr_z));
|
||||
|
||||
node->add_size(incr_z);
|
||||
|
||||
node = node->parent();
|
||||
}
|
||||
} /*post_modify_add_ancestor_size*/
|
||||
|
||||
static void post_modify_sub_ancestor_size(InternalNodeType * node, std::size_t decr_z, bool debug_flag) {
|
||||
using xo::scope;
|
||||
using xo::xtag;
|
||||
|
||||
scope log(XO_DEBUG(debug_flag));
|
||||
|
||||
while (node) {
|
||||
log && log(xtag("node", node),
|
||||
xtag("old_z", node->size()),
|
||||
xtag("decr_z", decr_z));
|
||||
|
||||
node->sub_size(decr_z);
|
||||
|
||||
node = node->parent();
|
||||
}
|
||||
} /*post_modify_sub_ancestor_size*/
|
||||
};
|
||||
|
||||
template <typename Key,
|
||||
typename Value,
|
||||
typename Properties>
|
||||
struct BplusTreeUtil<Key, Value, Properties, tags::ordinal_disabled> {
|
||||
public:
|
||||
using GenericNodeType = GenericNode<Key, Value, Properties>;
|
||||
using InternalNodeType = InternalNode<Key, Value, Properties>;
|
||||
using LeafNodeType = LeafNode<Key, Value, Properties>;
|
||||
using const_iterator = detail::ConstIterator<Key, Value, Properties>;
|
||||
|
||||
static std::size_t get_node_size(GenericNodeType const * node) { return 0; }
|
||||
|
||||
static void print_node_size(std::ostream & os, GenericNodeType const * node) {
|
||||
os << "n/a";
|
||||
}
|
||||
|
||||
/* find_ith not implemented without ordinal feature */
|
||||
static const_iterator find_ith(GenericNodeType * generic_node,
|
||||
std::size_t i_tree,
|
||||
const_iterator cend) {
|
||||
throw std::runtime_error("BplusTreeUtil::find_ith: not implemented (requires tags::ordinal_enabled)");
|
||||
}
|
||||
|
||||
/* per-node size not implemented, so these are no-ops */
|
||||
static void node_clear_size(InternalNodeType * node) {}
|
||||
static void node_add_size(InternalNodeType * node, std::size_t incr_z) {}
|
||||
static void node_sub_size(InternalNodeType * node, std::size_t decr_z) {}
|
||||
static void post_modify_add_ancestor_size(InternalNodeType * node, std::size_t incr_z, bool debug_flag) {}
|
||||
static void post_modify_sub_ancestor_size(InternalNodeType * node, std::size_t decr_z, bool debug_flag) {}
|
||||
};
|
||||
} /*namespace tree*/
|
||||
} /*namespace xo*/
|
||||
|
||||
namespace logutil {
|
||||
template <typename Node>
|
||||
struct nodesize {
|
||||
explicit nodesize(Node const * x) : node_{x} {}
|
||||
|
||||
Node const * node() const { return node_; }
|
||||
|
||||
private:
|
||||
Node const * node_ = nullptr;
|
||||
}; /*nodesize*/
|
||||
|
||||
template <typename Node>
|
||||
inline std::ostream & operator<<(std::ostream & os,
|
||||
nodesize<Node> const & x) {
|
||||
xo::tree::BplusTreeUtil<typename Node::PropertiesType::KeyType,
|
||||
typename Node::PropertiesType::ValueType,
|
||||
typename Node::PropertiesType>::print_node_size(os, x.node());
|
||||
return os;
|
||||
};
|
||||
} /*namespace logutil*/
|
||||
|
||||
/* end BplusTreeUtil.hpp */
|
||||
123
xo-ordinaltree/include/xo/ordinaltree/bplustree/GenericNode.hpp
Normal file
123
xo-ordinaltree/include/xo/ordinaltree/bplustree/GenericNode.hpp
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
/* @file GenericNode.hpp */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BplusTreeUtil.hpp"
|
||||
#include "bplustree_tags.hpp"
|
||||
#include <memory> // for std::unique_ptr
|
||||
#include <string>
|
||||
|
||||
namespace xo {
|
||||
namespace tree {
|
||||
/* shim so we can partially specialize */
|
||||
template <typename Properties, tags::ordinal_tag OrdinalTag = Properties::ordinal_tag_value()>
|
||||
struct GenericNodeBase {
|
||||
}; /*GenericNodeBase*/
|
||||
|
||||
template <typename Properties>
|
||||
struct GenericNodeBase<Properties, tags::ordinal_enabled> {
|
||||
/* #of items (key-value pairs) in this subtree */
|
||||
virtual std::size_t size() const = 0;
|
||||
}; /*GenericNodeShim*/
|
||||
|
||||
// ----- GenericNode -----
|
||||
//
|
||||
// base class for LeafNode, InternalNode
|
||||
|
||||
template <typename Key, typename Value, typename Properties>
|
||||
class GenericNode : public GenericNodeBase<Properties> {
|
||||
public:
|
||||
using PropertiesType = Properties;
|
||||
using InternalNodeType = InternalNode<Key, Value, Properties>;
|
||||
using LeafNodeType = LeafNode<Key, Value, Properties>;
|
||||
|
||||
public:
|
||||
explicit GenericNode(NodeType ntype, std::size_t branching_factor)
|
||||
: node_type_{ntype}, branching_factor_{branching_factor} {}
|
||||
virtual ~GenericNode() = default;
|
||||
|
||||
NodeType node_type() const { return node_type_; }
|
||||
InternalNodeType * parent() const { return parent_; }
|
||||
std::size_t n_elt() const { return n_elt_; }
|
||||
std::size_t branching_factor() const { return branching_factor_; }
|
||||
|
||||
void set_parent(InternalNodeType * x) { this->parent_ = x; }
|
||||
|
||||
#ifdef OBSOLETE
|
||||
/* #of items (key-value pairs) in this subtree */
|
||||
virtual std::size_t size() const = 0;
|
||||
#endif
|
||||
|
||||
virtual Key const & glb_key() const = 0;
|
||||
/* support methods for BplusTree::verify()
|
||||
* with_lub. true to use lub_key; false to ignore
|
||||
* lub_key. if with_lub=true, strict least upper bound key (in B+ tree) for this subtree;
|
||||
* all keys in this subtree must be strictly less than lub_key.
|
||||
* ignored when with_lub=false
|
||||
* lh_leaf. if null, this subtree contains the smallest key in ancestor B+ tree;
|
||||
* if non-null, lh_leaf's rightmost key is immediate predecessor
|
||||
* of leftmost key in this subtree
|
||||
* rh_leaf. if null, this subtree contains the largest key in ancestor B+ tree;
|
||||
* if non-null, rh_leaf's leftmost key is immediate successor
|
||||
* of rightmost key in this subtree
|
||||
*/
|
||||
virtual std::size_t verify_helper(InternalNodeType const * parent,
|
||||
bool with_lub,
|
||||
Key const & lub_key,
|
||||
LeafNodeType const * lh_leaf,
|
||||
LeafNodeType const * rh_leaf) const = 0;
|
||||
virtual void verify_glb_key(Key const & key) const = 0;
|
||||
FindNodeResult<LeafNodeType const> c_find_min_leaf_node() const;
|
||||
FindNodeResult<LeafNodeType const> c_find_max_leaf_node() const;
|
||||
|
||||
virtual FindNodeResult<LeafNodeType> find_min_leaf_node() = 0;
|
||||
virtual FindNodeResult<LeafNodeType> find_max_leaf_node() = 0;
|
||||
|
||||
/* notification just before permanently removing this node from B+ tree */
|
||||
virtual void notify_remove() {}
|
||||
|
||||
private:
|
||||
/* NodeType::internal | NodeType::leaf */
|
||||
NodeType node_type_;
|
||||
/* pointer to parent node
|
||||
* invariant: parent has direct pointer to this node,
|
||||
* except briefly during construction
|
||||
*/
|
||||
InternalNodeType * parent_ = nullptr;
|
||||
|
||||
protected:
|
||||
/* #of non-empty elements (children) of this node
|
||||
*
|
||||
* invariant:
|
||||
* - .elt_v[i].child.ptr is non-null for 0 <= i < .n_elt
|
||||
* - for (0 < i < .n_elt):
|
||||
* .elt_v[i-1].key < .elt_v[i].key
|
||||
* - elt_v[i].key not defined for (i >= .n_elt)
|
||||
*/
|
||||
std::size_t n_elt_ = 0;
|
||||
/* need to store actual branching factor, for LeafNode/InternalNode dtors */
|
||||
std::size_t branching_factor_ = 0;
|
||||
}; /*GenericNode*/
|
||||
|
||||
/* const version (non-const version below) */
|
||||
template <typename Key, typename Value, typename Properties>
|
||||
FindNodeResult<LeafNode<Key, Value, Properties> const>
|
||||
GenericNode<Key, Value, Properties>::c_find_min_leaf_node() const {
|
||||
InternalNode<Key, Value, Properties> * self = const_cast<InternalNode<Key, Value, Properties> *>(this);
|
||||
|
||||
return self->find_min_leaf_node();
|
||||
} /*c_find_min_leaf_node*/
|
||||
|
||||
/* const version (non-const version below) */
|
||||
template <typename Key, typename Value, typename Properties>
|
||||
FindNodeResult<LeafNode<Key, Value, Properties> const>
|
||||
GenericNode<Key, Value, Properties>::c_find_max_leaf_node() const {
|
||||
InternalNode<Key, Value, Properties> * self = const_cast<InternalNode<Key, Value, Properties> *>(this);
|
||||
|
||||
return self->find_max_leaf_node();
|
||||
} /*c_find_max_leaf_node*/
|
||||
|
||||
} /*namespace tree*/
|
||||
} /*namespace xo*/
|
||||
|
||||
/* end GenericNode.hpp */
|
||||
768
xo-ordinaltree/include/xo/ordinaltree/bplustree/InternalNode.hpp
Normal file
768
xo-ordinaltree/include/xo/ordinaltree/bplustree/InternalNode.hpp
Normal file
|
|
@ -0,0 +1,768 @@
|
|||
/* @file InternalNode.hpp */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "GenericNode.hpp"
|
||||
#include "xo/indentlog/scope.hpp"
|
||||
#include "xo/indentlog/print/tostr.hpp"
|
||||
#include <cassert>
|
||||
|
||||
namespace xo {
|
||||
namespace tree {
|
||||
// ----- InternalNodeItem ------
|
||||
|
||||
/* see also: NodeItem<NodeType::leaf, Key, Value, Properties> */
|
||||
template <typename Key, typename Value, typename Properties>
|
||||
struct NodeItem<NodeType::internal, Key, Value, Properties> {
|
||||
using GenericNodeType = GenericNode<Key, Value, Properties>;
|
||||
|
||||
public:
|
||||
NodeItem() = default;
|
||||
explicit NodeItem(std::unique_ptr<GenericNodeType> child)
|
||||
: child_{std::move(child)} {
|
||||
if (child_)
|
||||
this->key_ = child_->glb_key();
|
||||
}
|
||||
|
||||
Key const & key() const { return key_; }
|
||||
GenericNodeType * child() const { return child_.get(); }
|
||||
|
||||
std::unique_ptr<GenericNodeType> release_child() { return std::move(child_); }
|
||||
|
||||
void set_key(Key key) { key_ = std::move(key); }
|
||||
|
||||
void notify_remove() {
|
||||
if (child_)
|
||||
child_->notify_remove();
|
||||
} /*notify_remove*/
|
||||
|
||||
private:
|
||||
/* invariant: .key is leftmost key in subtree rooted at .child
|
||||
* (i.e. greatest lower bound for keys in that subtree)
|
||||
*/
|
||||
Key key_;
|
||||
/* subtree. subtree has minimum key value .key */
|
||||
std::unique_ptr<GenericNodeType> child_;
|
||||
}; /*NodeItem */
|
||||
|
||||
template <typename Key, typename Value, typename Properties>
|
||||
using InternalNodeItem = NodeItem<NodeType::internal, Key, Value, Properties>;
|
||||
|
||||
/* struct with same size as InternalNodeItem<Key,Properties>, but POD + with no ctor/dtor */
|
||||
template <typename Key, typename Value, typename Properties>
|
||||
using InternalNodeItemPlaceholder = NodeItemPlaceholder<NodeType::internal, Key, Value, Properties>;
|
||||
|
||||
/* default implements tags::ordinal_disabled; see partial specialization below for ordinal_enabled */
|
||||
template <typename Key, typename Value, typename Properties, tags::ordinal_tag OrdinalTag = Properties::ordinal_tag_value()>
|
||||
struct InternalNodeShim : public GenericNode<Key, Value, Properties> {
|
||||
public:
|
||||
using GenericNodeType = GenericNode<Key, Value, Properties>;
|
||||
|
||||
public:
|
||||
InternalNodeShim(NodeType ntype, std::size_t branching_factor) : GenericNode<Key, Value, Properties>{ntype, branching_factor} {}
|
||||
|
||||
protected:
|
||||
/* not implemented with tags::ordinal_disabled */
|
||||
void assign_size(std::size_t z) {}
|
||||
};
|
||||
|
||||
template <typename Key, typename Value, typename Properties>
|
||||
struct InternalNodeShim<Key, Value, Properties, tags::ordinal_enabled> : public GenericNode<Key, Value, Properties> {
|
||||
public:
|
||||
using GenericNodeType = GenericNode<Key, Value, Properties>;
|
||||
|
||||
public:
|
||||
InternalNodeShim(NodeType ntype, std::size_t branching_factor) : GenericNode<Key, Value, Properties>{ntype, branching_factor} {}
|
||||
|
||||
void clear_size() { this->size_ = 0; }
|
||||
void add_size(std::size_t z) { this->size_ += z; }
|
||||
void sub_size(std::size_t z) { this->size_ -= z; }
|
||||
|
||||
virtual std::size_t size() const override { return size_; }
|
||||
|
||||
protected:
|
||||
void assign_size(std::size_t z) { this->size_ = z; }
|
||||
|
||||
protected:
|
||||
std::size_t size_ = 0;
|
||||
}; /*InternalNodeShim*/
|
||||
|
||||
/* require:
|
||||
* - Properties.branching_factor()
|
||||
*/
|
||||
template <typename Key, typename Value, typename Properties>
|
||||
struct InternalNode : public InternalNodeShim<Key, Value, Properties> {
|
||||
public:
|
||||
using GenericNodeType = GenericNode<Key, Value, Properties>;
|
||||
using InternalNodeType = InternalNode<Key, Value, Properties>;
|
||||
using LeafNodeType = LeafNode<Key, Value, Properties>;
|
||||
using InternalNodeItemPlaceholderType = InternalNodeItemPlaceholder<Key, Value, Properties>;
|
||||
using InternalNodeItemType = InternalNodeItem<Key, Value, Properties>;
|
||||
|
||||
public:
|
||||
virtual ~InternalNode();
|
||||
|
||||
/* node size in bytes (increases with branching factor) */
|
||||
static std::size_t node_sizeof(std::size_t branching_factor);
|
||||
|
||||
/* use when splitting root node for the first time;
|
||||
* new root node will be leaf->internal.
|
||||
*
|
||||
* require: child_1, child_2 are non-empty
|
||||
*/
|
||||
static std::unique_ptr<InternalNode> make_2(std::unique_ptr<GenericNodeType> child_1,
|
||||
std::unique_ptr<GenericNodeType> child_2);
|
||||
|
||||
/* Before:
|
||||
*
|
||||
* m = mid_ix
|
||||
* n = src.n_elt - 1
|
||||
* xa @ [m-1]
|
||||
* xb @ [m]
|
||||
* xz @ [n-1]
|
||||
*
|
||||
* src.elt_v[]
|
||||
*
|
||||
* 0 m-1 m n-1
|
||||
* +----+-...-+----+----+-...-+----+
|
||||
* | x0 | ... | xa | xb | ... | xz |
|
||||
* +----+-...-+----+----+-...-+----+
|
||||
*
|
||||
* <----------- n items ----------->
|
||||
*
|
||||
* After:
|
||||
*
|
||||
* src.elt_v[] new_node.elt_v[]
|
||||
*
|
||||
* n-m-1
|
||||
* 0 m-1 0 v
|
||||
* +----+-...-+----+ +----+-...-+----+
|
||||
* | x0 | ... | xa | | xb | | xz |
|
||||
* +----+-...-+----+ +----+-...-+----+
|
||||
*
|
||||
* <--- m items ---> <-- n-m items -->
|
||||
*/
|
||||
static std::unique_ptr<InternalNode> annex(std::size_t mid_ix,
|
||||
InternalNode * src);
|
||||
|
||||
/* .elt_v[]
|
||||
*
|
||||
* 0 k n-1 with: n <= b = branching factor
|
||||
* +---+---+- ... -+---+- ... -+---+---+ k = lub(key) in {e1..en}
|
||||
* | e1| e2| | ek| | | en|
|
||||
* +---+---+- ... -+---+- ... -+---+---+
|
||||
*
|
||||
* retval.first: true if key already present in tree. implies lub_ix_recd.second >= 1
|
||||
* retval.second: upper bound (strict) index position in .elt_v[] of key
|
||||
*
|
||||
* Cost: O(log(bf)) key comparisons
|
||||
*/
|
||||
std::size_t find_lub_ix(Key const & key) const;
|
||||
|
||||
/* warning: requires key is present! */
|
||||
std::size_t find_ix(Key const & key) const { return this->find_lub_ix(key) - 1; }
|
||||
|
||||
/* O(bf), but does not rely on key invariants. */
|
||||
std::size_t locate_child_by_address(GenericNodeType const * target_child) const;
|
||||
|
||||
InternalNodeItemType & lookup_elt(std::size_t i) { return *(reinterpret_cast<InternalNodeItemType *>(&(elt_v_[i]))); }
|
||||
|
||||
InternalNodeItemType const & lookup_elt(std::size_t i) const { return *(reinterpret_cast<InternalNodeItemType const *>(&(elt_v_[i]))); }
|
||||
|
||||
FindNodeResult<GenericNodeType> find_child(Key const & key);
|
||||
|
||||
/* insert node at position ix; moving items starting in .elt_v[ix] one slot to the right */
|
||||
void insert_node(std::size_t ix, std::unique_ptr<GenericNodeType> child, bool debug_flag);
|
||||
|
||||
/* remove node at position ix; moving items starting .elt_v[ix+1] one slot to the left;
|
||||
* if target is a leaf node, also remove from prev_leafnode/next_leafnode list
|
||||
*/
|
||||
void remove_node(std::size_t ix, bool debug_flag);
|
||||
|
||||
/* redistribute last n items from left-hand sibling lh to this internal node */
|
||||
void prepend_from_lh_sibling(InternalNode * lh, std::size_t n, bool debug_flag);
|
||||
|
||||
/* redistribute first n items from right-hand sibling rh to this internal node */
|
||||
void append_from_rh_sibling(std::size_t n, InternalNode * rh);
|
||||
|
||||
void append_rh_sibling(InternalNode * rh) { this->append_from_rh_sibling(rh->n_elt(), rh); }
|
||||
|
||||
/* returns new node with upper half of original element vector (i.e. of this.elt_v[]);
|
||||
* original updated to retain lower half
|
||||
*/
|
||||
std::unique_ptr<InternalNode> split_internal();
|
||||
|
||||
void set_glb_key(Key key) { this->lookup_elt(0).set_key(key); }
|
||||
|
||||
/* memory for InternalNode instances is always created using new[],
|
||||
* so required to use delete[] to deallocate
|
||||
*/
|
||||
void operator delete (void * mem) noexcept { ::operator delete[](mem); }
|
||||
|
||||
// ----- inherited from GenericNode -----
|
||||
|
||||
virtual Key const & glb_key() const override { return this->lookup_elt(0).key(); }
|
||||
|
||||
virtual std::size_t verify_helper(InternalNode const * parent,
|
||||
bool with_lub_flag,
|
||||
Key const & lub_key,
|
||||
LeafNodeType const * lh_leaf,
|
||||
LeafNodeType const * rh_leaf) const override;
|
||||
|
||||
virtual void verify_glb_key(Key const & key) const override;
|
||||
|
||||
/* find in subtree_arg the leftmost leaf node (i.e. leaf node with smallest key) */
|
||||
virtual FindNodeResult<LeafNodeType> find_min_leaf_node() override;
|
||||
/* find in subtree_arg the rightmost leaf node (i.e. leaf node with largest key) */
|
||||
virtual FindNodeResult<LeafNodeType> find_max_leaf_node() override;
|
||||
|
||||
private:
|
||||
explicit InternalNode(std::size_t branching_factor);
|
||||
|
||||
private:
|
||||
#ifdef OBSOLETE
|
||||
/* total #of elements in this subtree */
|
||||
std::size_t size_ = 0;
|
||||
#endif
|
||||
/* flexible array; actual size will be .branching_factor().
|
||||
*
|
||||
* .elt_v[i] is created/destroyed as an InternalNodeItemType with non-trivial ctor/dtor.
|
||||
* we must declare member using POD placeholder to satisfy flexible array rules
|
||||
*
|
||||
* invariant:
|
||||
* - with branching factor b, so range for .elt_v[] is 0 .. b-1:
|
||||
* - .elt_v[j].child.ptr is null -> {.elt_v[j+1].child.ptr .. .elt_v[b-1].child.ptr} are also null
|
||||
*/
|
||||
InternalNodeItemPlaceholderType elt_v_[];
|
||||
}; /*InternalNode*/
|
||||
|
||||
template <typename Key, typename Value, typename Properties>
|
||||
InternalNode<Key, Value, Properties>::~InternalNode() {
|
||||
/* since we're using flexible array for .elt_v[], need to manually run destructors */
|
||||
for (std::size_t i=0, n=this->branching_factor_; i<n; ++i) {
|
||||
this->lookup_elt(i).~InternalNodeItemType();
|
||||
}
|
||||
|
||||
/* hygiene */
|
||||
BplusTreeUtil<Key, Value, Properties>::node_clear_size(this);
|
||||
this->n_elt_ = 0;
|
||||
this->branching_factor_ = 0;
|
||||
} /*dtor*/
|
||||
|
||||
template <typename Key, typename Value, typename Properties>
|
||||
std::size_t
|
||||
InternalNode<Key, Value, Properties>::node_sizeof(std::size_t branching_factor) {
|
||||
return (sizeof(InternalNode)
|
||||
+ (branching_factor
|
||||
* sizeof(InternalNodeItemType)));
|
||||
} /*node_sizeof*/
|
||||
|
||||
template <typename Key, typename Value, typename Properties>
|
||||
std::unique_ptr<InternalNode<Key, Value, Properties>>
|
||||
InternalNode<Key, Value, Properties>::make_2(std::unique_ptr<GenericNodeType> child_1,
|
||||
std::unique_ptr<GenericNodeType> child_2) {
|
||||
std::size_t branching_factor = child_1->branching_factor();
|
||||
|
||||
std::size_t mem_z = node_sizeof(branching_factor);
|
||||
std::uint8_t * mem = new std::uint8_t[mem_z];
|
||||
|
||||
assert(child_1->n_elt() > 0);
|
||||
assert(child_2->n_elt() > 0);
|
||||
|
||||
std::unique_ptr<InternalNode> retval(new (mem) InternalNode(branching_factor));
|
||||
|
||||
child_1->set_parent(retval.get());
|
||||
child_2->set_parent(retval.get());
|
||||
|
||||
retval->assign_size(BplusTreeUtil<Key, Value, Properties>::get_node_size(child_1.get())
|
||||
+ BplusTreeUtil<Key, Value, Properties>::get_node_size(child_2.get()));
|
||||
retval->n_elt_ = 2;
|
||||
|
||||
retval->lookup_elt(0) = std::move(InternalNodeItemType(std::move(child_1)));
|
||||
retval->lookup_elt(1) = std::move(InternalNodeItemType(std::move(child_2)));
|
||||
|
||||
return retval;
|
||||
} /*make_2*/
|
||||
|
||||
template <typename Key, typename Value, typename Properties>
|
||||
std::unique_ptr<InternalNode<Key, Value, Properties>>
|
||||
InternalNode<Key, Value, Properties>::annex(std::size_t mid_ix,
|
||||
InternalNode * src)
|
||||
{
|
||||
std::size_t branching_factor = src->branching_factor();
|
||||
|
||||
std::size_t mem_z = node_sizeof(branching_factor);
|
||||
std::uint8_t * mem = new std::uint8_t[mem_z];
|
||||
|
||||
std::unique_ptr<InternalNode> new_node(new (mem) InternalNode(branching_factor));
|
||||
|
||||
std::size_t hi_ix = src->n_elt();
|
||||
|
||||
new_node->n_elt_ = hi_ix - mid_ix;
|
||||
|
||||
std::size_t annex_z = 0;
|
||||
|
||||
/* annexing upper-half of *src into new_node */
|
||||
for (std::size_t i = 0, n = hi_ix - mid_ix; i < n; ++i) {
|
||||
InternalNodeItemType & src_slot = src->lookup_elt(mid_ix + i);
|
||||
InternalNodeItemType & new_slot = new_node->lookup_elt(i);
|
||||
|
||||
annex_z += BplusTreeUtil<Key, Value, Properties>::get_node_size(src_slot.child());
|
||||
|
||||
new_slot = std::move(src->lookup_elt(mid_ix + i));
|
||||
new_slot.child()->set_parent(new_node.get());
|
||||
}
|
||||
|
||||
new_node->assign_size(annex_z);
|
||||
|
||||
/* ordinal_disabled: noop
|
||||
* ordinal_enabled: bookkeeping for src.size (+ new_node.size, see above)
|
||||
*/
|
||||
src->assign_size(BplusTreeUtil<Key, Value, Properties>::get_node_size(src) - annex_z);
|
||||
src->n_elt_ = mid_ix;
|
||||
|
||||
return new_node;
|
||||
} /*annex*/
|
||||
|
||||
template <typename Key, typename Value, typename Properties>
|
||||
std::size_t
|
||||
InternalNode<Key, Value, Properties>::find_lub_ix(Key const & key) const {
|
||||
if (key < this->lookup_elt(0).key())
|
||||
return 0;
|
||||
|
||||
std::size_t lo = 0;
|
||||
std::size_t hi = this->n_elt_;
|
||||
|
||||
while (lo + 1 < hi) {
|
||||
std::size_t mid = lo + (hi - lo) / 2;
|
||||
|
||||
if (key < this->lookup_elt(mid).key())
|
||||
hi = mid;
|
||||
else
|
||||
lo = mid;
|
||||
}
|
||||
|
||||
return hi;
|
||||
} /*find_lub_ix*/
|
||||
|
||||
template <typename Key, typename Value, typename Properties>
|
||||
std::size_t
|
||||
InternalNode<Key, Value, Properties>::locate_child_by_address(GenericNodeType const * target_child) const {
|
||||
for (std::size_t ix = 0; ix < this->n_elt_; ++ix) {
|
||||
if (this->lookup_elt(ix).child() == target_child)
|
||||
return ix;
|
||||
}
|
||||
|
||||
return static_cast<std::size_t>(-1);
|
||||
} /*locate_child_by_address*/
|
||||
|
||||
template <typename Key, typename Value, typename Properties>
|
||||
FindNodeResult<LeafNode<Key, Value, Properties>>
|
||||
InternalNode<Key, Value, Properties>::find_min_leaf_node() {
|
||||
FindNodeResult<GenericNodeType> findresult(0, this);
|
||||
|
||||
while (findresult.node() && (findresult.node()->node_type() == NodeType::internal)) {
|
||||
std::size_t min_ix = 0;
|
||||
|
||||
findresult = FindNodeResult<GenericNodeType>(min_ix,
|
||||
(reinterpret_cast<InternalNodeType *>(findresult.node()))
|
||||
->lookup_elt(min_ix /*leftmost child*/).child());
|
||||
}
|
||||
|
||||
/* findresult.node()->node_type() == NodeType::leaf (if non-null) */
|
||||
|
||||
if (!findresult.node()) {
|
||||
assert(false);
|
||||
return FindNodeResult<LeafNodeType>();
|
||||
}
|
||||
|
||||
assert(findresult.node()->node_type() == NodeType::leaf);
|
||||
|
||||
return FindNodeResult<LeafNodeType>(findresult.ix(),
|
||||
reinterpret_cast<LeafNodeType *>(findresult.node()));
|
||||
} /*find_min_leaf_node*/
|
||||
|
||||
template <typename Key, typename Value, typename Properties>
|
||||
FindNodeResult<LeafNode<Key, Value, Properties>>
|
||||
InternalNode<Key, Value, Properties>::find_max_leaf_node() {
|
||||
FindNodeResult<GenericNodeType> findresult(0, this);
|
||||
|
||||
while (findresult.node() && (findresult.node()->node_type() == NodeType::internal)) {
|
||||
std::size_t max_ix = findresult.node()->n_elt() - 1;
|
||||
|
||||
findresult = FindNodeResult<GenericNodeType>
|
||||
(max_ix,
|
||||
(reinterpret_cast<InternalNodeType *>(findresult.node()))
|
||||
->lookup_elt(max_ix /*rightmost child*/).child());
|
||||
}
|
||||
|
||||
/* findresult.node()->node_type() == NodeType::leaf (if non-null) */
|
||||
|
||||
if (!findresult.node()) {
|
||||
assert(false);
|
||||
return FindNodeResult<LeafNodeType>();
|
||||
}
|
||||
|
||||
assert(findresult.node()->node_type() == NodeType::leaf);
|
||||
|
||||
return FindNodeResult<LeafNodeType>(findresult.ix(),
|
||||
reinterpret_cast<LeafNodeType *>(findresult.node()));
|
||||
} /*find_max_leaf_node*/
|
||||
|
||||
template <typename Key, typename Value, typename Properties>
|
||||
FindNodeResult<GenericNode<Key, Value, Properties>>
|
||||
InternalNode<Key, Value, Properties>::find_child(Key const & key) {
|
||||
std::size_t lub_ix = this->find_lub_ix(key);
|
||||
|
||||
if (lub_ix > 0)
|
||||
--lub_ix;
|
||||
|
||||
return FindNodeResult<GenericNodeType>(lub_ix, this->lookup_elt(lub_ix).child());
|
||||
} /*find_child*/
|
||||
|
||||
template <typename Key, typename Value, typename Properties>
|
||||
void
|
||||
InternalNode<Key, Value, Properties>::insert_node(std::size_t ix, std::unique_ptr<GenericNodeType> child, bool debug_flag)
|
||||
{
|
||||
using xo::scope;
|
||||
using xo::tostr;
|
||||
using xo::xtag;
|
||||
|
||||
scope log(XO_DEBUG(debug_flag),
|
||||
xtag("self", this),
|
||||
xtag("n_elt", this->n_elt()),
|
||||
xtag("bf", this->branching_factor()),
|
||||
xtag("ix", ix),
|
||||
xtag("child", child.get()));
|
||||
|
||||
if (this->n_elt_ >= this->branching_factor()) {
|
||||
assert(false);
|
||||
throw std::runtime_error(tostr("InternalNode::insert_node: node already full",
|
||||
xtag("node.n_elt", this->n_elt()),
|
||||
xtag("branching_factor", this->branching_factor())));
|
||||
}
|
||||
|
||||
if (ix > this->n_elt_) {
|
||||
assert(false);
|
||||
throw std::runtime_error(tostr("InternalNode::insert_node: insert position out of range",
|
||||
xtag("ix", ix),
|
||||
xtag("node.n_elt", this->n_elt()),
|
||||
xtag("bf", this->branching_factor())));
|
||||
}
|
||||
|
||||
std::size_t pos_ix = this->n_elt_;
|
||||
|
||||
while (pos_ix > ix) {
|
||||
this->lookup_elt(pos_ix) = std::move(this->lookup_elt(pos_ix - 1));
|
||||
--pos_ix;
|
||||
}
|
||||
|
||||
/* WARNING: don't update .size here
|
||||
* in practice we use .insert_node() when introducing a single new key/value pair;
|
||||
* when we use .insert_node() we split an existing node,
|
||||
* and actually just want to increment .size.
|
||||
*
|
||||
* We leave this to caller (e.g. BplusTree.internal_insert_aux())
|
||||
* because in that context can see the upstream split
|
||||
*/
|
||||
// this->size_ += child->n_elt();
|
||||
|
||||
++(this->n_elt_);
|
||||
child->set_parent(this);
|
||||
this->lookup_elt(ix) = InternalNodeItemType(std::move(child));
|
||||
} /*insert_node*/
|
||||
|
||||
template <typename Key, typename Value, typename Properties>
|
||||
void
|
||||
InternalNode<Key, Value, Properties>::remove_node(std::size_t ix, bool debug_flag) {
|
||||
using xo::scope;
|
||||
using xo::tostr;
|
||||
using xo::xtag;
|
||||
|
||||
scope log(XO_DEBUG(debug_flag),
|
||||
xtag("self", this),
|
||||
xtag("n_elt", this->n_elt()),
|
||||
xtag("bf", this->branching_factor()),
|
||||
xtag("ix", ix));
|
||||
|
||||
if (ix >= this->n_elt_) {
|
||||
assert(false);
|
||||
throw std::runtime_error(tostr("InternalNode::remove_node: target position out of range",
|
||||
xtag("ix", ix),
|
||||
xtag("node.n_elt", this->n_elt()),
|
||||
xtag("bf", this->branching_factor())));
|
||||
}
|
||||
|
||||
std::size_t pos_ix = ix;
|
||||
std::size_t end_ix = this->n_elt_ - 1;
|
||||
|
||||
{
|
||||
InternalNodeItemType & target_item = this->lookup_elt(pos_ix);
|
||||
|
||||
/* WARNING: don't update .size here
|
||||
* in practice we use .remove_node() when deleting a single new key/value pair;
|
||||
* when we use .remove_node() we merge existing nodes,
|
||||
* and actually just want to decrement .size.
|
||||
*
|
||||
* We leave this to caller (e.g. BplusTree.internal_remove_aux())
|
||||
* because in that context can see the upstream merge
|
||||
*/
|
||||
//this->size_ -= target_item.child()->size();
|
||||
target_item.notify_remove();
|
||||
}
|
||||
|
||||
while (pos_ix < end_ix) {
|
||||
//scope x1("loop", debug_flag);
|
||||
//x1(xtag("pos_ix", pos_ix));
|
||||
|
||||
this->lookup_elt(pos_ix) = std::move(this->lookup_elt(pos_ix + 1));
|
||||
++pos_ix;
|
||||
}
|
||||
|
||||
--(this->n_elt_);
|
||||
} /*remove_node*/
|
||||
|
||||
template <typename Key, typename Value, typename Properties>
|
||||
void
|
||||
InternalNode<Key, Value, Properties>::prepend_from_lh_sibling(InternalNode * lh, std::size_t n, bool debug_flag) {
|
||||
using xo::scope;
|
||||
using xo::xtag;
|
||||
|
||||
scope log(XO_DEBUG(debug_flag),
|
||||
xtag("@", this), xtag("n", n));
|
||||
|
||||
if (this->n_elt() + n > this->branching_factor()) {
|
||||
assert(false);
|
||||
throw std::runtime_error(tostr("InternalNode.prepend_from_lh_sibling: expected combined #elt <= bf",
|
||||
xtag("self.n_elt", this->n_elt()),
|
||||
xtag("n", n),
|
||||
xtag("bf", this->branching_factor())));
|
||||
}
|
||||
|
||||
std::size_t n_lh = lh->n_elt();
|
||||
std::size_t n_rh = this->n_elt();
|
||||
|
||||
/* move elts in *this to the right n steps (starting from the end) */
|
||||
for (std::size_t ixp1 = this->n_elt(); ixp1 > 0; --ixp1) {
|
||||
std::size_t ix = ixp1 - 1;
|
||||
//x.log("move", xtag("ix", ix), xtag("ix+n", ix+n));
|
||||
this->lookup_elt(ix + n) = std::move(this->lookup_elt(ix));
|
||||
}
|
||||
|
||||
std::size_t xfer_z = 0;
|
||||
|
||||
/* xfer n elts from upper end of lh, to lower end of *this */
|
||||
for (std::size_t ix = 0; ix < n; ++ix) {
|
||||
//x.log("fill", xtag("ix", ix), xtag("n_lh-n+ix", n_lh - n + ix));
|
||||
|
||||
InternalNodeItemType & lh_sibling_item = lh->lookup_elt(n_lh - n + ix);
|
||||
|
||||
xfer_z += BplusTreeUtil<Key, Value, Properties>::get_node_size(lh_sibling_item.child());
|
||||
|
||||
this->lookup_elt(ix) = std::move(lh_sibling_item);
|
||||
/* + fixup parent pointer */
|
||||
this->lookup_elt(ix).child()->set_parent(this);
|
||||
}
|
||||
|
||||
BplusTreeUtil<Key, Value, Properties>::node_add_size(this, xfer_z);
|
||||
BplusTreeUtil<Key, Value, Properties>::node_sub_size(lh, xfer_z);
|
||||
|
||||
this->n_elt_ += n;
|
||||
lh->n_elt_ -= n;
|
||||
|
||||
log && log(xtag("this.glb_key", this->glb_key()),
|
||||
xtag("this[0].key", this->lookup_elt(0).key()));
|
||||
|
||||
log.end_scope();
|
||||
} /*prepend_from_lh_sibling*/
|
||||
|
||||
template <typename Key, typename Value, typename Properties>
|
||||
void
|
||||
InternalNode<Key, Value, Properties>::append_from_rh_sibling(std::size_t n, InternalNode * rh) {
|
||||
using xo::xtag;
|
||||
|
||||
if (this->n_elt() + n > this->branching_factor()) {
|
||||
assert(false);
|
||||
throw std::runtime_error(tostr("InternalNode.append_from_rh_sibling: expected combined #elt <= bf",
|
||||
xtag("self.n_elt", this->n_elt()),
|
||||
xtag("n", n),
|
||||
xtag("bf", this->branching_factor())));
|
||||
}
|
||||
|
||||
std::size_t n_lh = this->n_elt();
|
||||
std::size_t xfer_z = 0;
|
||||
|
||||
for (std::size_t ix = 0; ix < n; ++ix) {
|
||||
InternalNodeItemType & rh_sibling_item = rh->lookup_elt(ix);
|
||||
|
||||
xfer_z += BplusTreeUtil<Key, Value, Properties>::get_node_size(rh_sibling_item.child());
|
||||
this->lookup_elt(n_lh + ix) = std::move(rh_sibling_item);
|
||||
/* + fixup parent pointer */
|
||||
this->lookup_elt(n_lh + ix).child()->set_parent(this);
|
||||
}
|
||||
|
||||
BplusTreeUtil<Key, Value, Properties>::node_add_size(this, xfer_z);
|
||||
this->n_elt_ += n;
|
||||
|
||||
/* shuffle remaining members of rh sibling n items to the left */
|
||||
for (std::size_t ix = 0; ix < rh->n_elt() - n; ++ix) {
|
||||
rh->lookup_elt(ix) = std::move(rh->lookup_elt(ix + n));
|
||||
}
|
||||
|
||||
BplusTreeUtil<Key, Value, Properties>::node_sub_size(rh, xfer_z);
|
||||
rh->n_elt_ -= n;
|
||||
} /*append_from_rh_sibling*/
|
||||
|
||||
template <typename Key, typename Value, typename Properties>
|
||||
std::unique_ptr<InternalNode<Key, Value, Properties>>
|
||||
InternalNode<Key, Value, Properties>::split_internal() {
|
||||
std::size_t n_elt = this->n_elt_;
|
||||
std::size_t mid_ix = n_elt / 2;
|
||||
|
||||
return InternalNode::annex(mid_ix, this);
|
||||
} /*split_internal*/
|
||||
|
||||
template <typename Key, typename Value, typename Properties>
|
||||
std::size_t
|
||||
InternalNode<Key, Value, Properties>::verify_helper(InternalNode const * parent,
|
||||
bool with_lub_flag,
|
||||
Key const & lub_key,
|
||||
LeafNodeType const * lh_leaf,
|
||||
LeafNodeType const * rh_leaf) const
|
||||
{
|
||||
using xo::tostr;
|
||||
using xo::xtag;
|
||||
|
||||
std::size_t retval = 0;
|
||||
|
||||
/* verify immediate parent pointer is correct */
|
||||
if (this->parent() != parent) {
|
||||
throw std::runtime_error(tostr("InternalNode::verify_helper"
|
||||
": expected parent pointer to refer to actual parent",
|
||||
xtag("stored_parent", this->parent()),
|
||||
xtag("actual_parent", parent)));
|
||||
}
|
||||
|
||||
std::size_t n = this->n_elt_;
|
||||
|
||||
/* verify all children have same NodeType (either all= internal or all= leaf) */
|
||||
NodeType target_child_node_type = NodeType::leaf;
|
||||
|
||||
if (n > 0)
|
||||
target_child_node_type = this->lookup_elt(0).child()->node_type();
|
||||
|
||||
LeafNodeType const * prev_lh_leaf = lh_leaf;
|
||||
|
||||
for (std::size_t i=0; i < n; ++i) {
|
||||
/* check consistent node type */
|
||||
NodeType i_nodetype = this->lookup_elt(i).child()->node_type();
|
||||
|
||||
if ((i > 0) && (i_nodetype != target_child_node_type)) {
|
||||
throw std::runtime_error(tostr("InternalNode::verify_helper"
|
||||
": expected all children to share the same node type",
|
||||
xtag("i", i),
|
||||
xtag("elt[0].node_type", target_child_node_type),
|
||||
xtag("elt[i].node_type", i_nodetype)));
|
||||
}
|
||||
|
||||
/* nested verify on child subtrees */
|
||||
InternalNodeItemType const & i_elt = this->lookup_elt(i);
|
||||
|
||||
LeafNodeType const * next_lh_leaf = ((i+1 < n)
|
||||
? this->lookup_elt(i+1).child()->find_min_leaf_node().node()
|
||||
: rh_leaf);
|
||||
|
||||
retval += i_elt.child()->verify_helper(this,
|
||||
(i+1 < n) ? true : with_lub_flag,
|
||||
(i+1 < n) ? this->lookup_elt(i+1).key() : lub_key,
|
||||
prev_lh_leaf,
|
||||
next_lh_leaf);
|
||||
|
||||
prev_lh_leaf = i_elt.child()->find_max_leaf_node().node();
|
||||
}
|
||||
|
||||
if (Properties::ordinal_tag_value() == tags::ordinal_enabled) {
|
||||
/* verify stored subtree size is consistent with children's */
|
||||
std::size_t sum_z = 0;
|
||||
|
||||
for (std::size_t i=0, n=this->n_elt_; i < n; ++i) {
|
||||
InternalNodeItemType const & elt = this->lookup_elt(i);
|
||||
|
||||
sum_z += BplusTreeUtil<Key, Value, Properties>::get_node_size(elt.child());
|
||||
}
|
||||
|
||||
std::size_t self_z = BplusTreeUtil<Key, Value, Properties>::get_node_size(this);
|
||||
|
||||
if (sum_z != self_z) {
|
||||
throw std::runtime_error(tostr("InternalNode::verify_helper",
|
||||
": inconsistent subtree size",
|
||||
xtag("node", this),
|
||||
xtag("treez[stored]", self_z),
|
||||
xtag("treez[computed]", sum_z)));
|
||||
}
|
||||
}
|
||||
|
||||
/* verify stored glb_key is correct */
|
||||
for (std::size_t i=0, n=this->n_elt_; i < n; ++i) {
|
||||
InternalNodeItemType const & elt = this->lookup_elt(i);
|
||||
|
||||
elt.child()->verify_glb_key(elt.key());
|
||||
}
|
||||
|
||||
/* verify locally stored keys appear in sorted order */
|
||||
for (std::size_t i=1; i < n; ++i) {
|
||||
InternalNodeItemType const & prev = this->lookup_elt(i-1);
|
||||
InternalNodeItemType const & elt = this->lookup_elt(i);
|
||||
|
||||
if (prev.key() < elt.key()) {
|
||||
;
|
||||
} else {
|
||||
throw std::runtime_error(tostr("InternalNode::verify_helper"
|
||||
": expected local keys in strictly increasing order",
|
||||
xtag("i", i),
|
||||
xtag("key(i-1)", prev.key()),
|
||||
xtag("key(i)", elt.key())));
|
||||
}
|
||||
}
|
||||
|
||||
/* verify highest stored key before parent-supplied upper bound */
|
||||
if (with_lub_flag) {
|
||||
if (this->lookup_elt(n-1).key() < lub_key) {
|
||||
;
|
||||
} else {
|
||||
throw std::runtime_error(tostr("InternalNode::verify_helper"
|
||||
": expected highest local key before parent-supplied lub key",
|
||||
xtag("n", n),
|
||||
xtag("key(n-1)", this->lookup_elt(n-1).key()),
|
||||
xtag("lub_key", lub_key)));
|
||||
}
|
||||
}
|
||||
|
||||
return retval;
|
||||
} /*verify_helper*/
|
||||
|
||||
template <typename Key, typename Value, typename Properties>
|
||||
void
|
||||
InternalNode<Key, Value, Properties>::verify_glb_key(Key const & key) const {
|
||||
InternalNodeItemType const & elt = this->lookup_elt(0);
|
||||
|
||||
elt.child()->verify_glb_key(key);
|
||||
} /*verify_glb_key*/
|
||||
|
||||
template <typename Key, typename Value, typename Properties>
|
||||
InternalNode<Key, Value, Properties>::InternalNode(std::size_t branching_factor)
|
||||
: InternalNodeShim<Key, Value, Properties>{NodeType::internal, branching_factor}
|
||||
{
|
||||
/* must invoke ctor explicitly for each .elt_v[i].
|
||||
* compiler doesn't know extent of .elt_v[], since it's a flexible array
|
||||
*/
|
||||
for (std::size_t i = 0; i < branching_factor; ++i) {
|
||||
/* using placement new to force ctor call inside flexible array */
|
||||
new (&(this->lookup_elt(i))) InternalNodeItemType();
|
||||
}
|
||||
} /*ctor*/
|
||||
|
||||
} /*namespace tree*/
|
||||
} /*namespace xo*/
|
||||
|
||||
/* end InternalNode.hpp */
|
||||
355
xo-ordinaltree/include/xo/ordinaltree/bplustree/Iterator.hpp
Normal file
355
xo-ordinaltree/include/xo/ordinaltree/bplustree/Iterator.hpp
Normal file
|
|
@ -0,0 +1,355 @@
|
|||
/* @file Iterator.hpp */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "IteratorUtil.hpp"
|
||||
#include "LeafNode.hpp"
|
||||
#include "xo/indentlog/print/tostr.hpp"
|
||||
|
||||
namespace xo {
|
||||
namespace tree {
|
||||
namespace detail {
|
||||
/* TODO: move to tree/IteratorUtil.hpp */
|
||||
|
||||
/* placeholder - specialize on isConst */
|
||||
template <typename Key,
|
||||
typename Value,
|
||||
typename Properties,
|
||||
bool isConst>
|
||||
struct NodeTypeTraits { using LeafNodeType = void; };
|
||||
|
||||
/* non-const node pointer */
|
||||
template <typename Key,
|
||||
typename Value,
|
||||
typename Properties>
|
||||
struct NodeTypeTraits<Key, Value, Properties, false /*!isConst*/> {
|
||||
using NativeLeafNodeType = LeafNode<Key, Value, Properties>;
|
||||
using LeafNodeType = NativeLeafNodeType;
|
||||
using NativeContentsType = typename LeafNodeType::ContentsType;
|
||||
using LeafNodePtrType = LeafNodeType *;
|
||||
};
|
||||
|
||||
/* const node pointer */
|
||||
template <typename Key,
|
||||
typename Value,
|
||||
typename Properties>
|
||||
struct NodeTypeTraits<Key, Value, Properties, true /*isConst*/> {
|
||||
using NativeLeafNodeType = LeafNode<Key, Value, Properties>;
|
||||
using LeafNodeType = NativeLeafNodeType const;
|
||||
using NativeContentsType = typename LeafNodeType::ContentsType const;
|
||||
using LeafNodePtrType = LeafNodeType const *;
|
||||
};
|
||||
|
||||
/* shared between const and non-const b+ tree iterators
|
||||
*
|
||||
* +------------+
|
||||
* |IteratorBase|
|
||||
* | .dirn |
|
||||
* | .location |
|
||||
* | .leafnode |
|
||||
* | .ix |
|
||||
* +------------+
|
||||
* ^
|
||||
* | isa +-------------+
|
||||
* +-----------|ConstIterator|
|
||||
* | | .operator++ |
|
||||
* | | .operator-- |
|
||||
* | +-------------+
|
||||
* |
|
||||
* | isa +--------+
|
||||
* +-----------|Iterator|
|
||||
* +--------+
|
||||
*/
|
||||
template <typename Key,
|
||||
typename Value,
|
||||
typename Properties,
|
||||
bool isConst>
|
||||
class IteratorBase {
|
||||
public:
|
||||
using Traits = NodeTypeTraits<Key, Value, Properties, isConst>;
|
||||
using BpLeafNodePtrType = typename Traits::LeafNodePtrType;
|
||||
using BpLeafNodeItemType = typename Traits::LeafNodeType::LeafNodeItemType;
|
||||
using NativeContentsType = typename Traits::NativeContentsType;
|
||||
|
||||
protected:
|
||||
IteratorBase() = default;
|
||||
IteratorBase(IteratorDirection dirn, IteratorLocation loc, BpLeafNodePtrType leaf, std::size_t ix)
|
||||
: dirn_{dirn}, location_{loc}, leafnode_{leaf}, ix_{ix} {}
|
||||
IteratorBase(IteratorBase const &) = default;
|
||||
|
||||
static IteratorBase prebegin_aux(BpLeafNodePtrType node) {
|
||||
return IteratorBase(ID_Forward, IL_BeforeBegin, node, 0 /*ix*/);
|
||||
}
|
||||
|
||||
static IteratorBase begin_aux(BpLeafNodePtrType node) {
|
||||
return IteratorBase(ID_Forward,
|
||||
(node ? IL_Regular : IL_AfterEnd),
|
||||
node,
|
||||
0 /*ix*/);
|
||||
}
|
||||
|
||||
static IteratorBase end_aux(BpLeafNodePtrType node) {
|
||||
return IteratorBase(ID_Forward,
|
||||
IL_AfterEnd,
|
||||
node,
|
||||
0 /*ix*/);
|
||||
}
|
||||
|
||||
static IteratorBase rprebegin_aux(BpLeafNodePtrType node) {
|
||||
return IteratorBase(ID_Reverse,
|
||||
IL_AfterEnd,
|
||||
node,
|
||||
0 /*ix*/);
|
||||
}
|
||||
|
||||
static IteratorBase rbegin_aux(BpLeafNodePtrType node) {
|
||||
return IteratorBase(ID_Reverse,
|
||||
(node ? IL_Regular : IL_BeforeBegin),
|
||||
node,
|
||||
(node ? node->n_elt() - 1: 0));
|
||||
}
|
||||
|
||||
static IteratorBase rend_aux(BpLeafNodePtrType node) {
|
||||
return IteratorBase(ID_Reverse,
|
||||
IL_BeforeBegin,
|
||||
node,
|
||||
0 /*ix*/);
|
||||
}
|
||||
|
||||
public:
|
||||
IteratorLocation location() const { return location_; }
|
||||
BpLeafNodePtrType node() const { return leafnode_; }
|
||||
BpLeafNodeItemType const * item_addr() const { return &(leafnode_->lookup_elt(this->ix_)); }
|
||||
|
||||
NativeContentsType const & operator*() const {
|
||||
this->check_regular();
|
||||
return this->leafnode_->lookup_elt(this->ix_).kv_pair();
|
||||
} /*operator**/
|
||||
|
||||
NativeContentsType const * operator->() const {
|
||||
return &(this->operator*());
|
||||
} /*operator->*/
|
||||
|
||||
bool is_sentinel() const { return (this->location_ != IL_Regular); }
|
||||
bool is_dereferenceable() const { return !this->is_sentinel(); }
|
||||
|
||||
operator bool() const { return this->is_deferenceable(); }
|
||||
|
||||
bool operator==(IteratorBase const & x) const {
|
||||
return (this->location_ == x.location_) && (this->leafnode_ == x.leafnode_) && (this->ix_ == x.ix_);
|
||||
}
|
||||
|
||||
bool operator!=(IteratorBase const & x) const {
|
||||
return (this->location_ != x.location_) || (this->leafnode_ != x.leafnode_) || (this->ix_ != x.ix_);
|
||||
}
|
||||
|
||||
void print(std::ostream & os) const {
|
||||
using xo::xtag;
|
||||
|
||||
os << "<bptree-iterator"
|
||||
<< xtag("dirn", dirn_)
|
||||
<< xtag("loc", location_)
|
||||
<< xtag("leaf", leafnode_)
|
||||
<< xtag("ix", ix_)
|
||||
<< ">";
|
||||
} /*print*/
|
||||
|
||||
/* pre-increment */
|
||||
IteratorBase & operator++() {
|
||||
return ((this->dirn_ == ID_Forward)
|
||||
? this->next_step()
|
||||
: this->prev_step());
|
||||
} /*operator++*/
|
||||
|
||||
/* pre-decrement */
|
||||
IteratorBase & operator--() {
|
||||
return ((this->dirn_ == ID_Forward)
|
||||
? this->prev_step()
|
||||
: this->next_step());
|
||||
} /*operator--*/
|
||||
|
||||
private:
|
||||
IteratorBase & next_step() {
|
||||
switch(this->location_) {
|
||||
case IL_BeforeBegin:
|
||||
/* .leafnode is leftmost node in tree */
|
||||
this->location_ = IL_Regular;
|
||||
break;
|
||||
case IL_Regular:
|
||||
{
|
||||
/* #of elts in node, not #of elts in tree! */
|
||||
std::size_t n_elt = this->leafnode_->n_elt();
|
||||
|
||||
if (this->ix_ + 1 < n_elt) {
|
||||
++(this->ix_);
|
||||
} else if (this->leafnode_->next_leafnode()) {
|
||||
this->leafnode_ = this->leafnode_->next_leafnode();
|
||||
this->ix_ = 0;
|
||||
} else {
|
||||
/* preserve .leafnode:
|
||||
* (a) for == comparison w/ .end() iterator
|
||||
* (b) so we can iterate backwards from end position
|
||||
*/
|
||||
//this->leafnode_ = this->leafnode_->next_leafnode();
|
||||
this->location_ = IL_AfterEnd;
|
||||
this->ix_ = 0;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case IL_AfterEnd:
|
||||
break;
|
||||
}
|
||||
|
||||
return *this;
|
||||
} /*next_step*/
|
||||
|
||||
IteratorBase & prev_step() {
|
||||
switch(this->location_) {
|
||||
case IL_BeforeBegin:
|
||||
break;
|
||||
case IL_Regular:
|
||||
if (this->ix_ > 0) {
|
||||
--(this->ix_);
|
||||
} else if (this->leafnode_->prev_leafnode()) {
|
||||
this->leafnode_ = this->leafnode_->prev_leafnode();
|
||||
this->ix_ = this->leafnode_->n_elt() - 1;
|
||||
} else /* .ix == 0 && .leafnode.prev_leafnode == nullptr */ {
|
||||
/* preserve .leafnode:
|
||||
* (a) for == comparison w/ .prebegin() iterator
|
||||
* (b) so iterator is reversible; can iterate forwards from prebegin position
|
||||
*/
|
||||
this->location_ = IL_BeforeBegin;
|
||||
}
|
||||
break;
|
||||
case IL_AfterEnd:
|
||||
/* .leafnode is rightmost node in tree */
|
||||
this->location_ = IL_Regular;
|
||||
this->ix_ = this->leafnode_->n_elt() - 1;
|
||||
break;
|
||||
}
|
||||
|
||||
return *this;
|
||||
} /*prev_step*/
|
||||
|
||||
private:
|
||||
void check_regular() const {
|
||||
using xo::tostr;
|
||||
using xo::xtag;
|
||||
|
||||
if (this->location_ != IL_Regular) {
|
||||
throw std::runtime_error(tostr("bplustree iterator: cannot deref iterator"
|
||||
" in sentinel state",
|
||||
xtag("loc", this->location_)));
|
||||
}
|
||||
} /*check_regular*/
|
||||
|
||||
private:
|
||||
/* ID_Forward forward iterator
|
||||
* ID_Reverse reverse iterator
|
||||
*/
|
||||
IteratorDirection dirn_ = ID_Forward;
|
||||
/* IL_BeforeBegin | IL_Regular | IL_AfterEnd
|
||||
*
|
||||
* operator++ operator++
|
||||
* IL_BeforeBegin --------------> IL_Regular --------------> IL_AfterEnd
|
||||
* /-> -\
|
||||
* | |
|
||||
* \----------------/
|
||||
* operator++
|
||||
*
|
||||
* operator-- operator--
|
||||
* IL_BeforeBegin <------------- IL_Regular <-------------- IL_AfterEnd
|
||||
* /-- <-\
|
||||
* | |
|
||||
* \-----------------/
|
||||
* operator--
|
||||
*
|
||||
*
|
||||
*/
|
||||
IteratorLocation location_ = IL_AfterEnd;
|
||||
/* .location .leafnode
|
||||
* IL_BeforeBegin BplusTree.leafnode_begin (leftmost leaf node)
|
||||
* IL_Regular any leaf node reachable from BplusTree.leafnode_begin
|
||||
* (or equivalently from BplusTree.leafnode_end)
|
||||
* IL_AfterEnd BplusTree.leafnode_end (rightmost leaf node)
|
||||
*/
|
||||
BpLeafNodePtrType leafnode_ = nullptr;
|
||||
/* index position within .leafnode;
|
||||
* 0 when .location is IL_BeforeBegin | IL_AfterEnd
|
||||
*/
|
||||
std::size_t ix_ = 0;
|
||||
}; /*IteratorBase*/
|
||||
|
||||
template <typename Key,
|
||||
typename Value,
|
||||
typename Properties>
|
||||
class ConstIterator : public IteratorBase<Key, Value, Properties, true /*isConst*/> {
|
||||
public:
|
||||
using iterator_concept = std::bidirectional_iterator_tag;
|
||||
|
||||
using BpIteratorBase = IteratorBase<Key, Value, Properties, true /*isConst*/>;
|
||||
using BpLeafNodePtrType = typename BpIteratorBase::BpLeafNodePtrType;
|
||||
|
||||
public:
|
||||
ConstIterator() = default;
|
||||
ConstIterator(IteratorDirection dirn, IteratorLocation loc, BpLeafNodePtrType leaf, std::size_t ix)
|
||||
: IteratorBase<Key, Value, Properties, true /*isConst*/>(dirn, loc, leaf, ix) {}
|
||||
ConstIterator(ConstIterator const & x) = default;
|
||||
ConstIterator(BpIteratorBase const & x) : BpIteratorBase(x) {}
|
||||
ConstIterator(BpIteratorBase && x) : BpIteratorBase{std::move(x)} {}
|
||||
|
||||
static ConstIterator prebegin_aux(BpLeafNodePtrType leaf) { return BpIteratorBase::prebegin_aux(leaf); }
|
||||
static ConstIterator begin_aux(BpLeafNodePtrType leaf) { return BpIteratorBase::begin_aux(leaf); }
|
||||
static ConstIterator end_aux(BpLeafNodePtrType leaf) { return BpIteratorBase::end_aux(leaf); }
|
||||
|
||||
static ConstIterator rprebegin_aux(BpLeafNodePtrType leaf) { return BpIteratorBase::rprebegin_aux(leaf); }
|
||||
static ConstIterator rbegin_aux(BpLeafNodePtrType leaf) { return BpIteratorBase::rbegin_aux(leaf); }
|
||||
static ConstIterator rend_aux(BpLeafNodePtrType leaf) { return BpIteratorBase::rend_aux(leaf); }
|
||||
|
||||
/* pre-increment */
|
||||
ConstIterator & operator++() {
|
||||
BpIteratorBase::operator++();
|
||||
return *this;
|
||||
} /*operator++*/
|
||||
|
||||
/* post-increment */
|
||||
ConstIterator operator++(int) {
|
||||
ConstIterator retval = *this;
|
||||
|
||||
++(*this);
|
||||
|
||||
return retval;
|
||||
} /*operator++*/
|
||||
|
||||
/* pre-decrement */
|
||||
ConstIterator & operator--() {
|
||||
BpIteratorBase::operator--();
|
||||
return *this;
|
||||
} /*operator--*/
|
||||
|
||||
/* post-decrement */
|
||||
ConstIterator operator--(int) {
|
||||
ConstIterator retval = *this;
|
||||
|
||||
--(*this);
|
||||
|
||||
return retval;
|
||||
} /*operator--*/
|
||||
}; /*ConstIterator*/
|
||||
} /*namespace detail*/
|
||||
|
||||
template <typename Key,
|
||||
typename Value,
|
||||
typename Properties,
|
||||
bool IsConst>
|
||||
inline std::ostream &
|
||||
operator<<(std::ostream & os,
|
||||
detail::IteratorBase<Key, Value, Properties, IsConst> const & iter)
|
||||
{
|
||||
iter.print(os);
|
||||
return os;
|
||||
} /*operator<<*/
|
||||
} /*namespace tree*/
|
||||
} /*namespace xo*/
|
||||
|
||||
/* end Iterator.hpp */
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
/* @file IteratorUtil.hpp */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <ostream>
|
||||
|
||||
namespace xo {
|
||||
namespace tree {
|
||||
namespace detail {
|
||||
|
||||
enum IteratorDirection {
|
||||
/* ID_Forward. forward iterator
|
||||
* ID_Reverse. reverse iterator
|
||||
*/
|
||||
ID_Forward,
|
||||
ID_Reverse
|
||||
}; /*IteratorDirection*/
|
||||
|
||||
/* specify iterator location relative to a particular b+ tree node */
|
||||
enum IteratorLocation {
|
||||
/*
|
||||
* IL_BeforeBegin. if non-empty tree, Iterator.node is the first node
|
||||
* in the tree (the one with smallest key),
|
||||
* and iterator refers to the location
|
||||
* "one before" that first node.
|
||||
* IL_Regular. iterator refers to member of the tree
|
||||
* given by Iterator.node
|
||||
* IL_AfterEnd. if non-empty tree, Iterator.node is the last node
|
||||
* in the tree (the one with largest key),
|
||||
* and iterator refers the the location
|
||||
* "one after" that last node.
|
||||
*/
|
||||
IL_BeforeBegin,
|
||||
IL_Regular,
|
||||
IL_AfterEnd
|
||||
}; /*IteratorLocation*/
|
||||
|
||||
static inline char const * iterator_location_descr(IteratorLocation x) {
|
||||
switch(x) {
|
||||
case IL_BeforeBegin: return "before-begin";
|
||||
case IL_Regular: return "regular";
|
||||
case IL_AfterEnd: return "after-end";
|
||||
default: return "???";
|
||||
}
|
||||
} /*iteerator_location_descr*/
|
||||
|
||||
inline std::ostream &
|
||||
operator<<(std::ostream & os, IteratorLocation x) {
|
||||
os << iterator_location_descr(x);
|
||||
return os;
|
||||
} /*operator<<*/
|
||||
} /*namespace detail*/
|
||||
} /*namespace tree*/
|
||||
} /*namespace xo*/
|
||||
|
||||
/* end IteratorUtil.hpp */
|
||||
684
xo-ordinaltree/include/xo/ordinaltree/bplustree/LeafNode.hpp
Normal file
684
xo-ordinaltree/include/xo/ordinaltree/bplustree/LeafNode.hpp
Normal file
|
|
@ -0,0 +1,684 @@
|
|||
/* @file LeafNode.hpp */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "GenericNode.hpp"
|
||||
#include "xo/indentlog/scope.hpp"
|
||||
#include <cassert>
|
||||
|
||||
namespace xo {
|
||||
namespace tree {
|
||||
|
||||
// ----- LeafNodeItem -----
|
||||
|
||||
template <typename Key, typename Value, typename Properties>
|
||||
using LeafNodeItem = NodeItem<NodeType::leaf, Key, Value, Properties>;
|
||||
|
||||
/* - define for symmetry with NodeItem<Key, Properties>
|
||||
* - LeafNodeItem doesn't contain a child pointer;
|
||||
* it belongs inside a leaf mode, which by definition doesn't have children
|
||||
*/
|
||||
template <typename Key, typename Value, typename Properties>
|
||||
struct NodeItem<NodeType::leaf, Key, Value, Properties> {
|
||||
public:
|
||||
using ContentsType = std::pair<Key, Value>;
|
||||
|
||||
public:
|
||||
NodeItem() = default;
|
||||
NodeItem(std::pair<Key, Value> kv) : kv_pair_{std::move(kv)} {}
|
||||
|
||||
std::pair<Key, Value> const & kv_pair() const { return kv_pair_; }
|
||||
|
||||
Key const & key () const { return kv_pair_.first; }
|
||||
Value const & value() const { return kv_pair_.second; }
|
||||
|
||||
void assign_value(Value x) { kv_pair_.second = std::move(x); }
|
||||
|
||||
private:
|
||||
/* key+value pair */
|
||||
std::pair<Key, Value> kv_pair_;
|
||||
}; /*NodeItem*/
|
||||
|
||||
/* struct with same size as LeafNodeItem<Key, Value, Properties>, but POD + with no ctor/dtor */
|
||||
template <typename Key, typename Value, typename Properties>
|
||||
using LeafNodeItemPlaceholder = NodeItemPlaceholder<NodeType::leaf, Key, Value, Properties>;
|
||||
|
||||
template <typename Key, typename Value, typename Properties, tags::ordinal_tag OrdinalTag = Properties::ordinal_tag_value()>
|
||||
struct LeafNodeShim : public GenericNode<Key, Value, Properties>
|
||||
{
|
||||
LeafNodeShim(NodeType ntype, std::size_t branching_factor) : GenericNode<Key, Value, Properties>(ntype, branching_factor) {}
|
||||
|
||||
/* ordinal_enabled: LeafNode will provide .size(): inherits+overrides GenericNodeBase.size() */
|
||||
};
|
||||
|
||||
template <typename Key, typename Value, typename Properties>
|
||||
struct LeafNodeShim<Key, Value, Properties, tags::ordinal_disabled> : public GenericNode<Key, Value, Properties>
|
||||
{
|
||||
LeafNodeShim(NodeType ntype, std::size_t branching_factor) : GenericNode<Key, Value, Properties>(ntype, branching_factor) {}
|
||||
|
||||
/* ordinal_disabled: LeafNode provides LeafNode::size(), but not used */
|
||||
|
||||
virtual std::size_t size() const = 0;
|
||||
};
|
||||
|
||||
// ----- LeafNode -----
|
||||
|
||||
/* require:
|
||||
* - Properties.branching_factor()
|
||||
*/
|
||||
template <typename Key, typename Value, typename Properties>
|
||||
struct LeafNode : public LeafNodeShim<Key, Value, Properties> {
|
||||
public:
|
||||
using GenericNodeType = GenericNode<Key, Value, Properties>;
|
||||
using LeafNodeType = LeafNode<Key, Value, Properties>;
|
||||
using LeafNodeItemType = LeafNodeItem<Key, Value, Properties>;
|
||||
using LeafNodeItemPlaceholderType = LeafNodeItemPlaceholder<Key, Value, Properties>;
|
||||
using InternalNodeType = InternalNode<Key, Value, Properties>;
|
||||
|
||||
using ContentsType = typename LeafNodeItemType::ContentsType;
|
||||
|
||||
public:
|
||||
virtual ~LeafNode();
|
||||
|
||||
/* node size in bytes (increases with branching factor) */
|
||||
static std::size_t node_sizeof(std::size_t branching_factor);
|
||||
|
||||
/* named ctor idiom. enforce heap allocation + unique_ptr wrapper */
|
||||
static std::unique_ptr<LeafNode> make(std::pair<Key const, Value> kv_pair,
|
||||
Properties const & properties);
|
||||
|
||||
/* create+return new leaf node that contains all the items in *src from position [lo_ix, hi_ix),
|
||||
* after this operation size of *src is reduced by (hi_ix - lo_ix)
|
||||
*/
|
||||
static std::unique_ptr<LeafNode> annex(std::size_t lo_ix,
|
||||
std::size_t hi_ix,
|
||||
LeafNode * src);
|
||||
|
||||
LeafNode * prev_leafnode() const { return prev_leafnode_; }
|
||||
LeafNode * next_leafnode() const { return next_leafnode_; }
|
||||
|
||||
/* .first: true if key in tree already
|
||||
* .second: index position of (strict) least upper bound in .elt_v[]
|
||||
* if .n_elt, key has no upper bound in this node
|
||||
*/
|
||||
std::pair<bool, std::size_t> find_lub_ix(Key const & key) const;
|
||||
|
||||
LeafNodeItemType & lookup_elt(std::size_t i) { return *(reinterpret_cast<LeafNodeItemType *>(&(this->elt_v_[i]))); }
|
||||
|
||||
LeafNodeItemType const & lookup_elt(std::size_t i) const { return *(reinterpret_cast<LeafNodeItemType const *>(&(this->elt_v_[i]))); }
|
||||
|
||||
void assign_leaf_value(std::size_t elt_ix, Value value) {
|
||||
assert(elt_ix < this->n_elt_);
|
||||
|
||||
this->lookup_elt(elt_ix).assign_value(std::move(value));
|
||||
} /*assign_leaf_value*/
|
||||
|
||||
/* assign precdeing leaf node (= LH sibling if share same parent) */
|
||||
void assign_prev_leafnode(LeafNode * x) { prev_leafnode_ = x; }
|
||||
void assign_next_leafnode(LeafNode * x) { next_leafnode_ = x; }
|
||||
|
||||
/* insert new leaf at position ix, associating key -> value
|
||||
* (shuffle existing elements at ix, ix+1.. 1 position to the right)
|
||||
*/
|
||||
void insert_leaf_item(std::size_t ix,
|
||||
std::pair<Key const, Value> const & kv_pair,
|
||||
bool debug_flag);
|
||||
|
||||
/* remove key,value pair at position ix */
|
||||
void remove_leaf(std::size_t ix, bool debug_flag);
|
||||
|
||||
/* append n items from left-hand sibling, as new left-most elements
|
||||
* require: combined #of items must be at most b = branching factor
|
||||
*/
|
||||
void prepend_from_lh_sibling(LeafNode * lh, std::size_t n, bool debug_flag);
|
||||
|
||||
/* apepnd n items from right-hand sibling, as new right-most elements
|
||||
* require: combined #of items must be at most b = branching factor
|
||||
*/
|
||||
void append_from_rh_sibling(std::size_t n, LeafNode * rh);
|
||||
|
||||
void append_rh_sibling(LeafNode * rh) { this->append_from_rh_sibling(rh->n_elt(), rh); }
|
||||
|
||||
/* returns new leaf with lower half of original element vector;
|
||||
* original updated to retain upper half
|
||||
*/
|
||||
std::unique_ptr<LeafNode> split_leaf_lower();
|
||||
|
||||
/* returns new leaf with upper half of original element vector;
|
||||
* original updated to retain lower half
|
||||
*/
|
||||
std::unique_ptr<LeafNode> split_leaf_upper();
|
||||
|
||||
/* memory for LeafNode instances is always created using new[],
|
||||
* so required to use delete[] to deallocate
|
||||
*/
|
||||
void operator delete (void * mem) noexcept { ::operator delete[](mem); }
|
||||
|
||||
// ----- Inherited from GenericNode -----
|
||||
|
||||
virtual std::size_t size() const override { return this->n_elt(); }
|
||||
|
||||
virtual Key const & glb_key() const override { return this->lookup_elt(0).key(); }
|
||||
|
||||
virtual std::size_t verify_helper(InternalNodeType const * parent,
|
||||
bool with_lub_flag,
|
||||
Key const & lub_key,
|
||||
LeafNodeType const * lh_leaf,
|
||||
LeafNodeType const * rh_leaf) const override;
|
||||
virtual void verify_glb_key(Key const & key) const override;
|
||||
virtual FindNodeResult<LeafNodeType> find_min_leaf_node() override;
|
||||
virtual FindNodeResult<LeafNodeType> find_max_leaf_node() override;
|
||||
|
||||
virtual void notify_remove() override;
|
||||
|
||||
private:
|
||||
explicit LeafNode(std::size_t branching_factor);
|
||||
|
||||
LeafNode(std::pair<Key const, Value> const & kv_pair,
|
||||
std::size_t branching_factor);
|
||||
|
||||
void assign_siblings(LeafNode * prev, LeafNode * next);
|
||||
|
||||
private:
|
||||
/* previous LeafNode in key order, immediately before (all the keys in) this node.
|
||||
* use to streamline inorder traversal.
|
||||
*/
|
||||
LeafNode * prev_leafnode_ = nullptr;
|
||||
/* next LeafNode in key order, immediately after (all the keys in) this node.
|
||||
* streamline inorder traversal.
|
||||
*/
|
||||
LeafNode * next_leafnode_ = nullptr;
|
||||
/* flexible array; actual capacity will be Properties.branching_factor();
|
||||
* but only members [0 .. n_elt-1] are defined.
|
||||
*
|
||||
* actual type of .elt_v[i] is LeafNodeItem<Key, Value, Properties>;
|
||||
* need to use POD LeafNodeItemPlaceholder<Key, Value, Properties> to satisfy flexible-array rules
|
||||
*/
|
||||
LeafNodeItemPlaceholderType elt_v_[];
|
||||
}; /*LeafNode*/
|
||||
|
||||
template <typename Key, typename Value, typename Properties>
|
||||
LeafNode<Key, Value, Properties>::~LeafNode() {
|
||||
/* since we're using flexible array for .elt_v[], need to manually run destructors */
|
||||
for (std::size_t i=0, n=this->branching_factor_; i<n; ++i) {
|
||||
this->lookup_elt(i).~LeafNodeItemType();
|
||||
}
|
||||
|
||||
/* hygiene */
|
||||
this->n_elt_ = 0;
|
||||
this->branching_factor_ = 0;
|
||||
} /*dtor*/
|
||||
|
||||
template <typename Key, typename Value, typename Properties>
|
||||
std::size_t
|
||||
LeafNode<Key, Value, Properties>::node_sizeof(std::size_t branching_factor) {
|
||||
/* since we're using flexible array for .elt_v[], need to manually account for it's allocated size */
|
||||
|
||||
return (sizeof(LeafNode)
|
||||
+ (branching_factor
|
||||
* sizeof(LeafNodeItem<Key, Value, Properties>)));
|
||||
} /*node_sizeof*/
|
||||
|
||||
template <typename Key, typename Value, typename Properties>
|
||||
std::unique_ptr<LeafNode<Key, Value, Properties>>
|
||||
LeafNode<Key, Value, Properties>::make(std::pair<Key const, Value> kv_pair,
|
||||
Properties const & properties)
|
||||
{
|
||||
using xo::scope;
|
||||
using xo::xtag;
|
||||
|
||||
std::size_t mem_z = node_sizeof(properties.branching_factor());
|
||||
/* storage for LeafNode, including storage cost for flexible array LeafNode.elt_v[] */
|
||||
std::uint8_t * mem = new std::uint8_t[mem_z];
|
||||
|
||||
#ifdef NOT_IN_USE
|
||||
scope x("LeafNode.make");
|
||||
x.log(xtag("sizeof(LeafNode)", sizeof(LeafNode)),
|
||||
xtag("bf", properties.branching_factor()),
|
||||
xtag("mem_z", mem_z),
|
||||
xtag("mem", (void *)mem));
|
||||
#endif
|
||||
|
||||
return std::unique_ptr<LeafNode>(new (mem) LeafNode(std::move(kv_pair),
|
||||
properties.branching_factor()));
|
||||
} /*make*/
|
||||
|
||||
template <typename Key, typename Value, typename Properties>
|
||||
std::unique_ptr<LeafNode<Key, Value, Properties>>
|
||||
LeafNode<Key, Value, Properties>::annex(std::size_t lo_ix,
|
||||
std::size_t hi_ix,
|
||||
LeafNode * src)
|
||||
{
|
||||
using xo::scope;
|
||||
using xo::xtag;
|
||||
|
||||
std::size_t branching_factor = src->branching_factor();
|
||||
|
||||
assert(hi_ix >= lo_ix);
|
||||
assert(hi_ix - lo_ix <= branching_factor);
|
||||
|
||||
std::size_t mem_z = node_sizeof(branching_factor);
|
||||
std::uint8_t * mem = new std::uint8_t[mem_z];
|
||||
|
||||
#ifdef NOT_IN_USE
|
||||
scope x("LeafNode.annex");
|
||||
x.log(xtag("sizeof(LeafNode)", sizeof(LeafNode)),
|
||||
xtag("bf", branching_factor),
|
||||
xtag("mem_z", mem_z),
|
||||
xtag("mem", (void *)mem));
|
||||
#endif
|
||||
|
||||
std::unique_ptr<LeafNode> new_node(new (mem) LeafNode(branching_factor));
|
||||
|
||||
std::size_t old_n = src->n_elt();
|
||||
|
||||
new_node->n_elt_ = hi_ix - lo_ix;
|
||||
|
||||
std::size_t n_annex = hi_ix - lo_ix;
|
||||
|
||||
/* annexing from *src into new_node */
|
||||
for (std::size_t i = 0; i < n_annex; ++i) {
|
||||
LeafNodeItemType & new_slot = new_node->lookup_elt(i);
|
||||
|
||||
new_slot = std::move(src->lookup_elt(lo_ix + i));
|
||||
}
|
||||
|
||||
/* shuffle over any remaining items in *src starting from hi_ix */
|
||||
for (std::size_t i = lo_ix; i + n_annex < old_n; ++i) {
|
||||
LeafNodeItemType & slot = src->lookup_elt(i);
|
||||
|
||||
slot = std::move(src->lookup_elt(i + n_annex));
|
||||
}
|
||||
|
||||
src->n_elt_ = old_n - n_annex;
|
||||
|
||||
if (lo_ix == 0) {
|
||||
/* new node builds by taking leftmost elements from src
|
||||
* -> new node becomes src's predecessor
|
||||
*/
|
||||
new_node->assign_siblings(src->prev_leafnode(), src);
|
||||
} else {
|
||||
/* new node builds by taking rightmost elements from src
|
||||
* -> new node becomes src's successor
|
||||
*/
|
||||
new_node->assign_siblings(src, src->next_leafnode());
|
||||
}
|
||||
|
||||
return new_node;
|
||||
} /*annex*/
|
||||
|
||||
template <typename Key, typename Value, typename Properties>
|
||||
std::pair<bool, std::size_t>
|
||||
LeafNode<Key, Value, Properties>::find_lub_ix(Key const & key) const {
|
||||
if (key < this->lookup_elt(0).key())
|
||||
return std::make_pair(false, 0);
|
||||
|
||||
/* promise: return value >= 0 */
|
||||
|
||||
/* .elt_v[0 .. n_elt-1] are maintained in sorted key order */
|
||||
std::size_t lo = 0;
|
||||
std::size_t hi = this->n_elt_;
|
||||
|
||||
while (lo + 1 < hi) {
|
||||
/* desired child item will be in range [lo, hi) */
|
||||
|
||||
std::size_t mid = lo + (hi - lo) / 2;
|
||||
|
||||
if (key < this->lookup_elt(mid).key())
|
||||
hi = mid;
|
||||
else
|
||||
lo = mid;
|
||||
}
|
||||
|
||||
/* invariant:
|
||||
* - lo is a valid index: elt_v[lo].kv_pair reflects outcome of most recent call to BplusTree.insert()
|
||||
* - .elt_v[lo].key <= key
|
||||
* - if hi<.n_elt, then key < .elt_v[hi].key
|
||||
*/
|
||||
bool presence_flag = (key == this->lookup_elt(lo).key());
|
||||
|
||||
return std::make_pair(presence_flag, hi);
|
||||
} /*find_lub_ix*/
|
||||
|
||||
template <typename Key, typename Value, typename Properties>
|
||||
void
|
||||
LeafNode<Key, Value, Properties>::insert_leaf_item(std::size_t ix,
|
||||
std::pair<Key const, Value> const & kv_pair,
|
||||
bool debug_flag) {
|
||||
using xo::scope;
|
||||
using xo::xtag;
|
||||
|
||||
scope log(XO_DEBUG(debug_flag),
|
||||
xtag("self", this),
|
||||
xtag("n_elt", this->n_elt()),
|
||||
xtag("bf", this->branching_factor()),
|
||||
xtag("ix", ix),
|
||||
xtag("key", kv_pair.first),
|
||||
xtag("value", kv_pair.second));
|
||||
|
||||
if (this->n_elt_ >= this->branching_factor()) {
|
||||
assert(false);
|
||||
throw std::runtime_error(tostr("LeafNode::insert_leaf: leaf already full",
|
||||
xtag("leaf.n_elt", this->n_elt()),
|
||||
xtag("branching_factor", this->branching_factor())));
|
||||
}
|
||||
|
||||
std::size_t pos_ix = this->n_elt_;
|
||||
|
||||
while (pos_ix > ix) {
|
||||
//scope x1("loop");
|
||||
//x1.log(xtag("pos_ix", pos_ix));
|
||||
|
||||
this->lookup_elt(pos_ix) = std::move(this->lookup_elt(pos_ix - 1));
|
||||
--pos_ix;
|
||||
}
|
||||
|
||||
++(this->n_elt_);
|
||||
this->lookup_elt(ix) = LeafNodeItemType(kv_pair);
|
||||
|
||||
log.end_scope();
|
||||
} /*insert_leaf*/
|
||||
|
||||
template <typename Key, typename Value, typename Properties>
|
||||
void
|
||||
LeafNode<Key, Value, Properties>::remove_leaf(std::size_t ix, bool debug_flag) {
|
||||
using xo::scope;
|
||||
using xo::xtag;
|
||||
|
||||
scope log(XO_DEBUG(debug_flag),
|
||||
xtag("self", this),
|
||||
xtag("n_elt", this->n_elt()),
|
||||
xtag("bf", this->branching_factor()),
|
||||
xtag("ix", ix));
|
||||
|
||||
if (this->n_elt_ == 0) {
|
||||
throw std::runtime_error(tostr("LeafNode::remove_leaf: leaf already empty",
|
||||
xtag("leaf.n_elt", this->n_elt()),
|
||||
xtag("branching_factor", this->branching_factor())));
|
||||
}
|
||||
|
||||
/* TODO: removal action for position pos_ix (maintain reductions) */
|
||||
|
||||
std::size_t pos_ix = ix;
|
||||
std::size_t end_ix = this->n_elt_ - 1;
|
||||
|
||||
while (pos_ix < end_ix) {
|
||||
//scope x1("loop");
|
||||
//x1.log(xtag("pos_ix", pos_ix));
|
||||
|
||||
this->lookup_elt(pos_ix) = std::move(this->lookup_elt(pos_ix + 1));
|
||||
++pos_ix;
|
||||
}
|
||||
|
||||
--(this->n_elt_);
|
||||
} /*remove_leaf*/
|
||||
|
||||
template <typename Key, typename Value, typename Properties>
|
||||
void
|
||||
LeafNode<Key, Value, Properties>::prepend_from_lh_sibling(LeafNode<Key, Value, Properties> * lh, std::size_t n, bool debug_flag) {
|
||||
using xo::scope;
|
||||
using xo::xtag;
|
||||
|
||||
scope log(XO_DEBUG(debug_flag),
|
||||
xtag("n", n));
|
||||
|
||||
if (this->n_elt() + n > this->branching_factor()) {
|
||||
assert(false);
|
||||
throw std::runtime_error(tostr("LeafNode.prepend_from_lh_sibling: expected combined #elt <= bf",
|
||||
xtag("self.n_elt", this->n_elt()),
|
||||
xtag("n", n),
|
||||
xtag("bf", this->branching_factor())));
|
||||
}
|
||||
|
||||
std::size_t n_lh = lh->n_elt();
|
||||
std::size_t n_rh = this->n_elt();
|
||||
|
||||
/* move elts in *this to the right n steps */
|
||||
for (std::size_t ixp1 = this->n_elt(); ixp1 > 0; --ixp1) {
|
||||
std::size_t ix = ixp1 - 1;
|
||||
this->lookup_elt(ix + n) = std::move(this->lookup_elt(ix));
|
||||
}
|
||||
|
||||
/* xfer n elts from upper end of lh, to lower end of *this */
|
||||
for (std::size_t ix = 0; ix < n; ++ix) {
|
||||
this->lookup_elt(ix) = lh->lookup_elt(n_lh - n + ix);
|
||||
}
|
||||
|
||||
this->n_elt_ += n;
|
||||
lh->n_elt_ -= n;
|
||||
|
||||
/* note: since we didn't create/destroy any LeafNodes,
|
||||
* .prev_leafnode / .next_leafnode pointers are unchanged
|
||||
*/
|
||||
|
||||
log.end_scope();
|
||||
} /*prepend_from_lh_sibling*/
|
||||
|
||||
template <typename Key, typename Value, typename Properties>
|
||||
void
|
||||
LeafNode<Key, Value, Properties>::append_from_rh_sibling(std::size_t n, LeafNode<Key, Value, Properties> * rh) {
|
||||
using xo::xtag;
|
||||
|
||||
if (this->n_elt() + n > this->branching_factor()) {
|
||||
assert(false);
|
||||
throw std::runtime_error(tostr("LeafNode.append_from_rh_sibling: expected combined #elt <= bf",
|
||||
xtag("self.n_elt", this->n_elt()),
|
||||
xtag("n", n),
|
||||
xtag("bf", this->branching_factor())));
|
||||
}
|
||||
|
||||
std::size_t n_lh = this->n_elt();
|
||||
|
||||
for (std::size_t ix = 0; ix < n; ++ix) {
|
||||
this->lookup_elt(n_lh + ix) = std::move(rh->lookup_elt(ix));
|
||||
/* note: leaf items are key,value pairs;
|
||||
* no parent pointers to fixup (cf InternalNode.append_from_rh_sibling)
|
||||
*/
|
||||
}
|
||||
|
||||
this->n_elt_ += n;
|
||||
|
||||
/* shuffle remaining members of rh sibling n items to the left */
|
||||
for (std::size_t ix = 0; ix < rh->n_elt() - n; ++ix) {
|
||||
rh->lookup_elt(ix) = std::move(rh->lookup_elt(ix + n));
|
||||
}
|
||||
|
||||
rh->n_elt_ -= n;
|
||||
|
||||
/* note: since we didn't create/destroy any LeafNodes,
|
||||
* .prev_leafnode / .next_leafnode pointers are unchanged
|
||||
*/
|
||||
|
||||
} /*append_from_rh_sibling*/
|
||||
|
||||
template <typename Key, typename Value, typename Properties>
|
||||
std::unique_ptr<LeafNode<Key, Value, Properties>>
|
||||
LeafNode<Key, Value, Properties>::split_leaf_lower() {
|
||||
std::size_t n_elt = this->n_elt_;
|
||||
std::size_t mid_ix = n_elt / 2;
|
||||
|
||||
return LeafNode::annex(0, mid_ix, this);
|
||||
} /*split_leaf_lower*/
|
||||
|
||||
template <typename Key, typename Value, typename Properties>
|
||||
std::unique_ptr<LeafNode<Key, Value, Properties>>
|
||||
LeafNode<Key, Value, Properties>::split_leaf_upper() {
|
||||
std::size_t n_elt = this->n_elt_;
|
||||
std::size_t mid_ix = n_elt / 2;
|
||||
|
||||
return LeafNode<Key, Value, Properties>::annex(mid_ix, n_elt, this);
|
||||
} /*split_leaf_upper*/
|
||||
|
||||
template <typename Key, typename Value, typename Properties>
|
||||
std::size_t
|
||||
LeafNode<Key, Value, Properties>::verify_helper(InternalNodeType const * parent,
|
||||
bool with_lub_flag,
|
||||
Key const & lub_key,
|
||||
LeafNodeType const * lh_leaf,
|
||||
LeafNodeType const * rh_leaf) const {
|
||||
using xo::xtag;
|
||||
|
||||
/* verify immediate parent pointer is correct */
|
||||
if (this->parent() != parent) {
|
||||
throw std::runtime_error(tostr("LeafNode::verify_helper"
|
||||
": expected parent pointer to refer to actual parent",
|
||||
xtag("stored_parent", this->parent()),
|
||||
xtag("actual_parent", parent)));
|
||||
}
|
||||
|
||||
/* verify locally stored keys appear in sorted order */
|
||||
std::size_t n = this->n_elt_;
|
||||
for (std::size_t i=1; i < n; ++i) {
|
||||
LeafNodeItemType const & prev = this->lookup_elt(i-1);
|
||||
LeafNodeItemType const & elt = this->lookup_elt(i);
|
||||
|
||||
if (prev.key() < elt.key()) {
|
||||
;
|
||||
} else {
|
||||
throw std::runtime_error(tostr("LeafNode::verify_helper"
|
||||
": expected local keys in strictly increasing order",
|
||||
xtag("i", i),
|
||||
xtag("key(i-1)", prev.key()),
|
||||
xtag("key(i)", elt.key())));
|
||||
}
|
||||
}
|
||||
|
||||
if (with_lub_flag) {
|
||||
if (this->lookup_elt(n-1).key() < lub_key) {
|
||||
;
|
||||
} else {
|
||||
throw std::runtime_error(tostr("LeafNode::verify_helper"
|
||||
": expected last local key before parent-supplied lub key",
|
||||
xtag("n", n),
|
||||
xtag("key(n-1)", this->lookup_elt(n-1).key()),
|
||||
xtag("lub_key", lub_key)));
|
||||
}
|
||||
}
|
||||
|
||||
/* verify next/prev leafnode pointers are consistent */
|
||||
if ((lh_leaf && (lh_leaf->next_leafnode() != this))
|
||||
|| (this->prev_leafnode() != lh_leaf))
|
||||
{
|
||||
throw std::runtime_error(tostr("LeafNode::verify_helper"
|
||||
": inconsistent prev/next leaf pointers",
|
||||
xtag("parent", parent),
|
||||
xtag("lh_leaf", lh_leaf),
|
||||
xtag("lh_leaf.next", lh_leaf ? lh_leaf->next_leafnode() : nullptr),
|
||||
xtag("self", this),
|
||||
xtag("self.prev", this->prev_leafnode())));
|
||||
}
|
||||
|
||||
if ((this->next_leafnode() != rh_leaf)
|
||||
|| (rh_leaf && (rh_leaf->prev_leafnode() != this)))
|
||||
{
|
||||
throw std::runtime_error(tostr("LeafNode::verify_helper"
|
||||
": inconsistent prev/next leaf pointers",
|
||||
xtag("parent", parent),
|
||||
xtag("self", this),
|
||||
xtag("self.next", this->next_leafnode()),
|
||||
xtag("rh_leaf", rh_leaf),
|
||||
xtag("rh_leaf.prev", rh_leaf ? rh_leaf->prev_leafnode() : nullptr)));
|
||||
}
|
||||
|
||||
return this->n_elt();
|
||||
} /*verify_helper*/
|
||||
|
||||
template <typename Key, typename Value, typename Properties>
|
||||
void
|
||||
LeafNode<Key, Value, Properties>::verify_glb_key(Key const & key) const {
|
||||
using xo::xtag;
|
||||
|
||||
LeafNodeItemType const & elt = this->lookup_elt(0);
|
||||
|
||||
if (elt.key() != key) {
|
||||
throw std::runtime_error(tostr("LeafNode::verify_glb_key"
|
||||
": expected stored greatest-lower-bound key to match leftmost leaf's key",
|
||||
xtag("@", this),
|
||||
xtag("reported_key", key),
|
||||
xtag("actual_key", elt.key())));
|
||||
}
|
||||
} /*verify_glb_key*/
|
||||
|
||||
template <typename Key, typename Value, typename Properties>
|
||||
FindNodeResult<LeafNode<Key, Value, Properties>>
|
||||
LeafNode<Key, Value, Properties>::find_min_leaf_node() {
|
||||
return FindNodeResult<LeafNode<Key, Value, Properties>>(0, this);
|
||||
} /*find_min_leaf_node*/
|
||||
|
||||
template <typename Key, typename Value, typename Properties>
|
||||
FindNodeResult<LeafNode<Key, Value, Properties>>
|
||||
LeafNode<Key, Value, Properties>::find_max_leaf_node() {
|
||||
return FindNodeResult<LeafNode<Key, Value, Properties>>(0, this);
|
||||
} /*c_find_max_leaf_node*/
|
||||
|
||||
template <typename Key, typename Value, typename Properties>
|
||||
void
|
||||
LeafNode<Key, Value, Properties>::notify_remove() {
|
||||
if (this->prev_leafnode_)
|
||||
this->prev_leafnode_->assign_next_leafnode(this->next_leafnode_);
|
||||
if (this->next_leafnode_)
|
||||
this->next_leafnode_->assign_prev_leafnode(this->prev_leafnode_);
|
||||
} /*notify_remove*/
|
||||
|
||||
template <typename Key, typename Value, typename Properties>
|
||||
LeafNode<Key, Value, Properties>::LeafNode(std::size_t branching_factor)
|
||||
: LeafNodeShim<Key, Value, Properties>(NodeType::leaf, branching_factor)
|
||||
{
|
||||
/* must call ctor explicitly for each element.
|
||||
* compiler can't do this for us, b/c it doesn't know size of flexible array
|
||||
*/
|
||||
for (std::size_t i = 0, n = branching_factor; i < n; ++i) {
|
||||
new (&(this->lookup_elt(i))) LeafNodeItemType();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Key, typename Value, typename Properties>
|
||||
LeafNode<Key, Value, Properties>::LeafNode(std::pair<Key const, Value> const & kv_pair,
|
||||
std::size_t branching_factor)
|
||||
: LeafNodeShim<Key, Value, Properties>(NodeType::leaf, branching_factor)
|
||||
{
|
||||
using xo::scope;
|
||||
using xo::xtag;
|
||||
|
||||
#ifdef NOT_USING_DEBUG
|
||||
scope x("LeafNode.ctor");
|
||||
#endif
|
||||
|
||||
this->n_elt_ = 1;
|
||||
/* since .elt_v[] is a flexible array, need to invoke constructors explicitly
|
||||
* (compiler doesn't know how many elements there are -> can't do it for us
|
||||
*/
|
||||
|
||||
#ifdef NOT_USING_DEBUG
|
||||
x.log(xtag("elt[0]", &(this->lookup_elt(0))));
|
||||
#endif
|
||||
|
||||
new (&(this->lookup_elt(0))) LeafNodeItemType(kv_pair);
|
||||
|
||||
for (std::size_t i = 1, n = branching_factor; i < n; ++i) {
|
||||
#ifdef NOT_USING_DEBUG
|
||||
x.log(xtag("i", i),
|
||||
xtag("elt[i]", &(this->lookup_elt(i))));
|
||||
#endif
|
||||
|
||||
/* using placement-new to invoke ctor explicitly */
|
||||
new (&(this->lookup_elt(i))) LeafNodeItemType();
|
||||
}
|
||||
} /*ctor*/
|
||||
|
||||
template <typename Key, typename Value, typename Properties>
|
||||
void
|
||||
LeafNode<Key, Value, Properties>::assign_siblings(LeafNode * p, LeafNode * n) {
|
||||
if (p)
|
||||
p->assign_next_leafnode(this);
|
||||
this->prev_leafnode_ = p;
|
||||
this->next_leafnode_ = n;
|
||||
if (n)
|
||||
n->assign_prev_leafnode(this);
|
||||
} /*assign_siblings*/
|
||||
|
||||
} /*namespace tree*/
|
||||
} /*namespace xo*/
|
||||
|
||||
|
||||
/* end LeafNode.hpp */
|
||||
68
xo-ordinaltree/include/xo/ordinaltree/bplustree/Lhs.hpp
Normal file
68
xo-ordinaltree/include/xo/ordinaltree/bplustree/Lhs.hpp
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
/* @file Lhs.hpp */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
namespace xo {
|
||||
namespace tree {
|
||||
namespace detail {
|
||||
/* xo::tree::detail::BplusTreeLhsBase
|
||||
*
|
||||
* use for {const + non-const} versions of BplusTree::operator[]
|
||||
*
|
||||
* Expect: either:
|
||||
* Tree = BplusTree<Key, Value, Properties>
|
||||
* LeafNodeItem = Tree::LeafNodeItemType
|
||||
* or
|
||||
* Tree = BplusTree<Key, Value, Properties> const
|
||||
* LeafNodeItem = Tree::LeafNodeItemType const
|
||||
*/
|
||||
template <class Tree, class LeafNodeItem>
|
||||
class BplusTreeLhsBase {
|
||||
public:
|
||||
using mapped_type = typename Tree::mapped_type;
|
||||
|
||||
public:
|
||||
BplusTreeLhsBase() = default;
|
||||
BplusTreeLhsBase(Tree * tree, LeafNodeItem const * item)
|
||||
: p_tree_{tree}, item_{item} {}
|
||||
|
||||
operator mapped_type const & () const {
|
||||
//using xo::tostr;
|
||||
|
||||
if (!this->item_) {
|
||||
throw std::runtime_error
|
||||
("bptree: attempt to use empty lhs object as rvalue");
|
||||
}
|
||||
|
||||
return this->item_->value();
|
||||
}
|
||||
|
||||
protected:
|
||||
Tree * p_tree_ = nullptr;
|
||||
/* points to key-value pair (interior to a B+ tree LeafNode */
|
||||
LeafNodeItem * item_ = nullptr;
|
||||
}; /*BplusTreeLhsBase*/
|
||||
|
||||
/* xo::tree::detail::BplusTreeConstLhs
|
||||
*
|
||||
* use for const version of BplusTree::operator[]
|
||||
*/
|
||||
template <class BplusTree>
|
||||
class BplusTreeConstLhs : public BplusTreeLhsBase<BplusTree const,
|
||||
typename BplusTree::LeafNodeItemType const>
|
||||
{
|
||||
public:
|
||||
BplusTreeConstLhs() = default;
|
||||
BplusTreeConstLhs(BplusTree const * tree,
|
||||
typename BplusTree::LeafNodeItemType const * item)
|
||||
: BplusTreeLhsBase<BplusTree const,
|
||||
typename BplusTree::LeafNodeItemType const>(tree, item) {}
|
||||
}; /*BplusTreeConstLhs*/
|
||||
|
||||
} /*namespace detail*/
|
||||
} /*namespace tree*/
|
||||
} /*namespace xo*/
|
||||
|
||||
/* end Lhs.hpp */
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
/* @file bplustree_tags.hpp */
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace xo {
|
||||
namespace tree {
|
||||
namespace tags {
|
||||
/* ordinal_enabled: compute ordinal statistics;
|
||||
* in particular maintain per-node subtree size
|
||||
*/
|
||||
enum ordinal_tag { ordinal_enabled, ordinal_disabled };
|
||||
} /*tags*/
|
||||
} /*namespace tree*/
|
||||
} /*namespace xo*/
|
||||
|
||||
/* end bplustree_tags.hpp */
|
||||
25
xo-ordinaltree/utest/CMakeLists.txt
Normal file
25
xo-ordinaltree/utest/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
# ordinaltree/utest/CMakeLists.txt
|
||||
|
||||
# note: tests in this directory use Catch2-provided main
|
||||
set(SELF_EXE utest.tree)
|
||||
set(SELF_SOURCE_FILES tree_utest_main.cpp redblacktree.cpp bplustree.cpp)
|
||||
|
||||
add_executable(${SELF_EXE} ${SELF_SOURCE_FILES})
|
||||
xo_include_options2(${SELF_EXE})
|
||||
|
||||
add_test(NAME ${SELF_EXE} COMMAND ${SELF_EXE})
|
||||
target_code_coverage(${SELF_EXE} AUTO ALL)
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
# internal dependencies: refcnt, ...
|
||||
|
||||
xo_dependency(${SELF_EXE} refcnt)
|
||||
xo_dependency(${SELF_EXE} indentlog)
|
||||
xo_dependency(${SELF_EXE} randomgen)
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
# 3rd part dependency: catch2:
|
||||
|
||||
xo_external_target_dependency(${SELF_EXE} Catch2 Catch2::Catch2)
|
||||
|
||||
# end ordinaltree/utest/CMakeLists.txt
|
||||
814
xo-ordinaltree/utest/bplustree.cpp
Normal file
814
xo-ordinaltree/utest/bplustree.cpp
Normal file
|
|
@ -0,0 +1,814 @@
|
|||
/* @file bplustree.cpp */
|
||||
|
||||
#define CATCH_CONFIG_ENABLE_BENCHMARKING
|
||||
|
||||
#include "random_tree_ops.hpp"
|
||||
#include "xo/ordinaltree/BplusTree.hpp"
|
||||
#include "xo/randomgen/random_seed.hpp"
|
||||
#include "xo/randomgen/print.hpp"
|
||||
#include "xo/indentlog/scope.hpp"
|
||||
#include "catch2/catch.hpp"
|
||||
|
||||
namespace {
|
||||
using xo::tree::BplusTree;
|
||||
using xo::tree::BplusStdProperties;
|
||||
using xo::tree::NullReduce;
|
||||
using xo::tree::Machdep;
|
||||
|
||||
using xo::rng::Seed;
|
||||
|
||||
using utest::TreeUtil;
|
||||
|
||||
using xo::scope;
|
||||
//using xo::scope_setup;
|
||||
using xo::xtag;
|
||||
|
||||
using BtreeKey = int;
|
||||
using BtreeValue = double;
|
||||
using BtreeProperties = BplusStdProperties<BtreeKey, BtreeValue, xo::tree::tags::ordinal_enabled>;
|
||||
//using BtreeProperties = BplusStdProperties<BtreeKey, BtreeValue, xo::tree::tags::ordinal_disabled>;
|
||||
using BpTree = BplusTree<BtreeKey,
|
||||
BtreeValue,
|
||||
NullReduce<BtreeKey>,
|
||||
BtreeProperties>;
|
||||
|
||||
/* random test data (e.g. permutation of integers [0 .. n-1]).
|
||||
* will do various tree operations using these permutations to control order
|
||||
* in which keys are presented
|
||||
*/
|
||||
struct RandomTestData {
|
||||
RandomTestData(std::size_t n,
|
||||
xo::rng::xoshiro256ss * p_rgen);
|
||||
|
||||
std::vector<std::uint32_t> const & u1v() const { return u1v_; }
|
||||
std::vector<std::uint32_t> const & u2v() const { return u2v_; }
|
||||
std::vector<std::uint32_t> const & u12_v() const { return u12_v_; }
|
||||
|
||||
private:
|
||||
/* a set comprising n randomly chosen elements drawn from [0 .. 2n-1].
|
||||
* here n = .u1v.size = .u2v.size
|
||||
*/
|
||||
std::vector<std::uint32_t> u1v_;
|
||||
/* complement of .u1v w.r.t. [0 .. 2n-1] */
|
||||
std::vector<std::uint32_t> u2v_;
|
||||
/* .u1v + .u2v */
|
||||
std::vector<std::uint32_t> u12_v_;
|
||||
}; /*RandomTestData*/
|
||||
|
||||
RandomTestData::RandomTestData(std::size_t n,
|
||||
xo::rng::xoshiro256ss * p_rgen)
|
||||
: u1v_(n), u2v_(n), u12_v_(2*n)
|
||||
{
|
||||
/* permutation of [0 .. 2n-1] */
|
||||
std::vector<std::uint32_t> u(2*n);
|
||||
|
||||
for (std::uint32_t i=0; i<2*n; ++i)
|
||||
u[i] = i;
|
||||
std::shuffle(u.begin(), u.end(), *p_rgen);
|
||||
|
||||
u1v_ = std::vector<std::uint32_t>(u.begin(), u.begin() + n);
|
||||
u2v_ = std::vector<std::uint32_t>(u.begin() + n, u.end());
|
||||
u12_v_ = std::move(u);
|
||||
} /*ctor*/
|
||||
|
||||
/* representation-independent feature benchmarks for tree algorithms.
|
||||
*
|
||||
* +------------------+
|
||||
* |AbstractTestParams|
|
||||
* +------------------+
|
||||
* ^
|
||||
* | isa +----------------+
|
||||
* +------------|StdMapTestParams| benchmark std::map<BtreeKey, BtreeValue> (bogey!)
|
||||
* | +----------------+
|
||||
* |
|
||||
* | isa +---------------+
|
||||
* +------------|BtreeTestParams| benchmark BplusTree<BtreeKey, BtreeValue, ..>
|
||||
* +---------------+
|
||||
*/
|
||||
struct AbstractTestParams {
|
||||
virtual ~AbstractTestParams() = default;
|
||||
/* insert benchmark:
|
||||
* 1. prime tree by inserting RandomTestData.u1v (random subset comprising n draws from [0 .. 2n-1])
|
||||
* 2. measure cost of inserting RandomTestData.u2v (complement of u1v w.r.t [0 .. 2n-1])
|
||||
*/
|
||||
virtual void run_insert_benchmark(RandomTestData const & random_testdata) const = 0;
|
||||
virtual void run_erase_benchmark(RandomTestData const & random_testdata) const = 0;
|
||||
virtual void run_lookup_benchmark(RandomTestData const & random_testdata) const = 0;
|
||||
virtual void run_traverse_benchmark(RandomTestData const & random_testdata) const = 0;
|
||||
};
|
||||
|
||||
struct StdMapTestParams : public AbstractTestParams {
|
||||
StdMapTestParams(char const * name)
|
||||
: test_name_{name} {}
|
||||
|
||||
/* 1. make map containing keys in random_testdata.u1v.
|
||||
* 2. during construction, interleave inserts against a temporary map,
|
||||
* to spoil sequential heap allocation (i.e. simulate fragmentation)
|
||||
*/
|
||||
std::map<BtreeKey, BtreeValue> make_random_map1(RandomTestData const & random_testdata) const {
|
||||
std::map<BtreeKey, BtreeValue> tree;
|
||||
/* 2nd tree to interfere with locality */
|
||||
std::map<BtreeKey, BtreeValue> tree2;
|
||||
|
||||
for (std::uint32_t x : random_testdata.u1v()) {
|
||||
tree.insert({x, 10*x});
|
||||
/* 2nd tree to interfere with locality */
|
||||
for (std::uint32_t y = 0; y < 8; ++y)
|
||||
tree2.insert({8*x+y, 10*8*x+y});
|
||||
}
|
||||
|
||||
return tree;
|
||||
} /*make_random_map1*/
|
||||
|
||||
/* 1. make map containing keys in both random_testdata.u1v + random_testdata.u2v
|
||||
* 2. during construction, interleave inserts against a temporary map,
|
||||
* to spoil sequential heap allocation (i.e. simulate fragmentation)
|
||||
*/
|
||||
std::map<BtreeKey, BtreeValue> make_random_map12(RandomTestData const & random_testdata) const {
|
||||
std::map<BtreeKey, BtreeValue> tree;
|
||||
/* temporary tree to interfere with locality */
|
||||
std::map<BtreeKey, BtreeValue> tree2;
|
||||
|
||||
for (std::uint32_t x : random_testdata.u12_v()) {
|
||||
tree.insert({x, 10*x});
|
||||
/* 2nd tree to interfere with memory locality */
|
||||
for (std::uint32_t y = 0; y < 8; ++y)
|
||||
tree2.insert({8*x+y, 10*8*x+y});
|
||||
}
|
||||
|
||||
return tree;
|
||||
} /*make_random_map12*/
|
||||
|
||||
virtual void run_insert_benchmark(RandomTestData const & random_testdata) const override;
|
||||
virtual void run_erase_benchmark(RandomTestData const & random_testdata) const override;
|
||||
virtual void run_lookup_benchmark(RandomTestData const & random_testdata) const override;
|
||||
virtual void run_traverse_benchmark(RandomTestData const & random_testdata) const override;
|
||||
|
||||
char const * test_name_ = nullptr;
|
||||
};
|
||||
|
||||
void
|
||||
StdMapTestParams::run_insert_benchmark(RandomTestData const & random_testdata) const
|
||||
{
|
||||
/* see also: BtreeTestParams::run_insert_benchmark() */
|
||||
|
||||
BENCHMARK_ADVANCED(this->test_name_)(Catch::Benchmark::Chronometer clock)
|
||||
{
|
||||
std::size_t n = random_testdata.u1v().size();
|
||||
|
||||
std::map<BtreeKey, BtreeValue> tree
|
||||
= std::move(this->make_random_map1(random_testdata));
|
||||
|
||||
/* benchmark additional inserts */
|
||||
clock.measure([&](int seq) {
|
||||
std::size_t key = random_testdata.u2v()[seq % n];
|
||||
double value = 10 * key;
|
||||
|
||||
tree.insert({key, value});
|
||||
return tree.size();
|
||||
});
|
||||
};
|
||||
} /*run_insert_benchmark*/
|
||||
|
||||
void
|
||||
StdMapTestParams::run_erase_benchmark(RandomTestData const & random_testdata) const
|
||||
{
|
||||
BENCHMARK_ADVANCED(this->test_name_)(Catch::Benchmark::Chronometer clock)
|
||||
{
|
||||
std::size_t n = random_testdata.u1v().size();
|
||||
|
||||
std::map<BtreeKey, BtreeValue> tree
|
||||
= std::move(this->make_random_map12(random_testdata));;
|
||||
|
||||
clock.measure([&](int seq) {
|
||||
/* catch2 decides how many times to run this lambda,
|
||||
* in effort to get statistically valid sample.
|
||||
*
|
||||
* If it calls lambda n times, then seq will increase from [0 .. n-1]
|
||||
*/
|
||||
|
||||
std::size_t key = random_testdata.u1v()[seq % n];
|
||||
|
||||
//std::clog << "i=" << i << std::endl;
|
||||
tree.erase(key);
|
||||
|
||||
return tree.size();
|
||||
});
|
||||
};
|
||||
} /*run_erase_benchmark*/
|
||||
|
||||
void
|
||||
StdMapTestParams::run_lookup_benchmark(RandomTestData const & random_testdata) const
|
||||
{
|
||||
BENCHMARK_ADVANCED(this->test_name_)(Catch::Benchmark::Chronometer clock)
|
||||
{
|
||||
std::size_t n = random_testdata.u1v().size();
|
||||
|
||||
std::map<BtreeKey, BtreeValue> tree
|
||||
= std::move(this->make_random_map1(random_testdata));
|
||||
|
||||
clock.measure([&](int seq) {
|
||||
/* catch2 decides how many times to run this lambda,
|
||||
* in effort to get statistically valid sample.
|
||||
*
|
||||
* If it calls lambda n times, then seq will increase from [0 .. n-1]
|
||||
*/
|
||||
|
||||
std::size_t key = random_testdata.u1v()[seq % n];
|
||||
|
||||
//std::clog << "i=" << i << std::endl;
|
||||
double value = tree[key];
|
||||
|
||||
return value;
|
||||
});
|
||||
};
|
||||
} /*run_lookup_benchmark*/
|
||||
|
||||
void
|
||||
StdMapTestParams::run_traverse_benchmark(RandomTestData const & random_testdata) const
|
||||
{
|
||||
BENCHMARK_ADVANCED(this->test_name_)(Catch::Benchmark::Chronometer clock)
|
||||
{
|
||||
std::size_t n = random_testdata.u1v().size();
|
||||
|
||||
std::map<BtreeKey, BtreeValue> tree
|
||||
= std::move(this->make_random_map1(random_testdata));
|
||||
|
||||
clock.measure([&](int seq) {
|
||||
/* catch2 decides how many times to run this lambda,
|
||||
* in effort to get statistically valid sample.
|
||||
*
|
||||
* If it calls lambda n times, then seq will increase from [0 .. n-1]
|
||||
*/
|
||||
|
||||
std::size_t key = random_testdata.u1v()[seq % n];
|
||||
|
||||
//std::clog << "i=" << i << std::endl;
|
||||
double value = tree[key];
|
||||
|
||||
return value;
|
||||
});
|
||||
};
|
||||
} /*run_traverse_benchmark*/
|
||||
|
||||
struct BtreeTestParams : public AbstractTestParams {
|
||||
BtreeTestParams(char const * name, std::size_t bf, bool debug_flag)
|
||||
: test_name_{name}, branching_factor_{bf}, debug_flag_{debug_flag} {}
|
||||
|
||||
BpTree make_empty_bptree() const {
|
||||
BtreeProperties properties(branching_factor_,
|
||||
debug_flag_);
|
||||
return BpTree(properties);
|
||||
}
|
||||
|
||||
/* 1. make b+ tree containing keys in random_testdata.u1v.
|
||||
* 2. during constructions, interleave inserts against a temporary b+ tree,
|
||||
* to spoil sequential heap allocation (i.e. simulate fragmentation)
|
||||
*/
|
||||
BpTree make_random_bptree1(RandomTestData const & random_testdata) const {
|
||||
BpTree bptree = this->make_empty_bptree();
|
||||
/* 2nd tree, just to spoil memory locality */
|
||||
BpTree bptree2 = this->make_empty_bptree();
|
||||
|
||||
for (std::uint32_t x : random_testdata.u1v()) {
|
||||
bptree.insert(BpTree::value_type(x, 10 * x));
|
||||
/* 2nd tree to interfere with locality */
|
||||
for (std::uint32_t y = 0; y < 8; ++y) {
|
||||
bptree2.insert(BpTree::value_type(8*x+y, 10 * (8*x+y)));
|
||||
}
|
||||
}
|
||||
|
||||
return bptree;
|
||||
} /*make_random_bptree1*/
|
||||
|
||||
BpTree make_random_bptree12(RandomTestData const & random_testdata) const {
|
||||
BpTree bptree = this->make_empty_bptree();
|
||||
/* 2nd tree, just to spoil memory locality */
|
||||
BpTree bptree2 = this->make_empty_bptree();
|
||||
|
||||
for (std::uint32_t x : random_testdata.u12_v()) {
|
||||
bptree.insert(BpTree::value_type(x, 10 * x));
|
||||
/* 2nd tree to interfere with locality */
|
||||
for (std::uint32_t y = 0; y < 8; ++y) {
|
||||
bptree2.insert(BpTree::value_type(8*x+y, 10 * (8*x+y)));
|
||||
}
|
||||
}
|
||||
|
||||
return bptree;
|
||||
} /*make_random_bptree12*/
|
||||
|
||||
void run_unit_test(xo::rng::xoshiro256ss * p_rgen) const;
|
||||
|
||||
virtual void run_insert_benchmark(RandomTestData const & random_testdata) const override;
|
||||
virtual void run_erase_benchmark(RandomTestData const & random_testdata) const override;
|
||||
virtual void run_lookup_benchmark(RandomTestData const & random_testdata) const override;
|
||||
virtual void run_traverse_benchmark(RandomTestData const & random_testdata) const override;
|
||||
|
||||
/* test (or benchmark) name -- 1st argument to catch2 TEST_CASE() / BENCHMARK() / SECTION() macro */
|
||||
char const * test_name_ = nullptr;
|
||||
/* exercise B+ tree with this branching factor */
|
||||
std::size_t branching_factor_ = 0;
|
||||
/* for benchmarks only: if true enable verbose logging of B+ tree operations. otherwise not used */
|
||||
bool debug_flag_ = false;
|
||||
}; /*BtreeTestParams*/
|
||||
|
||||
void
|
||||
BtreeTestParams::run_unit_test(xo::rng::xoshiro256ss * p_rgen) const
|
||||
{
|
||||
std::size_t branching_factor = this->branching_factor_;
|
||||
|
||||
/* perform a series of tests with increasing scale */
|
||||
for (std::uint32_t n = 0; n <= 1024;) {
|
||||
if (n == 0) {
|
||||
bool ok_flag = false;
|
||||
|
||||
for (std::uint32_t attention = 0; !ok_flag && (attention < 2); ++attention) {
|
||||
ok_flag = true;
|
||||
|
||||
bool debug_flag = (attention == 1);
|
||||
|
||||
BtreeProperties properties(branching_factor,
|
||||
debug_flag);
|
||||
BpTree bptree(properties);
|
||||
|
||||
scope log(XO_DEBUG2(debug_flag, "bptree"),
|
||||
xtag("vm_page_size", Machdep::get_page_size()),
|
||||
xtag("branching_factor", bptree.branching_factor()),
|
||||
xtag("leaf_node_size", sizeof(BpTree::LeafNodeType)),
|
||||
xtag("internal_node_size", sizeof(BpTree::InternalNodeType)));
|
||||
|
||||
REQUIRE_ORCAPTURE(ok_flag, debug_flag, bptree.size() == 0);
|
||||
REQUIRE_ORCAPTURE(ok_flag, debug_flag, bptree.verify_ok(true) == true);
|
||||
|
||||
log && log(xtag("size", n));
|
||||
|
||||
ok_flag &= TreeUtil<BpTree>::check_bidirectional_iterator(0 /*dvalue - not used*/,
|
||||
debug_flag,
|
||||
bptree);
|
||||
|
||||
ok_flag &= TreeUtil<BpTree>::test_clear(debug_flag, &bptree);
|
||||
|
||||
log.end_scope();
|
||||
}
|
||||
} else {
|
||||
/* for each tree size, do multiple trials;
|
||||
* choosing different pseudorandom key order for each trial
|
||||
*/
|
||||
for (std::uint32_t trial = 0; trial < 10; ++trial) {
|
||||
/* repeated trials with different rng state */
|
||||
|
||||
bool ok_flag = false;
|
||||
|
||||
for (std::uint32_t attention = 0; !ok_flag && (attention < 2); ++attention) {
|
||||
ok_flag = true;
|
||||
|
||||
/* attention=0:
|
||||
* - no logging
|
||||
* - detect assertion failures, but don't report them to catch
|
||||
* attention=1:
|
||||
* - only runs if failure detected with attention=0
|
||||
* - full logging
|
||||
* - report to catch
|
||||
*/
|
||||
|
||||
bool debug_flag = (attention == 1);
|
||||
|
||||
BtreeProperties properties(branching_factor,
|
||||
debug_flag);
|
||||
BpTree bptree(properties);
|
||||
|
||||
scope log(XO_DEBUG2(debug_flag, "bptree"),
|
||||
xtag("vm_page_size", Machdep::get_page_size()),
|
||||
xtag("branching_factor", bptree.branching_factor()),
|
||||
xtag("leaf_node_size", sizeof(BpTree::LeafNodeType)),
|
||||
xtag("internal_node_size", sizeof(BpTree::InternalNodeType)));
|
||||
|
||||
REQUIRE_ORCAPTURE(ok_flag, debug_flag, bptree.size() == 0);
|
||||
REQUIRE_ORCAPTURE(ok_flag, debug_flag, bptree.verify_ok(true) == true);
|
||||
|
||||
log && log(xtag("size", n), xtag("trial", trial));
|
||||
|
||||
/* insert [0..n-1] in random order */
|
||||
ok_flag &= TreeUtil<BpTree>::random_inserts(n, debug_flag, p_rgen, &bptree);
|
||||
|
||||
/* verification problem -> print tree */
|
||||
log && log(xtag("bptree", (char const *)"..."));
|
||||
if (log) bptree.print(std::cout, log.nesting_level() + 2);
|
||||
|
||||
try {
|
||||
REQUIRE_ORCAPTURE(ok_flag, debug_flag, bptree.verify_ok(debug_flag));
|
||||
} catch(std::exception & ex) {
|
||||
log && log(xtag("exception", ex.what()));
|
||||
}
|
||||
|
||||
if (properties.ordinal_enabled()) {
|
||||
ok_flag &= TreeUtil<BpTree>::check_ordinal_lookup(0 /*dvalue*/,
|
||||
debug_flag,
|
||||
bptree);
|
||||
}
|
||||
|
||||
/* verify inorder traverse, using iterator api */
|
||||
ok_flag &= TreeUtil<BpTree>::check_bidirectional_iterator(0,
|
||||
debug_flag,
|
||||
bptree);
|
||||
|
||||
ok_flag &= TreeUtil<BpTree>::random_lookups(debug_flag,
|
||||
bptree,
|
||||
p_rgen);
|
||||
|
||||
if (properties.ordinal_enabled()) {
|
||||
/* paranoid check that iteration / random_lookups didn't somehow disturb tree */
|
||||
ok_flag &= TreeUtil<BpTree>::check_ordinal_lookup(0 /*dvalue*/,
|
||||
debug_flag,
|
||||
bptree);
|
||||
}
|
||||
|
||||
/* TODO:
|
||||
* - check_reduced_sum()
|
||||
* - check_ordinal_lookup()
|
||||
* - check_bidirectional_iterator()
|
||||
* - random_updates()
|
||||
* - check_ordinal_lookup()
|
||||
* - check_bidirectional_iterator()
|
||||
* - check_reduced_sum()
|
||||
*/
|
||||
|
||||
/* remove [0..n-1] in random order */
|
||||
ok_flag &= TreeUtil<BpTree>::random_removes(debug_flag, p_rgen, &bptree);
|
||||
|
||||
/* insert [0..n-1] again, so we can test .clear() */
|
||||
ok_flag &= TreeUtil<BpTree>::random_inserts(n, debug_flag, p_rgen, &bptree);
|
||||
|
||||
ok_flag &= TreeUtil<BpTree>::test_clear(debug_flag, &bptree);
|
||||
|
||||
log.end_scope();
|
||||
} /*loop over attention value*/
|
||||
} /*loop over trial#*/
|
||||
}
|
||||
|
||||
if (n == 0)
|
||||
n = 1;
|
||||
else
|
||||
n = 2*n;
|
||||
}
|
||||
} /*run_unit_test*/
|
||||
|
||||
void
|
||||
BtreeTestParams::run_insert_benchmark(RandomTestData const & random_testdata) const
|
||||
{
|
||||
BENCHMARK_ADVANCED(this->test_name_)(Catch::Benchmark::Chronometer clock)
|
||||
{
|
||||
std::size_t n = random_testdata.u1v().size();
|
||||
|
||||
BpTree bptree = std::move(this->make_random_bptree1(random_testdata));
|
||||
|
||||
/* benchmark additional inserts (don't want to benchmark on empty tree) */
|
||||
clock.measure([&](int seq) {
|
||||
/* catch2 decides how many times to run this lambda,
|
||||
* in effort to get statistically valid sample.
|
||||
*
|
||||
* If it calls lambda n times, then seq will increase from [0 .. n-1]
|
||||
*/
|
||||
|
||||
std::size_t key = random_testdata.u2v()[seq % n];
|
||||
double value = 10 * key;
|
||||
|
||||
bptree.insert(BpTree::value_type(key, value));
|
||||
|
||||
return bptree.size();
|
||||
});
|
||||
};
|
||||
} /*run_insert_benchmark*/
|
||||
|
||||
void
|
||||
BtreeTestParams::run_erase_benchmark(RandomTestData const & random_testdata) const
|
||||
{
|
||||
BENCHMARK_ADVANCED(this->test_name_)(Catch::Benchmark::Chronometer clock)
|
||||
{
|
||||
std::size_t n = random_testdata.u1v().size();
|
||||
|
||||
/* b+ tree with 2n elements */
|
||||
BpTree bptree = std::move(this->make_random_bptree12(random_testdata));
|
||||
|
||||
/* measure time to remove n elements */
|
||||
clock.measure([&](int seq) {
|
||||
/* catch2 decides how many times to run this lambda,
|
||||
* in effort to get statistically valid sample.
|
||||
*
|
||||
* If it calls lambda n times, then seq will increase from [0 .. n-1]
|
||||
*/
|
||||
|
||||
//std::clog << "i=" << i << std::endl;
|
||||
bptree.erase(random_testdata.u1v()[seq % n]);
|
||||
|
||||
return bptree.size();
|
||||
});
|
||||
};
|
||||
} /*run_erase_benchmark*/
|
||||
|
||||
void
|
||||
BtreeTestParams::run_lookup_benchmark(RandomTestData const & random_testdata) const
|
||||
{
|
||||
BENCHMARK_ADVANCED(this->test_name_)(Catch::Benchmark::Chronometer clock)
|
||||
{
|
||||
std::size_t n = random_testdata.u1v().size();
|
||||
|
||||
BpTree bptree = std::move(this->make_random_bptree1(random_testdata));
|
||||
|
||||
/* benchmark random lookups */
|
||||
clock.measure([&](int seq) {
|
||||
/* catch2 decides how many times to run this lambda,
|
||||
* in effort to get statistically valid sample.
|
||||
*
|
||||
* If it calls lambda n times, then seq will increase from [0 .. n-1]
|
||||
*/
|
||||
|
||||
std::size_t key = random_testdata.u1v()[seq % n];
|
||||
|
||||
double value = bptree[key];
|
||||
|
||||
return value;
|
||||
});
|
||||
};
|
||||
} /*run_lookup_benchmark*/
|
||||
|
||||
void
|
||||
BtreeTestParams::run_traverse_benchmark(RandomTestData const & random_testdata) const
|
||||
{
|
||||
BENCHMARK_ADVANCED(this->test_name_)(Catch::Benchmark::Chronometer clock)
|
||||
{
|
||||
std::size_t n = random_testdata.u1v().size();
|
||||
|
||||
BpTree bptree = std::move(this->make_random_bptree1(random_testdata));
|
||||
|
||||
/* benchmark traverse */
|
||||
BpTree::const_iterator ix = bptree.begin();
|
||||
|
||||
clock.measure([&](int seq) {
|
||||
/* catch2 decides how many times to run this lambda,
|
||||
* in effort to get statistically valid sample.
|
||||
*
|
||||
* If it calls lambda n times, then seq will increase from [0 .. n-1]
|
||||
*/
|
||||
|
||||
if (seq % n == 0)
|
||||
ix = bptree.begin();
|
||||
|
||||
return ix++;
|
||||
});
|
||||
};
|
||||
} /*run_traverse_benchmark*/
|
||||
|
||||
TEST_CASE("bptree", "[bplustree]") {
|
||||
uint64_t seed = 14950349842636922572UL;
|
||||
/* can reseed from /dev/random with: */
|
||||
//Seed<xo::rng::xoshiro256ss> seed;
|
||||
|
||||
auto rgen = xo::rng::xoshiro256ss(seed);
|
||||
|
||||
/* exercise multiple branching factors */
|
||||
std::array<BtreeTestParams, 4> const params_v
|
||||
= {{
|
||||
BtreeTestParams("bf=4",
|
||||
4 /*branching_factor*/,
|
||||
false /*debug_flag - not used*/),
|
||||
BtreeTestParams("bf=12",
|
||||
12 /*branching_factor*/,
|
||||
false /*debug_flag - not used*/),
|
||||
BtreeTestParams("bf=28",
|
||||
28 /*branching_factor*/,
|
||||
false /*debug_flag - not used*/),
|
||||
BtreeTestParams("bf=60",
|
||||
60 /*branching_factor*/,
|
||||
false /*debug_flag - not used*/)
|
||||
}};
|
||||
|
||||
for (std::uint32_t i_pm = 0; i_pm < params_v.size(); ++i_pm) {
|
||||
SECTION(params_v[i_pm].test_name_) {
|
||||
params_v[i_pm].run_unit_test(&rgen);
|
||||
}
|
||||
}
|
||||
} /*TEST_CASE(bptree)*/
|
||||
|
||||
/* to run:
|
||||
* $ ./utest.tree [!benchmark]
|
||||
*
|
||||
* looks like ospage4 (1k nodes) gets best performance
|
||||
*/
|
||||
TEST_CASE("bptree-benchmark", "[!benchmark]") {
|
||||
using BtreeProperties = BplusStdProperties<BtreeKey, BtreeValue>;
|
||||
|
||||
/* 2 cache lines per node (though note that we're not aligning nodes on cacheline boundaries) */
|
||||
std::size_t const c_cacheline_branching_factor = 4; // BtreeProperties::default_cacheline_branching_factor();
|
||||
std::size_t const c_ospage16_branching_factor = BtreeProperties::branching_factor_for_size(Machdep::get_page_size() / 16);
|
||||
std::size_t const c_ospage8_branching_factor = BtreeProperties::branching_factor_for_size(Machdep::get_page_size() / 8);
|
||||
std::size_t const c_ospage4_branching_factor = BtreeProperties::branching_factor_for_size(Machdep::get_page_size() / 4);
|
||||
std::size_t const c_ospage2_branching_factor = BtreeProperties::branching_factor_for_size(Machdep::get_page_size() / 2);
|
||||
std::size_t const c_ospage1_branching_factor = BtreeProperties::branching_factor_for_size(Machdep::get_page_size());
|
||||
|
||||
/* random seed -- we don't need deterministic behavior for benchmarking, unless we encounter internal logic error */
|
||||
//std::uint64_t seed = 17372468046414980217UL;
|
||||
Seed<xo::rng::xoshiro256ss> seed;
|
||||
|
||||
auto rgen = xo::rng::xoshiro256ss(seed);
|
||||
|
||||
constexpr bool c_debug_flag = false;
|
||||
|
||||
/* n keys [0 .. n-1] */
|
||||
std::uint32_t n = 25000;
|
||||
|
||||
RandomTestData random_testdata(n, &rgen);
|
||||
|
||||
#ifdef OBSOLETE
|
||||
/* random permutation of [0..n-1] */
|
||||
std::vector<std::uint32_t> u(n);
|
||||
{
|
||||
for (std::uint32_t i=0; i<n; ++i)
|
||||
u[i] = i;
|
||||
std::shuffle(u.begin(), u.end(), rgen);
|
||||
}
|
||||
|
||||
/* random permutation of [n..2n-1] */
|
||||
std::vector<std::uint32_t> u2(n);
|
||||
{
|
||||
for (std::uint32_t i=0; i<n; ++i)
|
||||
u2[i] = n+i;
|
||||
std::shuffle(u2.begin(), u2.end(), rgen);
|
||||
}
|
||||
#endif
|
||||
|
||||
std::clog << "rng-seed=" << seed << "\n"
|
||||
<< "opage16-branching-factor=" << c_ospage16_branching_factor << "\n"
|
||||
<< "opage16-leaf-size=" << sizeof(BpTree::LeafNodeType) + c_ospage16_branching_factor * sizeof(BpTree::LeafNodeItemType) << "\n"
|
||||
<< "opage16-internal-size=" << sizeof(BpTree::InternalNodeType) + c_ospage16_branching_factor * sizeof(BpTree::InternalNodeItemType) << "\n"
|
||||
<< "opage8-branching-factor=" << c_ospage8_branching_factor << "\n"
|
||||
<< "opage8-leaf-size=" << sizeof(BpTree::LeafNodeType) + c_ospage8_branching_factor * sizeof(BpTree::LeafNodeItemType) << "\n"
|
||||
<< "opage8-internal-size=" << sizeof(BpTree::InternalNodeType) + c_ospage8_branching_factor * sizeof(BpTree::InternalNodeItemType) << "\n"
|
||||
<< "opage4-branching-factor=" << c_ospage4_branching_factor << "\n"
|
||||
<< "opage4-leaf-size=" << sizeof(BpTree::LeafNodeType) + c_ospage4_branching_factor * sizeof(BpTree::LeafNodeItemType) << "\n"
|
||||
<< "opage4-internal-size=" << sizeof(BpTree::InternalNodeType) + c_ospage4_branching_factor * sizeof(BpTree::InternalNodeItemType) << "\n"
|
||||
<< "opage2-branching-factor=" << c_ospage2_branching_factor << "\n"
|
||||
<< "opage2-leaf-size=" << sizeof(BpTree::LeafNodeType) + c_ospage2_branching_factor * sizeof(BpTree::LeafNodeItemType) << "\n"
|
||||
<< "opage2-internal-size=" << sizeof(BpTree::InternalNodeType) + c_ospage2_branching_factor * sizeof(BpTree::InternalNodeItemType) << "\n"
|
||||
<< "opage1-branching-factor=" << c_ospage1_branching_factor << "\n"
|
||||
<< "default-branching-factor[cacheline]=" << BplusStdProperties<BtreeKey, BtreeValue>::default_cacheline_branching_factor() << "\n"
|
||||
<< "page-size=" << Machdep::get_page_size() << "\n"
|
||||
<< "cache-line-size=" << Machdep::get_cache_line_size() << "\n"
|
||||
<< "generic-node-overhead=" << sizeof(BpTree::GenericNodeType) << "\n"
|
||||
<< "leaf-node-overhead=" << sizeof(BpTree::LeafNodeType) << "\n"
|
||||
<< "leaf-node-item-size=" << sizeof(BpTree::LeafNodeItemType) << "\n"
|
||||
<< "internal-node-overhead=" << sizeof(BpTree::InternalNodeType) << "\n"
|
||||
<< "internal-node-item-size=" << sizeof(BpTree::InternalNodeItemType) << "\n"
|
||||
<< "actual-cacheline-leaf-node-size=" << sizeof(BpTree::LeafNodeType) + c_cacheline_branching_factor * sizeof(BpTree::LeafNodeItemType) << "\n"
|
||||
<< "actual-cacheline-internal-node-size=" << sizeof(BpTree::InternalNodeType) + c_cacheline_branching_factor * sizeof(BpTree::InternalNodeItemType) << "\n"
|
||||
<< "actual-ospage-leaf-node-size=" << sizeof(BpTree::LeafNodeType) + c_ospage1_branching_factor * sizeof(BpTree::LeafNodeItemType) << "\n"
|
||||
<< "actual-ospage-internal-node-size=" << sizeof(BpTree::InternalNodeType) + c_ospage1_branching_factor * sizeof(BpTree::InternalNodeItemType) << "\n"
|
||||
<< "n=" << n << "\n"
|
||||
<< std::endl;
|
||||
|
||||
{
|
||||
std::array<std::unique_ptr<AbstractTestParams>, 7> const params_v
|
||||
= {{
|
||||
std::unique_ptr<AbstractTestParams>(new StdMapTestParams("std-map-insert")),
|
||||
std::unique_ptr<AbstractTestParams>(new BtreeTestParams("bplus-min-insert",
|
||||
c_cacheline_branching_factor,
|
||||
false)),
|
||||
std::unique_ptr<AbstractTestParams>(new BtreeTestParams("bplus-ospage16-insert",
|
||||
c_ospage16_branching_factor,
|
||||
false)),
|
||||
std::unique_ptr<AbstractTestParams>(new BtreeTestParams("bplus-ospage8-insert",
|
||||
c_ospage8_branching_factor,
|
||||
false)),
|
||||
std::unique_ptr<AbstractTestParams>(new BtreeTestParams("bplus-ospage4-insert",
|
||||
c_ospage4_branching_factor,
|
||||
false)),
|
||||
std::unique_ptr<AbstractTestParams>(new BtreeTestParams("bplus-ospage2-insert",
|
||||
c_ospage2_branching_factor,
|
||||
false)),
|
||||
std::unique_ptr<AbstractTestParams>(new BtreeTestParams("bplus-ospage-insert",
|
||||
c_ospage1_branching_factor,
|
||||
false))
|
||||
}};
|
||||
|
||||
/* note: w/cacheline:
|
||||
* getting 593ms for 10^6 inserts;
|
||||
* i.e. ~593ns each
|
||||
* w/ospage:
|
||||
* getting 188ms for 10^6 inserts;
|
||||
* i.e. ~188ns each
|
||||
* (with ospage size 4k -> branching factor 252)
|
||||
*/
|
||||
for(std::uint32_t i_bm = 0; i_bm < params_v.size(); ++i_bm) {
|
||||
params_v[i_bm]->run_insert_benchmark(random_testdata);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
std::array<std::unique_ptr<AbstractTestParams>, 7> const params_v
|
||||
= {{
|
||||
std::unique_ptr<AbstractTestParams>(new StdMapTestParams("std-map-erase")),
|
||||
std::unique_ptr<AbstractTestParams>(new BtreeTestParams("bplus-min-remove",
|
||||
c_cacheline_branching_factor,
|
||||
false)),
|
||||
std::unique_ptr<AbstractTestParams>(new BtreeTestParams("bplus-ospage16-remove",
|
||||
c_ospage16_branching_factor,
|
||||
false)),
|
||||
std::unique_ptr<AbstractTestParams>(new BtreeTestParams("bplus-ospage8-remove",
|
||||
c_ospage8_branching_factor,
|
||||
false)),
|
||||
std::unique_ptr<AbstractTestParams>(new BtreeTestParams("bplus-ospage4-remove",
|
||||
c_ospage8_branching_factor,
|
||||
false)),
|
||||
std::unique_ptr<AbstractTestParams>(new BtreeTestParams("bplus-ospage2-remove",
|
||||
c_ospage8_branching_factor,
|
||||
false)),
|
||||
std::unique_ptr<AbstractTestParams>(new BtreeTestParams("bplus-ospage1-remove",
|
||||
c_ospage1_branching_factor,
|
||||
false))
|
||||
}};
|
||||
|
||||
/* note: cacheline: getting 72us for 10^2 removes;
|
||||
* i.e. ~7.2ns each
|
||||
*
|
||||
* ospage: getting 243us for 10^4 removes;
|
||||
* i.e. ~24ns each
|
||||
*/
|
||||
for (std::uint32_t i_bm = 0; i_bm < params_v.size(); ++i_bm) {
|
||||
params_v[i_bm]->run_erase_benchmark(random_testdata);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
std::array<std::unique_ptr<AbstractTestParams>, 7> const params_v
|
||||
= {{
|
||||
std::unique_ptr<AbstractTestParams>(new StdMapTestParams("std-map-lookup")),
|
||||
std::unique_ptr<AbstractTestParams>(new BtreeTestParams("bplus-min-lookup",
|
||||
c_cacheline_branching_factor,
|
||||
false)),
|
||||
std::unique_ptr<AbstractTestParams>(new BtreeTestParams("bplus-ospage16-lookup",
|
||||
c_ospage16_branching_factor,
|
||||
false)),
|
||||
std::unique_ptr<AbstractTestParams>(new BtreeTestParams("bplus-ospage8-lookup",
|
||||
c_ospage8_branching_factor,
|
||||
false)),
|
||||
std::unique_ptr<AbstractTestParams>(new BtreeTestParams("bplus-ospage4-lookup",
|
||||
c_ospage4_branching_factor,
|
||||
false)),
|
||||
std::unique_ptr<AbstractTestParams>(new BtreeTestParams("bplus-ospage2-lookup",
|
||||
c_ospage2_branching_factor,
|
||||
false)),
|
||||
std::unique_ptr<AbstractTestParams>(new BtreeTestParams("bplus-ospage1-lookup",
|
||||
c_ospage1_branching_factor,
|
||||
false))
|
||||
}};
|
||||
|
||||
/* note: cacheline:
|
||||
* getting 850us for 10^4 lookups;
|
||||
* -> ~85ns each
|
||||
* ospage:
|
||||
* getting 585us for 10^4 lookups;
|
||||
* -> ~58ns each
|
||||
*/
|
||||
for (std::uint32_t i_bm = 0; i_bm < params_v.size(); ++i_bm) {
|
||||
params_v[i_bm]->run_lookup_benchmark(random_testdata);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
std::array<std::unique_ptr<AbstractTestParams>, 7> const params_v
|
||||
= {{
|
||||
std::unique_ptr<AbstractTestParams>(new StdMapTestParams("std-map-traverse")),
|
||||
std::unique_ptr<AbstractTestParams>(new BtreeTestParams("bplus-min-traverse",
|
||||
c_cacheline_branching_factor,
|
||||
false)),
|
||||
std::unique_ptr<AbstractTestParams>(new BtreeTestParams("bplus-ospage16-traverse",
|
||||
c_ospage16_branching_factor,
|
||||
false)),
|
||||
std::unique_ptr<AbstractTestParams>(new BtreeTestParams("bplus-ospage8-traverse",
|
||||
c_ospage8_branching_factor,
|
||||
false)),
|
||||
std::unique_ptr<AbstractTestParams>(new BtreeTestParams("bplus-ospage4-traverse",
|
||||
c_ospage4_branching_factor,
|
||||
false)),
|
||||
std::unique_ptr<AbstractTestParams>(new BtreeTestParams("bplus-ospage2-traverse",
|
||||
c_ospage2_branching_factor,
|
||||
false)),
|
||||
std::unique_ptr<AbstractTestParams>(new BtreeTestParams("bplus-ospage1-traverse",
|
||||
c_ospage1_branching_factor,
|
||||
false))
|
||||
}};
|
||||
|
||||
/* note: cacheline: getting 25us to traverse tree of size 10^4
|
||||
* -> ~2.5ns each
|
||||
* note: ospage: getting 6us to traverse tree of size 10^4
|
||||
* -> ~0.6ns each
|
||||
*/
|
||||
for (std::uint32_t i_bm = 0; i_bm < params_v.size(); ++i_bm) {
|
||||
params_v[i_bm]->run_traverse_benchmark(random_testdata);
|
||||
}
|
||||
}
|
||||
|
||||
} /*TEST_CASE(bptree-benchmark)*/
|
||||
} /*namespace*/
|
||||
|
||||
/* end bplustree.cpp */
|
||||
450
xo-ordinaltree/utest/random_tree_ops.hpp
Normal file
450
xo-ordinaltree/utest/random_tree_ops.hpp
Normal file
|
|
@ -0,0 +1,450 @@
|
|||
/* @file random_tree_ops.hpp **/
|
||||
|
||||
#include "xo/randomgen/xoshiro256.hpp"
|
||||
#include "xo/indentlog/scope.hpp"
|
||||
#include "xo/indentlog/print/tag.hpp"
|
||||
#include "xo/indentlog/print/vector.hpp"
|
||||
#include "catch2/catch.hpp"
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
namespace utest {
|
||||
struct Util {
|
||||
/* generate vector with integers [0.. n-1] */
|
||||
static std::vector<std::uint32_t> vector_upto(std::uint32_t n) {
|
||||
std::vector<std::uint32_t> u(n);
|
||||
for (std::uint32_t i = 0; i < n; ++i)
|
||||
u[i] = i;
|
||||
|
||||
return u;
|
||||
} /*vector_upto*/
|
||||
|
||||
static std::map<std::uint32_t, std::uint32_t>
|
||||
map_upto(std::uint32_t n)
|
||||
{
|
||||
std::map<std::uint32_t, std::uint32_t> m;
|
||||
for(std::uint32_t i=0; i<n; ++i) {
|
||||
m[i] = i;
|
||||
}
|
||||
|
||||
return m;
|
||||
} /*map_upto*/
|
||||
|
||||
/* generate random permutation of integers [0.. n-1] */
|
||||
static std::vector<uint32_t>
|
||||
random_permutation(uint32_t n, xo::rng::xoshiro256ss *p_rgen) {
|
||||
/* vector [0 .. n-1] */
|
||||
std::vector<uint32_t> u = vector_upto(n);
|
||||
|
||||
/* shuffle to get unpredictable permutation */
|
||||
std::shuffle(u.begin(), u.end(), *p_rgen);
|
||||
|
||||
return u;
|
||||
} /*random_permutation*/
|
||||
}; /*Util*/
|
||||
|
||||
/* note: trivial REQUIRE() call in else branch bc we still want
|
||||
* catch2 to count assertions when verification succeeds
|
||||
*/
|
||||
# define REQUIRE_ORCAPTURE(ok_flag, catch_flag, expr) \
|
||||
if (catch_flag) { \
|
||||
REQUIRE((expr)); \
|
||||
} else { \
|
||||
REQUIRE(true); \
|
||||
ok_flag &= (expr); \
|
||||
}
|
||||
|
||||
# define REQUIRE_ORFAIL(ok_flag, catch_flag, expr) \
|
||||
REQUIRE_ORCAPTURE(ok_flag, catch_flag, expr); \
|
||||
if (!ok_flag) \
|
||||
return ok_flag
|
||||
|
||||
|
||||
template <typename Tree>
|
||||
struct TreeUtil : public Util {
|
||||
static bool
|
||||
test_clear(bool catch_flag,
|
||||
Tree * p_tree)
|
||||
{
|
||||
bool ok_flag = true;
|
||||
|
||||
REQUIRE_ORFAIL(ok_flag, catch_flag, p_tree->verify_ok());
|
||||
|
||||
p_tree->clear();
|
||||
|
||||
REQUIRE_ORFAIL(ok_flag, catch_flag, p_tree->verify_ok(catch_flag));
|
||||
REQUIRE_ORFAIL(ok_flag, catch_flag, p_tree->empty());
|
||||
REQUIRE_ORFAIL(ok_flag, catch_flag, p_tree->size() == 0);
|
||||
|
||||
return ok_flag;
|
||||
} /*test_clear*/
|
||||
|
||||
/* do n random inserts (taken from *p_rgen) into *p_rbtreẹ
|
||||
* inserted keys will be distinct values in [0, .., n-1]
|
||||
*/
|
||||
static bool
|
||||
random_inserts(std::uint32_t n,
|
||||
bool catch_flag,
|
||||
xo::rng::xoshiro256ss * p_rgen,
|
||||
Tree * p_tree)
|
||||
{
|
||||
using xo::xtag;
|
||||
|
||||
bool ok_flag = true;
|
||||
|
||||
xo::scope log(XO_DEBUG(catch_flag));
|
||||
|
||||
REQUIRE_ORFAIL(ok_flag, catch_flag, p_tree->verify_ok());
|
||||
|
||||
/* n keys 0..n-1 */
|
||||
std::vector<std::uint32_t> u(n);
|
||||
for(std::uint32_t i=0; i<n; ++i)
|
||||
u[i] = i;
|
||||
|
||||
/* shuffle to get unpredictable insert order */
|
||||
std::shuffle(u.begin(), u.end(), *p_rgen);
|
||||
|
||||
/* insert keys according to permutation u */
|
||||
uint32_t i = 1;
|
||||
for(uint32_t x : u) {
|
||||
log && log(xtag("i", i), xtag("n", n), xtag("key", x));
|
||||
/* .first: iterator @ insert position
|
||||
* .second: true if insert occurred (ịẹ tree size incremented)
|
||||
*/
|
||||
auto insert_result = p_tree->insert(typename Tree::value_type(x, 10 * x));
|
||||
|
||||
REQUIRE_ORFAIL(ok_flag, catch_flag, p_tree->verify_ok(catch_flag));
|
||||
|
||||
REQUIRE_ORFAIL(ok_flag, catch_flag, insert_result.second);
|
||||
|
||||
/* verify: iterator returned by Treẹinsert(), refers to inserted key,value pair */
|
||||
log && log(xtag("iter.node", insert_result.first.node()));
|
||||
REQUIRE_ORFAIL(ok_flag, catch_flag, insert_result.first->first == x);
|
||||
REQUIRE_ORFAIL(ok_flag, catch_flag, insert_result.first->second == 10 * x);
|
||||
|
||||
++i;
|
||||
}
|
||||
|
||||
REQUIRE_ORFAIL(ok_flag, catch_flag, p_tree->size() == n);
|
||||
|
||||
return ok_flag;
|
||||
} /*random_inserts*/
|
||||
|
||||
/* do n random removes (taken from *p_rgen) from *p_rbtree;
|
||||
* assumes *p_rbtree has keys [0 .. n-1] where n=p_rbtreẹsize
|
||||
*/
|
||||
static bool
|
||||
random_removes(bool catch_flag,
|
||||
xo::rng::xoshiro256ss * p_rgen,
|
||||
Tree * p_tree)
|
||||
{
|
||||
using xo::scope;
|
||||
using xo::xtag;
|
||||
|
||||
bool ok_flag = true;
|
||||
|
||||
xo::scope log(XO_DEBUG(catch_flag));
|
||||
|
||||
REQUIRE_ORFAIL(ok_flag, catch_flag, p_tree->verify_ok(catch_flag));
|
||||
|
||||
uint32_t n = p_tree->size();
|
||||
|
||||
/* random permutation of keys in *p_tree */
|
||||
std::vector<std::uint32_t> u
|
||||
= random_permutation(n, p_rgen);
|
||||
|
||||
log && log(xtag("remove-order", u));
|
||||
|
||||
/* will keep track of which keys remain as we move them */
|
||||
std::map<std::uint32_t, std::uint32_t> m = Util::map_upto(n);
|
||||
|
||||
/* remove keys in permutation order */
|
||||
std::uint32_t i = 1;
|
||||
for (std::uint32_t x : u) {
|
||||
log && log("iter i: removing key from n-node tree",
|
||||
xtag("i", i), xtag("key", x), xtag("n", n));
|
||||
|
||||
/* remove x from tracking map m also */
|
||||
m.erase(x);
|
||||
|
||||
log && log("remove key :iter ", i, "/", n, xtag("key", x));
|
||||
|
||||
p_tree->erase(x);
|
||||
// rbtreẹdisplay();
|
||||
REQUIRE_ORFAIL(ok_flag, catch_flag, p_tree->size() == n-i);
|
||||
/* amongst other things, this guarantees that keys in *p_tree
|
||||
* appear in increasing order
|
||||
*/
|
||||
REQUIRE_ORFAIL(ok_flag, catch_flag, p_tree->verify_ok(catch_flag));
|
||||
|
||||
#ifdef NOT_YET
|
||||
/* 1. rbtree should now contain all the keys in [0..n-1],
|
||||
* with u[0]..u[i-1] excluded; this is the same as the
|
||||
* contents of m.
|
||||
*/
|
||||
auto m_ix = m.begin();
|
||||
auto m_end_ix = m.end();
|
||||
auto visitor_fn =
|
||||
([&m_ix, m_end_ix]
|
||||
(std::pair<int, double> const & contents)
|
||||
{
|
||||
REQUIRE(m_ix != m_end_ix);
|
||||
REQUIRE(contents.first == m_ix->second);
|
||||
++m_ix;
|
||||
});
|
||||
p_tree->visit_inorder(visitor_fn);
|
||||
#endif
|
||||
++i;
|
||||
}
|
||||
|
||||
REQUIRE_ORFAIL(ok_flag, catch_flag, m.empty());
|
||||
REQUIRE_ORFAIL(ok_flag, catch_flag, p_tree->size() == 0);
|
||||
|
||||
log.end_scope();
|
||||
|
||||
return ok_flag;
|
||||
} /*random_removes*/
|
||||
|
||||
/* Require:
|
||||
* - tree has keys [0..n-1], where n=treẹsize()
|
||||
* - for each key k, associated value is 10*k
|
||||
*/
|
||||
static bool
|
||||
random_lookups(bool catch_flag,
|
||||
Tree const & tree,
|
||||
xo::rng::xoshiro256ss * p_rgen)
|
||||
{
|
||||
using xo::scope;
|
||||
using xo::xtag;
|
||||
|
||||
xo::scope log(XO_DEBUG(catch_flag));
|
||||
|
||||
/* -> false if/when verification fails */
|
||||
bool ok_flag = true;
|
||||
|
||||
REQUIRE_ORFAIL(ok_flag, catch_flag, tree.verify_ok(catch_flag));
|
||||
|
||||
size_t n = tree.size();
|
||||
std::vector<std::uint32_t> u
|
||||
= random_permutation(n, p_rgen);
|
||||
|
||||
/* lookup keys in permutation order */
|
||||
std::uint32_t i = 1;
|
||||
for (std::uint32_t x : u) {
|
||||
INFO(tostr(xtag("i", i), xtag("n", n), xtag("x", x)));
|
||||
|
||||
REQUIRE_ORFAIL(ok_flag, catch_flag, tree[x] == x*10);
|
||||
REQUIRE_ORFAIL(ok_flag, catch_flag, tree.verify_ok(catch_flag));
|
||||
REQUIRE_ORFAIL(ok_flag, catch_flag, tree.size() == n);
|
||||
|
||||
/* also test treẹfind() */
|
||||
auto find_ix = tree.find(x);
|
||||
|
||||
REQUIRE_ORFAIL(ok_flag, catch_flag, find_ix != tree.end());
|
||||
REQUIRE_ORFAIL(ok_flag, catch_flag, find_ix->first == x);
|
||||
REQUIRE_ORFAIL(ok_flag, catch_flag, find_ix->second == x*10);
|
||||
|
||||
++i;
|
||||
}
|
||||
|
||||
REQUIRE_ORFAIL(ok_flag, catch_flag, tree.size() == n);
|
||||
|
||||
log.end_scope();
|
||||
|
||||
return ok_flag;
|
||||
} /*random_lookups*/
|
||||
|
||||
/* Require:
|
||||
* - tree has keys [0..n-1], where n=treẹsize()
|
||||
* - tree value at key k is dvalue+10*k
|
||||
*/
|
||||
static bool
|
||||
check_ordinal_lookup(std::uint32_t dvalue,
|
||||
bool catch_flag,
|
||||
Tree const & tree)
|
||||
{
|
||||
using xo::scope;
|
||||
using xo::xtag;
|
||||
|
||||
/* -> false if/when verification fails */
|
||||
bool ok_flag = true;
|
||||
|
||||
xo::scope log(XO_DEBUG(catch_flag));
|
||||
|
||||
std::size_t const n = tree.size();
|
||||
std::size_t i = 0;
|
||||
|
||||
log && log("tree with size n", xtag("n", n));
|
||||
|
||||
for (std::size_t i=0; i<n; ++i) {
|
||||
typename Tree::const_iterator ix;
|
||||
|
||||
try {
|
||||
ix = tree.find_ith(i); /* find_ith() may throw if broken */
|
||||
} catch(...) {
|
||||
if (catch_flag)
|
||||
throw;
|
||||
}
|
||||
|
||||
REQUIRE_ORFAIL(ok_flag, catch_flag, ix.is_dereferenceable());
|
||||
REQUIRE_ORFAIL(ok_flag, catch_flag, (ix != tree.end()));
|
||||
REQUIRE_ORFAIL(ok_flag, catch_flag, (ix->first == i));
|
||||
REQUIRE_ORFAIL(ok_flag, catch_flag, (ix->second == 10*i + dvalue));
|
||||
}
|
||||
|
||||
log.end_scope();
|
||||
|
||||
return ok_flag;
|
||||
} /*check_ordinal_lookup*/
|
||||
|
||||
/* Require:
|
||||
* - tree has keys [0..n-1], where n=treẹsize()
|
||||
* - tree values at key k is dvalue+10*k
|
||||
*
|
||||
* catch_flag. true -> log to console + interact with catch2
|
||||
* false -> verify iteration behavior for return code
|
||||
*/
|
||||
static bool
|
||||
check_bidirectional_iterator(uint32_t dvalue,
|
||||
bool catch_flag,
|
||||
Tree const & tree)
|
||||
{
|
||||
using xo::scope;
|
||||
using xo::xtag;
|
||||
|
||||
/* -> false if/when verification fails */
|
||||
bool ok_flag = true;
|
||||
|
||||
std::size_t const n = tree.size();
|
||||
|
||||
xo::scope log(XO_DEBUG(catch_flag));
|
||||
|
||||
log && log("tree with size n", xtag("n", n));
|
||||
|
||||
{
|
||||
std::size_t i = 0;
|
||||
|
||||
auto end_ix = tree.end();
|
||||
|
||||
log && log(xtag("end_ix", end_ix));
|
||||
|
||||
auto begin_ix = tree.begin();
|
||||
auto ix = begin_ix;
|
||||
|
||||
int last_key = -1;
|
||||
|
||||
while (ix != end_ix) {
|
||||
log && log("forward loop top",
|
||||
xtag("i", i),
|
||||
xtag("ix", ix));
|
||||
|
||||
REQUIRE_ORFAIL(ok_flag, catch_flag, ix->first == i);
|
||||
REQUIRE_ORFAIL(ok_flag, catch_flag, ix->second == dvalue + 10*i);
|
||||
if(i > 0) {
|
||||
REQUIRE_ORFAIL(ok_flag, catch_flag, ix->first > last_key);
|
||||
}
|
||||
last_key = ix->first;
|
||||
++i;
|
||||
++ix;
|
||||
|
||||
log && log("forward loop bottom",
|
||||
xtag("last_key", last_key),
|
||||
xtag("next ix", ix));
|
||||
}
|
||||
|
||||
/* should have visited exactly n locations */
|
||||
REQUIRE_ORFAIL(ok_flag, catch_flag, i == n);
|
||||
REQUIRE_ORFAIL(ok_flag, catch_flag, ix == end_ix);
|
||||
|
||||
log && log(xtag("ix", ix), xtag("begin_ix", begin_ix));
|
||||
|
||||
/* now run iterator backwards,
|
||||
* starting from "one past the end"
|
||||
*/
|
||||
if(ix != begin_ix) {
|
||||
do {
|
||||
--i;
|
||||
--ix;
|
||||
|
||||
log && log("forward backup",
|
||||
xtag("i", i),
|
||||
xtag("ix", ix));
|
||||
|
||||
REQUIRE_ORFAIL(ok_flag, catch_flag, ix.is_dereferenceable());
|
||||
|
||||
log && log(xtag("ix.first", (*ix).first));
|
||||
|
||||
REQUIRE_ORFAIL(ok_flag, catch_flag, (*ix).first == i);
|
||||
} while (ix != begin_ix);
|
||||
}
|
||||
|
||||
/* should have visited exactly n locations in reverse */
|
||||
REQUIRE_ORFAIL(ok_flag, catch_flag, i == 0);
|
||||
}
|
||||
|
||||
/* ----- reverse iterators ----- */
|
||||
|
||||
{
|
||||
std::int64_t i = n - 1;
|
||||
|
||||
auto rbegin_ix = tree.rbegin();
|
||||
auto rend_ix = tree.rend();
|
||||
|
||||
auto rix = rbegin_ix;
|
||||
|
||||
int last_key = -1;
|
||||
|
||||
while (rix != rend_ix) {
|
||||
log && log("reverse loop top",
|
||||
xtag("i", i),
|
||||
xtag("rix", rix));
|
||||
|
||||
REQUIRE_ORFAIL(ok_flag, catch_flag, rix->first == i);
|
||||
REQUIRE_ORFAIL(ok_flag, catch_flag, rix->second == dvalue + 10*i);
|
||||
if (i < n-1) {
|
||||
REQUIRE_ORFAIL(ok_flag, catch_flag, rix->first < last_key);
|
||||
}
|
||||
last_key = rix->first;
|
||||
--i;
|
||||
++rix;
|
||||
|
||||
log && log("reverse loop bottom",
|
||||
xtag("last_key", last_key),
|
||||
xtag("next ix", rix));
|
||||
}
|
||||
|
||||
/* should have visited exactly n locations */
|
||||
REQUIRE_ORFAIL(ok_flag, catch_flag, i == -1);
|
||||
|
||||
log && log(xtag("rbegin_ix", rbegin_ix));
|
||||
|
||||
/* now run reverse iterator backwrds,
|
||||
* starting from "one before the beginning"
|
||||
*/
|
||||
if (rix != rbegin_ix) {
|
||||
do {
|
||||
++i;
|
||||
--rix;
|
||||
|
||||
log && log("reverse backup",
|
||||
xtag("i", i),
|
||||
xtag("rix", rix),
|
||||
xtag("rix.first", rix->first));
|
||||
|
||||
REQUIRE_ORFAIL(ok_flag, catch_flag, (*rix).first == i);
|
||||
} while (rix != rbegin_ix);
|
||||
}
|
||||
|
||||
/* should have visited exactly n locations in reversê2 */
|
||||
REQUIRE_ORFAIL(ok_flag, catch_flag, i == n - 1);
|
||||
}
|
||||
|
||||
log.end_scope();
|
||||
|
||||
return ok_flag;
|
||||
} /*check_bidirectional_iterator*/
|
||||
}; /*TreeUtil*/
|
||||
} /*namespace utest*/
|
||||
|
||||
/* end random_tree_ops.hpp */
|
||||
248
xo-ordinaltree/utest/redblacktree.cpp
Normal file
248
xo-ordinaltree/utest/redblacktree.cpp
Normal file
|
|
@ -0,0 +1,248 @@
|
|||
/* @file redblacktree.cpp */
|
||||
|
||||
#include "random_tree_ops.hpp"
|
||||
#include "xo/ordinaltree/RedBlackTree.hpp"
|
||||
#include <map>
|
||||
|
||||
namespace {
|
||||
using xo::tree::RedBlackTree;
|
||||
using xo::tree::SumReduce;
|
||||
using xo::tree::OrdinalReduce;
|
||||
using xo::tree::NullReduce;
|
||||
using xo::rng::xoshiro256ss;
|
||||
|
||||
using utest::Util;
|
||||
using utest::TreeUtil;
|
||||
|
||||
using xo::scope;
|
||||
using xo::scope_setup;
|
||||
using xo::xtag;
|
||||
|
||||
//using RbTree = RedBlackTree<int, double, OrdinalReduce<double>>;
|
||||
using RbTree = RedBlackTree<int, double, SumReduce<double>>;
|
||||
|
||||
#ifdef OBSOLETE
|
||||
/* Require:
|
||||
* - rbtree has keys [0..n-1] where n=rbtree.size(),
|
||||
* - rbtree value at key k is dvalue+10*k
|
||||
*/
|
||||
void
|
||||
check_ordinal_lookup(uint32_t dvalue,
|
||||
RbTree const & rbtree)
|
||||
{
|
||||
size_t const n = rbtree.size();
|
||||
size_t i = 0;
|
||||
|
||||
for(size_t i=0; i<n; ++i) {
|
||||
RbTree::const_iterator ix = rbtree.find_ith(i);
|
||||
|
||||
REQUIRE(ix != rbtree.end());
|
||||
REQUIRE(ix->first == i);
|
||||
}
|
||||
} /*check_ordinal_lookup*/
|
||||
#endif
|
||||
|
||||
/* check that RedBlackTree<>::find_sum_glb() works as advertised.
|
||||
*
|
||||
* partial sums of v[j] for j<=i will be:
|
||||
*
|
||||
* (i+1) . i
|
||||
* 10 . --------- + ((i+1) . dvalue)
|
||||
* 2
|
||||
*
|
||||
* = (i+1).(5.i + dvalue)
|
||||
*
|
||||
* Require:
|
||||
* - rbtree has keys [0..n-1], where n=rbtree.size()
|
||||
* - rbtree value at key k is dvalue+10*k
|
||||
*/
|
||||
void
|
||||
check_reduced_sum(uint32_t dvalue,
|
||||
RbTree const & rbtree)
|
||||
{
|
||||
size_t const n = rbtree.size();
|
||||
|
||||
for(size_t i = 0; i < n; ++i) {
|
||||
/* compute reduction up to key=i */
|
||||
double reduced_upto
|
||||
= rbtree.reduce_lub(i /*key*/,
|
||||
true /*is_closed*/);
|
||||
|
||||
double reduced = (i+1) * (5*i + dvalue);
|
||||
|
||||
INFO(tostr(xtag("i", i), xtag("n", n),
|
||||
xtag("tree.reduced_upto", reduced_upto),
|
||||
xtag("reduced", reduced),
|
||||
xtag("dvalue", dvalue)));
|
||||
|
||||
auto glb_ix = rbtree.cfind_sum_glb(reduced);
|
||||
|
||||
REQUIRE(reduced_upto == reduced);
|
||||
|
||||
REQUIRE(glb_ix.is_dereferenceable());
|
||||
/* glb_ix is truth-y */
|
||||
REQUIRE(glb_ix);
|
||||
|
||||
REQUIRE(glb_ix->first == i);
|
||||
}
|
||||
} /*check_reduced_sum*/
|
||||
|
||||
#ifdef OBSOLETE
|
||||
/* Require:
|
||||
* - *p_rbtree has keys [0..n-1], where n=rbtree.size()
|
||||
* - for each key k, associated value is 10*k
|
||||
*/
|
||||
void
|
||||
random_lookups(RbTree const & rbtree,
|
||||
xoshiro256ss * p_rgen)
|
||||
{
|
||||
REQUIRE(rbtree.verify_ok());
|
||||
|
||||
size_t n = rbtree.size();
|
||||
std::vector<uint32_t> u
|
||||
= Util::random_permutation(n, p_rgen);
|
||||
|
||||
/* lookup keys in permutation order */
|
||||
uint32_t i = 1;
|
||||
for (uint32_t x : u) {
|
||||
INFO(tostr(xtag("i", i), xtag("n", n), xtag("x", x)));
|
||||
|
||||
REQUIRE(rbtree[x] == x*10);
|
||||
REQUIRE(rbtree.verify_ok());
|
||||
REQUIRE(rbtree.size() == n);
|
||||
++i;
|
||||
}
|
||||
|
||||
REQUIRE(rbtree.size() == n);
|
||||
} /*random_lookups*/
|
||||
#endif
|
||||
|
||||
/* Require:
|
||||
* - *p_rbtree has keys [0..n-1], where n=rbtree.size()
|
||||
* - for each key k, associated value is 10*k
|
||||
*
|
||||
* Promise:
|
||||
* - for each key k, associated value is dvalue + 10*k
|
||||
*/
|
||||
void
|
||||
random_updates(uint32_t dvalue,
|
||||
RbTree * p_rbtree,
|
||||
xoshiro256ss * p_rgen)
|
||||
{
|
||||
REQUIRE(p_rbtree->verify_ok());
|
||||
|
||||
std::size_t n = p_rbtree->size();
|
||||
std::vector<uint32_t> u
|
||||
= Util::random_permutation(n, p_rgen);
|
||||
|
||||
/* update key/value pairs in permutation order */
|
||||
uint32_t i = 1;
|
||||
for (uint32_t x : u) {
|
||||
REQUIRE((*p_rbtree)[x] == x*10);
|
||||
|
||||
(*p_rbtree)[x] = dvalue + 10*x;
|
||||
|
||||
REQUIRE((*p_rbtree)[x] == dvalue + 10*x);
|
||||
REQUIRE(p_rbtree->verify_ok());
|
||||
/* assignment to existing key does not change tree size */
|
||||
REQUIRE(p_rbtree->size() == n);
|
||||
++i;
|
||||
}
|
||||
|
||||
REQUIRE(p_rbtree->size() == n);
|
||||
} /*random_updates_1*/
|
||||
|
||||
TEST_CASE("rbtree", "[redblacktree]") {
|
||||
RbTree rbtree;
|
||||
|
||||
std::uint64_t seed = 14950349842636922572UL;
|
||||
/* can reseed from /dev/urandom with: */
|
||||
//arc4random_buf(&seed, sizeof(seed));
|
||||
|
||||
auto rgen = xo::rng::xoshiro256ss(seed);
|
||||
|
||||
/* perform a series of tests with increasing scale */
|
||||
for(std::uint32_t n=0; n<=1024; ) {
|
||||
bool ok_flag = false;
|
||||
|
||||
for (std::uint32_t attention = 0; !ok_flag && (attention < 2); ++attention) {
|
||||
/* attention=0:
|
||||
* - no logging
|
||||
* - detect assertion failures, but don't report them to catch2
|
||||
* attention=1:
|
||||
* - only runs if failure detected with attention=0
|
||||
* - full logging
|
||||
* - report to catch
|
||||
*/
|
||||
|
||||
bool debug_flag = (attention == 1);
|
||||
|
||||
scope log(XO_DEBUG2(debug_flag, "rbtree"));
|
||||
log && log(xtag("size", n));
|
||||
|
||||
ok_flag = true;
|
||||
|
||||
if (n == 0) {
|
||||
/* check iteration on empty tree */
|
||||
ok_flag &= TreeUtil<RbTree>::check_bidirectional_iterator(0 /*dvalue - not used*/,
|
||||
debug_flag,
|
||||
rbtree);
|
||||
} else {
|
||||
/* insert [0..n-1] in random order */
|
||||
ok_flag &= TreeUtil<RbTree>::random_inserts(n, debug_flag, &rgen, &rbtree);
|
||||
|
||||
/* TODO: generalize remaining helpers; share with bplustree unit test */
|
||||
|
||||
/* check iterator traverses [0..n-1] in both directions (using ++ and --) */
|
||||
ok_flag &= TreeUtil<RbTree>::check_ordinal_lookup(0 /*dvalue*/,
|
||||
debug_flag,
|
||||
rbtree);
|
||||
/* verify end-to-end iteration */
|
||||
ok_flag &= TreeUtil<RbTree>::check_bidirectional_iterator(0,
|
||||
debug_flag,
|
||||
rbtree);
|
||||
/* verify behavior of .reduce_lub(), .find_sum_glb() */
|
||||
check_reduced_sum(0, rbtree);
|
||||
/* verify behavior of read-only variant of operator[] */
|
||||
ok_flag &= TreeUtil<RbTree>::random_lookups(debug_flag,
|
||||
rbtree,
|
||||
&rgen);
|
||||
|
||||
/* verify that lookups didn't somehow disturb tree contents */
|
||||
ok_flag &= TreeUtil<RbTree>::check_ordinal_lookup(0 /*dvalue*/,
|
||||
debug_flag,
|
||||
rbtree);
|
||||
|
||||
ok_flag &= TreeUtil<RbTree>::check_bidirectional_iterator(0,
|
||||
debug_flag,
|
||||
rbtree);
|
||||
/* verify update via read/write operator[] */
|
||||
random_updates(10000, &rbtree, &rgen);
|
||||
|
||||
/* verify that updates changed tree contents in expected way */
|
||||
ok_flag &= TreeUtil<RbTree>::check_ordinal_lookup(10000 /*dvalue*/,
|
||||
debug_flag,
|
||||
rbtree);
|
||||
|
||||
/* verify end-to-end iteration */
|
||||
ok_flag &= TreeUtil<RbTree>::check_bidirectional_iterator(10000,
|
||||
debug_flag,
|
||||
rbtree);
|
||||
/* verify behavior of .reduce_lub(), .find_sum_glb() */
|
||||
check_reduced_sum(10000, rbtree);
|
||||
/* verify behavior of read/write variant of operator[] */
|
||||
ok_flag &= TreeUtil<RbTree>::random_removes(debug_flag, &rgen, &rbtree);
|
||||
}
|
||||
|
||||
log.end_scope();
|
||||
}
|
||||
|
||||
if (n == 0)
|
||||
n = 1;
|
||||
else
|
||||
n = 2*n;
|
||||
}
|
||||
} /*TEST_CASE(rbtree)*/
|
||||
} /*namespace*/
|
||||
|
||||
/* end redblacktree.cpp */
|
||||
7
xo-ordinaltree/utest/tree_utest_main.cpp
Normal file
7
xo-ordinaltree/utest/tree_utest_main.cpp
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
/* @file tree_utest_main.cpp */
|
||||
|
||||
#define CATCH_CONFIG_MAIN
|
||||
#define CATCH_CONFIG_ENABLE_BENCHMARKING
|
||||
#include "catch2/catch.hpp"
|
||||
|
||||
/* end tree_utest_main.cpp */
|
||||
Loading…
Add table
Add a link
Reference in a new issue