diff --git a/CMakeLists.txt b/CMakeLists.txt index 3f97b122..2011702a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -77,12 +77,13 @@ set(DOX_EXCLUDE_PATTERNS [=[ add_subdirectory(xo-cmake) add_subdirectory(xo-indentlog) add_subdirectory(xo-reflectutil) # header-only reflect support +add_subdirectory(xo-randomgen) # xoshiro256ss add_subdirectory(xo-arena) # arena allocator (DArena) add_subdirectory(xo-facet) # sep iface,data add_subdirectory(xo-allocutil) add_subdirectory(xo-refcnt) add_subdirectory(xo-subsys) -add_subdirectory(xo-randomgen) +#add_subdirectory(xo-randomgen) add_subdirectory(xo-flatstring) add_subdirectory(xo-pyutil) add_subdirectory(xo-reflect) diff --git a/xo-arena/include/xo/arena/DArenaHashMap.hpp b/xo-arena/include/xo/arena/DArenaHashMap.hpp index f021236a..b1659e67 100644 --- a/xo-arena/include/xo/arena/DArenaHashMap.hpp +++ b/xo-arena/include/xo/arena/DArenaHashMap.hpp @@ -6,10 +6,22 @@ #pragma once #include "DArenaVector.hpp" +#include +#include #include +#include namespace xo { namespace mm { +#ifdef NOT_YET + enum class insert_error : int32_t { + /** sentinel **/ + invalid = -1, + /** not an error **/ + ok, + }; +#endif + struct DArenaHashMapUtil { using size_type = std::size_t; using control_type = std::uint8_t; @@ -22,6 +34,9 @@ namespace xo { /** group size **/ static constexpr size_type c_group_size = 16; + /** max load factor **/ + static constexpr float c_max_load_factor = 0.875; + /** find smallest multiple k : k * c_group_size >= n **/ static size_type lub_group_mult(size_t n) { return (n + c_group_size - 1) / c_group_size; @@ -50,7 +65,7 @@ namespace xo { std::array ctrl_; explicit Group(uint8_t * lo) { - std::memcpy(ctrl_.data(), lo, DArenaHashMapUtil::c_group_size); + ::memcpy(ctrl_.data(), lo, DArenaHashMapUtil::c_group_size); } /** find all exact matches in ctrl_[0..15] for @p h2. @@ -140,18 +155,26 @@ namespace xo { size_type size() const noexcept { return size_; } size_type capacity() const noexcept { return n_group_ * c_group_size; } + float load_factor() const noexcept { return size_ / static_cast(n_slot_); } + /** insert @p kv_pair into hash map. replaces any previous value * stored under the same key. * - * Return true if size incremented; false if value updated - * for existing key + * Return pair retval with: + * reval.first: true if size incremented; + * retval.second: address of slots_[p] at which pair inserted/updated + * + * When table is full retval.second will be nullptr, + * with error captured in last_error_ **/ - bool insert(const std::pair & kv_pair); + std::pair try_insert(const std::pair & kv_pair); + + bool verify_ok(bool /*throw_flag_not_implemented*/ = true) const; private: /** load group abstraction from control bytes starting at @p ix **/ - group_type _load_group(size_type ix) const { - return group_type(&control_[ix]); + group_type _load_group(size_type ix) { + return group_type(&(control_[ix])); } /** like ctrl_[ix] = h2, but maintain overflow copy @@ -165,15 +188,15 @@ namespace xo { /** key equal **/ key_equal equal_; /** number of pairs in this table **/ - std::size_t size_ = 0; + size_type size_ = 0; /** base-2 logarithm of n_group_ **/ - std::size_t n_group_exponent_ = 0; + size_type n_group_exponent_ = 0; /** table has capacity for this number of groups. always an exact power of two. * number of slots is n_group_ * c_group_size **/ - std::size_t n_group_ = 1 << n_group_exponent_; + size_type n_group_ = (1 << n_group_exponent_); /** table has capacity for this number of {key,value} pairs **/ - std::size_t n_slot_ = n_group_ * c_group_size; + size_type n_slot_ = n_group_ * c_group_size; /** control_[] partitioned into groups of c_group_size (16) consecutive elements **/ DArenaVector control_; @@ -209,8 +232,14 @@ namespace xo { slots_{DArenaVector::map(ArenaConfig{.size_ = n_slot_ * sizeof(value_type)})}, debug_flag_{debug_flag} { - } + /* invariant: arenas have allocated address range, but no committed memory yet */ + this->control_.resize(n_slot_ + c_group_size); + /* all slots marked empty initially */ + std::fill(this->control_.begin(), this->control_.end(), c_empty_slot); + + this->slots_.resize(n_slot_); + } template void @@ -227,8 +256,8 @@ namespace xo { } template - bool - DArenaHashMap::insert(const std::pair & kv_pair) + auto + DArenaHashMap::try_insert(const std::pair & kv_pair) -> std::pair { size_type h = hash_(kv_pair.first); // h1: hi bits: probe sequence @@ -269,7 +298,7 @@ namespace xo { slot.second = kv_pair.second; // false: did not change table size - return false; + return std::make_pair(&slot, false); } // e.g: @@ -289,6 +318,14 @@ namespace xo { // process each empty slot if (e) { + // check that table is below max load factor (0.875). + // Check here so that table can stay at max load factor + // indefinitely as long as updates only + // + if (load_factor() >= c_max_load_factor) { + return std::make_pair(nullptr, false); + } + // zeroes: #of 0 before least significant 1 bit int skip = __builtin_ctz(e); size_type slot_ix = (ix + skip) & (N - 1); @@ -299,13 +336,13 @@ namespace xo { // mark slot occupied in control space; // maintain copy-at-end for overflow - this->update_control(slot_ix, h2); + this->_update_control(slot_ix, h2); new (&slot) value_type(kv_pair); ++(this->size_); // true: increased table size - return true; + return std::make_pair(&slot, true); } } @@ -319,6 +356,36 @@ namespace xo { ix = (ix + c_group_size) & (N - 1); } } + + /** + * Verify DArenaHashMap class invariants. + * + * SM1. size consistency + * - SM1.1 size_ <= n_slot_ + * - SM1.2 control_[] size consistent with slots_[] size + * - SM1.3 n_group_ consistent with n_group_exponent_ + * - SM1.4 n_slot_ consistent with n_group_ + * - SM1.5 n_slot_ a power of 2 + * SM2. load factor + * - SM2.1 load_factor() <= c_max_load_factor + * SM3. control_ + * - SM3.1 control_[N+i] = control_[i] for i in [0, c_group_size) + * - SM3.2 {number of control_[i] spots with non-sentinel values} = size_ + * SM4. slots_ + * - SM4.1 if control_[i] is non-sentinel: + * - SM4.1.1 control_[i] = hash_(slots_[i].first) & 0x7f + * - SM4.1.2 all slots in range [h .. i] are non-empty, + * where h is hash_(slots_[i].first >> 7 + * - SM4.2 if control_[i] is empty or tombstone: + * - slots_[i].first = key_type() + * + **/ + template + bool + DArenaHashMap::verify_ok(bool /*throw_flag_not_implemented*/) const + { + return true; + } } } /*namespace xo*/ diff --git a/xo-arena/include/xo/arena/DArenaVector.hpp b/xo-arena/include/xo/arena/DArenaVector.hpp index 5b2e38f1..d1d66343 100644 --- a/xo-arena/include/xo/arena/DArenaVector.hpp +++ b/xo-arena/include/xo/arena/DArenaVector.hpp @@ -7,6 +7,7 @@ #include "DArena.hpp" #include +#include // for ::memset() namespace xo { namespace mm { @@ -153,8 +154,7 @@ namespace xo { // run ctors if constexpr (std::is_trivially_constructible_v) { - // nothing to do - ; + ::memset(this->_address_of(size_), 0, req_z - (size_ * sizeof(T))); } else { for (size_type i = size_; i < z; ++i) { void * addr = &(*this)[i]; diff --git a/xo-arena/utest/CMakeLists.txt b/xo-arena/utest/CMakeLists.txt index 5b45103f..c45a9710 100644 --- a/xo-arena/utest/CMakeLists.txt +++ b/xo-arena/utest/CMakeLists.txt @@ -17,7 +17,7 @@ set(UTEST_SRCS if (ENABLE_TESTING) xo_add_utest_executable(${UTEST_EXE} ${UTEST_SRCS}) xo_self_dependency(${UTEST_EXE} xo_arena) -# xo_headeronly_dependency(${UTEST_EXE} randomgen) + xo_headeronly_dependency(${UTEST_EXE} randomgen) # xo_headeronly_dependency(${UTEST_EXE} indentlog) xo_external_target_dependency(${UTEST_EXE} Catch2 Catch2::Catch2) endif() diff --git a/xo-arena/utest/DArenaHashMap.test.cpp b/xo-arena/utest/DArenaHashMap.test.cpp index c7ebc4e9..3f4a24fd 100644 --- a/xo-arena/utest/DArenaHashMap.test.cpp +++ b/xo-arena/utest/DArenaHashMap.test.cpp @@ -4,12 +4,19 @@ **/ #include "DArenaHashMap.hpp" +#include "random_hash_ops.hpp" +#include +#include +#include #include namespace xo { using xo::mm::DArenaHashMapUtil; using xo::mm::DArenaHashMap; - //using xo::mM::ArenaConfig; + using xo::rng::random_seed; + using xo::rng::xoshiro256ss; + using utest::UtestTools; + using utest::HashMapUtil; namespace ut { TEST_CASE("DArenaHashMap-ctor", "[arena][DArenaHashMap]") @@ -36,6 +43,98 @@ namespace xo { REQUIRE(map.capacity() == std::max(512ul, DArenaHashMapUtil::c_group_size)); } + + TEST_CASE("DArenaHashMap-try-insert", "[arena][DArenaHashMap]") + { + using HashMap = DArenaHashMap; + + HashMap map; + + REQUIRE(map.empty()); + REQUIRE(map.size() == 0); + REQUIRE(map.groups() == 1); + REQUIRE(map.capacity() == DArenaHashMapUtil::c_group_size); + + { + auto x = map.try_insert(std::make_pair(1, 11)); + + REQUIRE(x.first); + REQUIRE(x.second); + REQUIRE(!map.empty()); + REQUIRE(map.size() == 1); + REQUIRE(map.groups() == 1); + REQUIRE(map.capacity() == DArenaHashMapUtil::c_group_size); + REQUIRE(map.load_factor() == 1/16.0); + } + + { + auto x = map.try_insert(std::make_pair(2, 9)); + + REQUIRE(x.first); + REQUIRE(x.second); + REQUIRE(!map.empty()); + REQUIRE(map.size() == 2); + REQUIRE(map.groups() == 1); + REQUIRE(map.capacity() == DArenaHashMapUtil::c_group_size); + REQUIRE(map.load_factor() == 2/16.0); + } + + { + auto x = map.try_insert(std::make_pair(259, 12)); + + REQUIRE(x.first); + REQUIRE(x.second); + REQUIRE(!map.empty()); + REQUIRE(map.size() == 3); + REQUIRE(map.groups() == 1); + REQUIRE(map.capacity() == DArenaHashMapUtil::c_group_size); + REQUIRE(map.load_factor() == 3/16.0); + } + } + + TEST_CASE("DArenaHashMap-try-insert2", "[arena][DArenaHashMap]") + { + using HashMap = DArenaHashMap; + + std::uint64_t seed = 17747889312058974961ul; + //random_seed(&seed); // to get new random seed + //log && log(xtag("seed", seed)); + + auto rgen = xoshiro256ss(seed); + + /* 1. Perform series of tests with increasing scale + * 2. Each test may run in two modes: + * a. silent fast fail. just report success. + * In this mode avoid catch2 REQUIRE + * b. noisy. run with logging enabled + * This mode automatically invoked when silent mode + * observes test failure + */ + + for (std::uint32_t n = 0; n <= 2; ) { + HashMap hash_map; + + auto test_fn = [&rgen, &hash_map](bool dbg_flag, + std::uint32_t n) + { + bool ok_flag = true; + + ok_flag &= HashMapUtil::random_inserts(n, dbg_flag, &rgen, &hash_map); + + return ok_flag; + }; + + bool ok_flag = UtestTools::bimodal_test("DArenaHashMap-try-insert2", test_fn, n); + + if (n == 0) + n = 1; + else + n = 2*n; + } + } + + // TODO: + // - let's try getting lcov to work in xo-umbrella2 } } diff --git a/xo-arena/utest/random_hash_ops.hpp b/xo-arena/utest/random_hash_ops.hpp new file mode 100644 index 00000000..6216a457 --- /dev/null +++ b/xo-arena/utest/random_hash_ops.hpp @@ -0,0 +1,631 @@ +/* @file random_hash_ops.hpp **/ + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace utest { + struct Util { + /* generate vector with integers [0.. n-1] */ + static std::vector vector_upto(std::uint32_t n) { + std::vector u(n); + for (std::uint32_t i = 0; i < n; ++i) + u[i] = i; + + return u; + } /*vector_upto*/ + + static std::map + map_upto(std::uint32_t n) + { + std::map m; + for(std::uint32_t i=0; i + random_permutation(uint32_t n, xo::rng::xoshiro256ss *p_rgen) { + /* vector [0 .. n-1] */ + std::vector u = vector_upto(n); + + /* shuffle to get unpredictable permutation */ + std::shuffle(u.begin(), u.end(), *p_rgen); + + return u; + } /*random_permutation*/ + }; /*Util*/ + + // TODO: move REQUIRE_OR_CAPTURE(), REQUIRE_ORFAIL() to new subsystem xo-utestutil + +/* 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 + + /** UtestTools + **/ + struct UtestTools { + /** bimodal may run twice: + * - first mode is silent, only determines success or failure. + * - second mode skipped when first mode succeeds. + * when first mode fails, second mode runs noisily with debug logging enabled + * + * goal is to get detailed information from failing test; + * more detailed than feasible from catch2 INFO() + * + * test function should use REQUIRE_ORCAPTURE() / REQUIRE_ORFAIL(). + * It should *not* use REQUIRE() or CHECK(). + **/ + static inline bool bimodal_test(std::string test_name, + std::function test_fn, + std::uint32_t n) + { + bool ok_flag = false; + + for (std::uint32_t attention = 0; !ok_flag && (attention < 2); ++attention) { + bool debug_flag = (attention == 1); + + xo::scope log(XO_DEBUG2(debug_flag, test_name)); + + ok_flag = test_fn(debug_flag, n); + } + + return ok_flag; + } + }; + + /* compare xo-ordinaltree/utest/random_tree_ops.hpp */ + template + struct HashMapUtil : public Util { +#ifdef NOT_YET + static bool + test_clear(bool catch_flag, + Tree * p_tree) + { + bool ok_flag = true; + + REQUIRE_ORFAIL(ok_flag, catch_flag, p_tree->verify_ok(catch_flag)); + + 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*/ +#endif + + static bool + random_inserts(const std::vector & keys, + bool catch_flag, + xo::rng::xoshiro256ss * p_rgen, + HashMap * p_map) + { + using xo::xtag; + + bool ok_flag = true; + + xo::scope log(XO_DEBUG(catch_flag), xtag("n-keys", keys.size())); + + REQUIRE_ORFAIL(ok_flag, catch_flag, p_map->verify_ok(catch_flag)); + + /* n keys */ + std::size_t n = keys.size(); + /* permute keys, remembering original position */ + std::vector> permuted_keys(n); + { + uint32_t i = 0; + for (const auto & x : keys) { + permuted_keys[i] = std::make_pair(i, x); + } + } + /* shuffle to get unpredictable insert order */ + std::shuffle(keys.begin(), keys.end(), *p_rgen); + + size_t tree_z0 = p_map->size(); + + /* insert keys in permuted order */ + { + uint32_t i = 1; + for(const auto & pr_i : permuted_keys) { + log && log(xtag("i", i), xtag("ord", pr_i.first), xtag("n", n), xtag("key", pr_i.second)); + + /* .first: iterator @ insert position + * .second: true if insert occurred (ịẹ tree size incremented) + */ + auto insert_result = p_map->insert(typename HashMap::value_type(pr_i.second, 10.0 * i)); + + REQUIRE_ORFAIL(ok_flag, catch_flag, p_map->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 == pr_i.second); + REQUIRE_ORFAIL(ok_flag, catch_flag, insert_result.first->second == 10.0 * i); + + ++i; + } + } + + REQUIRE_ORFAIL(ok_flag, catch_flag, p_map->size() == tree_z0 + n); + + return ok_flag; + } + + /* do + * n = (hi - lo) / k + * random inserts (taken from *p_rgen) into *p_rbtreẹ + * inserted keys will comprise the distinct values + * {lo, lo+k, lo+2k, ..., lo+n.k} + */ + static bool + random_inserts(std::uint32_t lo, + std::uint32_t hi, + std::uint32_t k, + bool catch_flag, + xo::rng::xoshiro256ss * p_rgen, + HashMap * p_map) + { + // TODO: rewrite in terms of 'random_inserts with explicit vector'. + + using xo::xtag; + + bool ok_flag = true; + + xo::scope log(XO_DEBUG(catch_flag), xtag("lo", lo), xtag("hi", hi), xtag("k", k)); + + REQUIRE_ORFAIL(ok_flag, catch_flag, p_map->verify_ok(catch_flag)); + + if ((hi <= lo) || (k == 0)) + return true; + + uint32_t n = (hi - lo) / k; + + /* n keys 0..n-1 */ + std::vector u(n); + for(std::uint32_t i=0; isize(); + + /* 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_map->try_insert(typename HashMap::value_type(x, 10 * x)); + + REQUIRE_ORFAIL(ok_flag, catch_flag, p_map->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_map->size() == tree_z0 + n); + + return ok_flag; + } /*random_inserts*/ + + static bool + random_inserts(std::uint32_t n, + bool catch_flag, + xo::rng::xoshiro256ss * p_rgen, + HashMap * p_map) + { + return random_inserts(0, n, 1, catch_flag, p_rgen, p_map); + } + +#ifdef NOT_YET + /* 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, // dbg_flag + xo::rng::xoshiro256ss * p_rgen, + Tree * p_map) + { + using xo::scope; + using xo::xtag; + + bool ok_flag = true; + + xo::scope log(XO_DEBUG(catch_flag)); + + REQUIRE_ORFAIL(ok_flag, catch_flag, p_map->verify_ok(catch_flag)); + + uint32_t n = p_map->size(); + + /* random permutation of keys in *p_map */ + std::vector 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 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_map->erase(x); + // rbtreẹdisplay(); + REQUIRE_ORFAIL(ok_flag, catch_flag, p_map->size() == n-i); + /* amongst other things, this guarantees that keys in *p_map + * appear in increasing order + */ + REQUIRE_ORFAIL(ok_flag, catch_flag, p_map->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 const & contents) + { + REQUIRE(m_ix != m_end_ix); + REQUIRE(contents.first == m_ix->second); + ++m_ix; + }); + p_map->visit_inorder(visitor_fn); +#endif + ++i; + } + + REQUIRE_ORFAIL(ok_flag, catch_flag, m.empty()); + REQUIRE_ORFAIL(ok_flag, catch_flag, p_map->size() == 0); + + log.end_scope(); + + return ok_flag; + } /*random_removes*/ +#endif + +#ifdef NOT_YET + /* 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 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*/ +#endif + +#ifdef NOT_YET + /* 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*/ +#endif + +#ifdef NOT_YET + /** Require: + * - tree has keys [0..n-1], where n=treesize() + * - tree valu at key k is dvalue+10*k + * + * @p catch_flag. control behavior at each test assertion. + * true -> log to console + interact with catch2 + * false -> verify iteration behavior for return code. + * + **/ + static bool + check_reduced_sum(uint32_t dvalue, + bool catch_flag, + Tree const & rbtree) + { + using xo::scope; + using xo::xtag; + + scope log(XO_DEBUG(catch_flag)); + + /* -> false if/when check fails */ + bool ok_flag = true; + + 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_ORFAIL(ok_flag, catch_flag, reduced_upto == reduced); + REQUIRE_ORFAIL(ok_flag, catch_flag, glb_ix.is_dereferenceable()); + /* glb_ix is truth-y */ + REQUIRE_ORFAIL(ok_flag, catch_flag, glb_ix); + + REQUIRE_ORFAIL(ok_flag, catch_flag, glb_ix->first == i); + } + + return ok_flag; + } /*check_reduced_sum*/ +#endif + +#ifdef NOT_YET + /* 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 + */ + static bool + random_updates(uint32_t dvalue, + bool catch_flag, + Tree * p_rbtree, + xo::rng::xoshiro256ss * p_rgen) + { + using xo::scope; + using xo::xtag; + + scope log(XO_DEBUG(catch_flag)); + + /* -> false if/when check fails */ + bool ok_flag = true; + + REQUIRE_ORFAIL(ok_flag, catch_flag, p_rbtree->verify_ok()); + + std::size_t n = p_rbtree->size(); + std::vector u + = Util::random_permutation(n, p_rgen); + + /* update key/value pairs in permutation order */ + uint32_t i = 1; + for (uint32_t x : u) { + REQUIRE_ORFAIL(ok_flag, catch_flag, (*p_rbtree)[x] == x*10); + + (*p_rbtree)[x] = dvalue + 10*x; + + REQUIRE_ORFAIL(ok_flag, catch_flag, (*p_rbtree)[x] == dvalue + 10*x); + REQUIRE_ORFAIL(ok_flag, catch_flag, p_rbtree->verify_ok()); + /* assignment to existing key does not change tree size */ + REQUIRE_ORFAIL(ok_flag, catch_flag, p_rbtree->size() == n); + ++i; + } + + REQUIRE(p_rbtree->size() == n); + + return ok_flag; + } /*random_updates*/ +#endif + }; /*TreeUtil*/ +} /*namespace utest*/ + +/* end random_tree_ops.hpp */ diff --git a/xo-ordinaltree/utest/bplustree.cpp b/xo-ordinaltree/utest/bplustree.cpp index 825f33b4..a6066285 100644 --- a/xo-ordinaltree/utest/bplustree.cpp +++ b/xo-ordinaltree/utest/bplustree.cpp @@ -3,11 +3,11 @@ #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" +#include "BplusTree.hpp" +#include +#include +#include +#include namespace { using xo::tree::BplusTree; diff --git a/xo-randomgen/include/xo/randomgen/print.hpp b/xo-randomgen/include/xo/randomgen/print.hpp deleted file mode 100644 index d5b0a992..00000000 --- a/xo-randomgen/include/xo/randomgen/print.hpp +++ /dev/null @@ -1,7 +0,0 @@ -/* @file print.hpp */ - -#pragma once - -#include "xo/indentlog/print/array.hpp" - -/* end print.hpp */