git-subtree-dir: third_party/immer git-subtree-split: ad3e3556d38bb75966dd24c61a774970a7c7957e
509 lines
15 KiB
C++
509 lines
15 KiB
C++
//
|
|
// immer: immutable data structures for C++
|
|
// Copyright (C) 2016, 2017, 2018 Juan Pedro Bolivar Puente
|
|
//
|
|
// This software is distributed under the Boost Software License, Version 1.0.
|
|
// See accompanying file LICENSE or copy at http://boost.org/LICENSE_1_0.txt
|
|
//
|
|
|
|
#pragma once
|
|
|
|
#include <immer/detail/rbts/node.hpp>
|
|
#include <immer/detail/rbts/operations.hpp>
|
|
#include <immer/detail/rbts/position.hpp>
|
|
|
|
#include <immer/detail/type_traits.hpp>
|
|
|
|
#include <cassert>
|
|
#include <memory>
|
|
#include <numeric>
|
|
|
|
namespace immer {
|
|
namespace detail {
|
|
namespace rbts {
|
|
|
|
template <typename T, typename MemoryPolicy, bits_t B, bits_t BL>
|
|
struct rbtree
|
|
{
|
|
using node_t = node<T, MemoryPolicy, B, BL>;
|
|
using edit_t = typename node_t::edit_t;
|
|
using owner_t = typename MemoryPolicy::transience_t::owner;
|
|
|
|
size_t size;
|
|
shift_t shift;
|
|
node_t* root;
|
|
node_t* tail;
|
|
|
|
static const rbtree& empty()
|
|
{
|
|
static const rbtree empty_{
|
|
0, BL, node_t::make_inner_n(0u), node_t::make_leaf_n(0u)};
|
|
return empty_;
|
|
}
|
|
|
|
template <typename U>
|
|
static auto from_initializer_list(std::initializer_list<U> values)
|
|
{
|
|
auto e = owner_t{};
|
|
auto result = rbtree{empty()};
|
|
for (auto&& v : values)
|
|
result.push_back_mut(e, v);
|
|
return result;
|
|
}
|
|
|
|
template <typename Iter,
|
|
typename Sent,
|
|
std::enable_if_t<compatible_sentinel_v<Iter, Sent>, bool> = true>
|
|
static auto from_range(Iter first, Sent last)
|
|
{
|
|
auto e = owner_t{};
|
|
auto result = rbtree{empty()};
|
|
for (; first != last; ++first)
|
|
result.push_back_mut(e, *first);
|
|
return result;
|
|
}
|
|
|
|
static auto from_fill(size_t n, T v)
|
|
{
|
|
auto e = owner_t{};
|
|
auto result = rbtree{empty()};
|
|
while (n-- > 0)
|
|
result.push_back_mut(e, v);
|
|
return result;
|
|
}
|
|
|
|
rbtree(size_t sz, shift_t sh, node_t* r, node_t* t)
|
|
: size{sz}
|
|
, shift{sh}
|
|
, root{r}
|
|
, tail{t}
|
|
{
|
|
assert(check_tree());
|
|
}
|
|
|
|
rbtree(const rbtree& other)
|
|
: rbtree{other.size, other.shift, other.root, other.tail}
|
|
{
|
|
inc();
|
|
}
|
|
|
|
rbtree(rbtree&& other)
|
|
: rbtree{empty()}
|
|
{
|
|
swap(*this, other);
|
|
}
|
|
|
|
rbtree& operator=(const rbtree& other)
|
|
{
|
|
auto next = other;
|
|
swap(*this, next);
|
|
return *this;
|
|
}
|
|
|
|
rbtree& operator=(rbtree&& other)
|
|
{
|
|
swap(*this, other);
|
|
return *this;
|
|
}
|
|
|
|
friend void swap(rbtree& x, rbtree& y)
|
|
{
|
|
using std::swap;
|
|
swap(x.size, y.size);
|
|
swap(x.shift, y.shift);
|
|
swap(x.root, y.root);
|
|
swap(x.tail, y.tail);
|
|
}
|
|
|
|
~rbtree() { dec(); }
|
|
|
|
void inc() const
|
|
{
|
|
root->inc();
|
|
tail->inc();
|
|
}
|
|
|
|
void dec() const { traverse(dec_visitor()); }
|
|
|
|
auto tail_size() const { return size ? ((size - 1) & mask<BL>) +1 : 0; }
|
|
|
|
auto tail_offset() const { return size ? (size - 1) & ~mask<BL> : 0; }
|
|
|
|
template <typename Visitor, typename... Args>
|
|
void traverse(Visitor v, Args&&... args) const
|
|
{
|
|
auto tail_off = tail_offset();
|
|
auto tail_size = size - tail_off;
|
|
|
|
if (tail_off)
|
|
make_regular_sub_pos(root, shift, tail_off).visit(v, args...);
|
|
else
|
|
make_empty_regular_pos(root).visit(v, args...);
|
|
|
|
make_leaf_sub_pos(tail, tail_size).visit(v, args...);
|
|
}
|
|
|
|
template <typename Visitor, typename... Args>
|
|
void traverse(Visitor v, size_t first, size_t last, Args&&... args) const
|
|
{
|
|
auto tail_off = tail_offset();
|
|
auto tail_size = size - tail_off;
|
|
|
|
if (first < tail_off)
|
|
make_regular_sub_pos(root, shift, tail_off)
|
|
.visit(v, first, last < tail_off ? last : tail_off, args...);
|
|
if (last > tail_off)
|
|
make_leaf_sub_pos(tail, tail_size)
|
|
.visit(v,
|
|
first > tail_off ? first - tail_off : 0,
|
|
last - tail_off,
|
|
args...);
|
|
}
|
|
|
|
template <typename Visitor, typename... Args>
|
|
bool traverse_p(Visitor v, Args&&... args) const
|
|
{
|
|
auto tail_off = tail_offset();
|
|
auto tail_size = size - tail_off;
|
|
return (tail_off ? make_regular_sub_pos(root, shift, tail_off)
|
|
.visit(v, args...)
|
|
: make_empty_regular_pos(root).visit(v, args...)) &&
|
|
make_leaf_sub_pos(tail, tail_size).visit(v, args...);
|
|
}
|
|
|
|
template <typename Visitor, typename... Args>
|
|
bool traverse_p(Visitor v, size_t first, size_t last, Args&&... args) const
|
|
{
|
|
auto tail_off = tail_offset();
|
|
auto tail_size = size - tail_off;
|
|
|
|
return (first < tail_off ? make_regular_sub_pos(root, shift, tail_off)
|
|
.visit(v,
|
|
first,
|
|
last < tail_off ? last : tail_off,
|
|
args...)
|
|
: true) &&
|
|
(last > tail_off
|
|
? make_leaf_sub_pos(tail, tail_size)
|
|
.visit(v,
|
|
first > tail_off ? first - tail_off : 0,
|
|
last - tail_off,
|
|
args...)
|
|
: true);
|
|
}
|
|
|
|
template <typename Visitor>
|
|
decltype(auto) descend(Visitor v, size_t idx) const
|
|
{
|
|
auto tail_off = tail_offset();
|
|
return idx >= tail_off ? make_leaf_descent_pos(tail).visit(v, idx)
|
|
: visit_regular_descent(root, shift, v, idx);
|
|
}
|
|
|
|
template <typename Fn>
|
|
void for_each_chunk(Fn&& fn) const
|
|
{
|
|
traverse(for_each_chunk_visitor{}, std::forward<Fn>(fn));
|
|
}
|
|
|
|
template <typename Fn>
|
|
void for_each_chunk(size_t first, size_t last, Fn&& fn) const
|
|
{
|
|
traverse(for_each_chunk_i_visitor{}, first, last, std::forward<Fn>(fn));
|
|
}
|
|
|
|
template <typename Fn>
|
|
bool for_each_chunk_p(Fn&& fn) const
|
|
{
|
|
return traverse_p(for_each_chunk_p_visitor{}, std::forward<Fn>(fn));
|
|
}
|
|
|
|
template <typename Fn>
|
|
bool for_each_chunk_p(size_t first, size_t last, Fn&& fn) const
|
|
{
|
|
return traverse_p(
|
|
for_each_chunk_p_i_visitor{}, first, last, std::forward<Fn>(fn));
|
|
}
|
|
|
|
bool equals(const rbtree& other) const
|
|
{
|
|
if (size != other.size)
|
|
return false;
|
|
if (size == 0)
|
|
return true;
|
|
return (size <= branches<BL> ||
|
|
make_regular_sub_pos(root, shift, tail_offset())
|
|
.visit(equals_visitor{}, other.root)) &&
|
|
make_leaf_sub_pos(tail, tail_size())
|
|
.visit(equals_visitor{}, other.tail);
|
|
}
|
|
|
|
void ensure_mutable_tail(edit_t e, count_t n)
|
|
{
|
|
if (!tail->can_mutate(e)) {
|
|
auto new_tail = node_t::copy_leaf_e(e, tail, n);
|
|
dec_leaf(tail, n);
|
|
tail = new_tail;
|
|
}
|
|
}
|
|
|
|
void push_back_mut(edit_t e, T value)
|
|
{
|
|
auto tail_off = tail_offset();
|
|
auto ts = size - tail_off;
|
|
if (ts < branches<BL>) {
|
|
ensure_mutable_tail(e, ts);
|
|
new (&tail->leaf()[ts]) T{std::move(value)};
|
|
} else {
|
|
auto new_tail = node_t::make_leaf_e(e, std::move(value));
|
|
try {
|
|
if (tail_off == size_t{branches<B>} << shift) {
|
|
auto new_root = node_t::make_inner_e(e);
|
|
try {
|
|
auto path = node_t::make_path_e(e, shift, tail);
|
|
new_root->inner()[0] = root;
|
|
new_root->inner()[1] = path;
|
|
root = new_root;
|
|
tail = new_tail;
|
|
shift += B;
|
|
} catch (...) {
|
|
node_t::delete_inner_e(new_root);
|
|
throw;
|
|
}
|
|
} else if (tail_off) {
|
|
auto new_root =
|
|
make_regular_sub_pos(root, shift, tail_off)
|
|
.visit(push_tail_mut_visitor<node_t>{}, e, tail);
|
|
root = new_root;
|
|
tail = new_tail;
|
|
} else {
|
|
auto new_root = node_t::make_path_e(e, shift, tail);
|
|
assert(tail_off == 0);
|
|
dec_empty_regular(root);
|
|
root = new_root;
|
|
tail = new_tail;
|
|
}
|
|
} catch (...) {
|
|
node_t::delete_leaf(new_tail, 1);
|
|
throw;
|
|
}
|
|
}
|
|
++size;
|
|
}
|
|
|
|
rbtree push_back(T value) const
|
|
{
|
|
auto tail_off = tail_offset();
|
|
auto ts = size - tail_off;
|
|
if (ts < branches<BL>) {
|
|
auto new_tail =
|
|
node_t::copy_leaf_emplace(tail, ts, std::move(value));
|
|
return {size + 1, shift, root->inc(), new_tail};
|
|
} else {
|
|
auto new_tail = node_t::make_leaf_n(1, std::move(value));
|
|
try {
|
|
if (tail_off == size_t{branches<B>} << shift) {
|
|
auto new_root = node_t::make_inner_n(2);
|
|
try {
|
|
auto path = node_t::make_path(shift, tail);
|
|
new_root->inner()[0] = root;
|
|
new_root->inner()[1] = path;
|
|
root->inc();
|
|
tail->inc();
|
|
return {size + 1, shift + B, new_root, new_tail};
|
|
} catch (...) {
|
|
node_t::delete_inner(new_root, 2);
|
|
throw;
|
|
}
|
|
} else if (tail_off) {
|
|
auto new_root =
|
|
make_regular_sub_pos(root, shift, tail_off)
|
|
.visit(push_tail_visitor<node_t>{}, tail);
|
|
tail->inc();
|
|
return {size + 1, shift, new_root, new_tail};
|
|
} else {
|
|
auto new_root = node_t::make_path(shift, tail);
|
|
tail->inc();
|
|
return {size + 1, shift, new_root, new_tail};
|
|
}
|
|
} catch (...) {
|
|
node_t::delete_leaf(new_tail, 1);
|
|
throw;
|
|
}
|
|
}
|
|
}
|
|
|
|
const T* array_for(size_t index) const
|
|
{
|
|
return descend(array_for_visitor<T>(), index);
|
|
}
|
|
|
|
T& get_mut(edit_t e, size_t idx)
|
|
{
|
|
auto tail_off = tail_offset();
|
|
if (idx >= tail_off) {
|
|
ensure_mutable_tail(e, size - tail_off);
|
|
return tail->leaf()[idx & mask<BL>];
|
|
} else {
|
|
return make_regular_sub_pos(root, shift, tail_off)
|
|
.visit(get_mut_visitor<node_t>{}, idx, e, &root);
|
|
}
|
|
}
|
|
|
|
const T& get(size_t index) const
|
|
{
|
|
return descend(get_visitor<T>(), index);
|
|
}
|
|
|
|
const T& get_check(size_t index) const
|
|
{
|
|
if (index >= size)
|
|
throw std::out_of_range{"index out of range"};
|
|
return descend(get_visitor<T>(), index);
|
|
}
|
|
|
|
const T& front() const { return get(0); }
|
|
|
|
const T& back() const { return tail->leaf()[(size - 1) & mask<BL>]; }
|
|
|
|
template <typename FnT>
|
|
void update_mut(edit_t e, size_t idx, FnT&& fn)
|
|
{
|
|
auto& elem = get_mut(e, idx);
|
|
elem = std::forward<FnT>(fn)(std::move(elem));
|
|
}
|
|
|
|
template <typename FnT>
|
|
rbtree update(size_t idx, FnT&& fn) const
|
|
{
|
|
auto tail_off = tail_offset();
|
|
if (idx >= tail_off) {
|
|
auto tail_size = size - tail_off;
|
|
auto new_tail =
|
|
make_leaf_sub_pos(tail, tail_size)
|
|
.visit(update_visitor<node_t>{}, idx - tail_off, fn);
|
|
return {size, shift, root->inc(), new_tail};
|
|
} else {
|
|
auto new_root = make_regular_sub_pos(root, shift, tail_off)
|
|
.visit(update_visitor<node_t>{}, idx, fn);
|
|
return {size, shift, new_root, tail->inc()};
|
|
}
|
|
}
|
|
|
|
void assoc_mut(edit_t e, size_t idx, T value)
|
|
{
|
|
update_mut(e, idx, [&](auto&&) { return std::move(value); });
|
|
}
|
|
|
|
rbtree assoc(size_t idx, T value) const
|
|
{
|
|
return update(idx, [&](auto&&) { return std::move(value); });
|
|
}
|
|
|
|
rbtree take(size_t new_size) const
|
|
{
|
|
auto tail_off = tail_offset();
|
|
if (new_size == 0) {
|
|
return empty();
|
|
} else if (new_size >= size) {
|
|
return *this;
|
|
} else if (new_size > tail_off) {
|
|
auto new_tail = node_t::copy_leaf(tail, new_size - tail_off);
|
|
return {new_size, shift, root->inc(), new_tail};
|
|
} else {
|
|
using std::get;
|
|
auto l = new_size - 1;
|
|
auto v = slice_right_visitor<node_t>();
|
|
auto r = make_regular_sub_pos(root, shift, tail_off).visit(v, l);
|
|
auto new_shift = get<0>(r);
|
|
auto new_root = get<1>(r);
|
|
auto new_tail = get<3>(r);
|
|
if (new_root) {
|
|
IMMER_ASSERT_TAGGED(new_root->compute_shift() == get<0>(r));
|
|
assert(new_root->check(new_shift, new_size - get<2>(r)));
|
|
return {new_size, new_shift, new_root, new_tail};
|
|
} else {
|
|
return {new_size, BL, empty().root->inc(), new_tail};
|
|
}
|
|
}
|
|
}
|
|
|
|
void take_mut(edit_t e, size_t new_size)
|
|
{
|
|
auto tail_off = tail_offset();
|
|
if (new_size == 0) {
|
|
// todo: more efficient?
|
|
*this = empty();
|
|
} else if (new_size >= size) {
|
|
return;
|
|
} else if (new_size > tail_off) {
|
|
auto ts = size - tail_off;
|
|
auto newts = new_size - tail_off;
|
|
if (tail->can_mutate(e)) {
|
|
destroy_n(tail->leaf() + newts, ts - newts);
|
|
} else {
|
|
auto new_tail = node_t::copy_leaf_e(e, tail, newts);
|
|
dec_leaf(tail, ts);
|
|
tail = new_tail;
|
|
}
|
|
size = new_size;
|
|
return;
|
|
} else {
|
|
using std::get;
|
|
auto l = new_size - 1;
|
|
auto v = slice_right_mut_visitor<node_t>();
|
|
auto r = make_regular_sub_pos(root, shift, tail_off).visit(v, l, e);
|
|
auto new_shift = get<0>(r);
|
|
auto new_root = get<1>(r);
|
|
auto new_tail = get<3>(r);
|
|
if (new_root) {
|
|
root = new_root;
|
|
shift = new_shift;
|
|
} else {
|
|
root = empty().root->inc();
|
|
shift = BL;
|
|
}
|
|
dec_leaf(tail, size - tail_off);
|
|
size = new_size;
|
|
tail = new_tail;
|
|
return;
|
|
}
|
|
}
|
|
|
|
bool check_tree() const
|
|
{
|
|
#if IMMER_DEBUG_DEEP_CHECK
|
|
assert(shift >= BL);
|
|
assert(tail_offset() <= size);
|
|
assert(check_root());
|
|
assert(check_tail());
|
|
#endif
|
|
return true;
|
|
}
|
|
|
|
bool check_tail() const
|
|
{
|
|
#if IMMER_DEBUG_DEEP_CHECK
|
|
if (tail_size() > 0)
|
|
assert(tail->check(0, tail_size()));
|
|
#endif
|
|
return true;
|
|
}
|
|
|
|
bool check_root() const
|
|
{
|
|
#if IMMER_DEBUG_DEEP_CHECK
|
|
if (tail_offset() > 0)
|
|
assert(root->check(shift, tail_offset()));
|
|
else {
|
|
IMMER_ASSERT_TAGGED(root->kind() == node_t::kind_t::inner);
|
|
assert(shift == BL);
|
|
}
|
|
#endif
|
|
return true;
|
|
}
|
|
};
|
|
|
|
} // namespace rbts
|
|
} // namespace detail
|
|
} // namespace immer
|