Add util::flat_list and tests
This commit is contained in:
parent
91faa2423e
commit
e3c42627c4
2 changed files with 295 additions and 51 deletions
|
|
@ -1,77 +1,207 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <vector>
|
#include <memory>
|
||||||
#include <type_traits>
|
#include <cstdint>
|
||||||
#include <initializer_list>
|
|
||||||
|
|
||||||
namespace psemek::util
|
namespace psemek::util
|
||||||
{
|
{
|
||||||
|
|
||||||
template <typename T, typename IndexType = std::size_t>
|
template <typename T, typename Handle = std::size_t>
|
||||||
struct flat_list
|
struct flat_list
|
||||||
{
|
{
|
||||||
flat_list();
|
using handle_type = Handle;
|
||||||
explicit flat_list(std::size_t count);
|
|
||||||
flat_list(std::size_t count, T const & value);
|
static constexpr Handle null = static_cast<Handle>(-1);
|
||||||
flat_list(flat_list const &);
|
|
||||||
flat_list(flat_list &&);
|
flat_list() = default;
|
||||||
flat_list(std::initializer_list<T> init);
|
flat_list(flat_list &&) noexcept;
|
||||||
|
flat_list(flat_list const &) = delete;
|
||||||
|
|
||||||
|
flat_list & operator = (flat_list &&) noexcept;
|
||||||
|
flat_list & operator = (flat_list const &) = delete;
|
||||||
|
|
||||||
~flat_list();
|
~flat_list();
|
||||||
|
|
||||||
std::size_t size() const;
|
Handle insert(T const & value);
|
||||||
|
Handle insert(T && value);
|
||||||
|
|
||||||
|
void erase(Handle handle);
|
||||||
|
|
||||||
|
T & operator[] (Handle handle) { return nodes_[handle].value; }
|
||||||
|
T const & operator[] (Handle handle) const { return nodes_[handle].value; }
|
||||||
|
|
||||||
|
std::size_t size() const { return size_; }
|
||||||
|
std::size_t capacity() const { return capacity_; }
|
||||||
|
bool empty() const { return size_ == 0; }
|
||||||
|
|
||||||
|
void reserve(std::size_t size);
|
||||||
|
void clear();
|
||||||
|
|
||||||
|
void swap(flat_list & other);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
using item = std::aligned_storage_t<std::max(sizeof(T), sizeof(IndexType))>;
|
union node
|
||||||
|
{
|
||||||
|
Handle next;
|
||||||
|
T value;
|
||||||
|
|
||||||
static constexpr std::size_t nil = static_cast<std::size_t>(-1);
|
node()
|
||||||
|
: next{null}
|
||||||
|
{}
|
||||||
|
|
||||||
// Invariant: data_.size() >= size_
|
node(Handle next)
|
||||||
std::vector<item> data_;
|
: next{next}
|
||||||
std::size_t size_ = 0;
|
{}
|
||||||
std::size_t first_free_ = nil;
|
|
||||||
|
~node() {}
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename T, typename I>
|
std::unique_ptr<node[]> nodes_;
|
||||||
flat_list<T, I>::flat_list() = default;
|
std::size_t capacity_ = 0;
|
||||||
|
std::size_t size_ = 0;
|
||||||
|
Handle first_ = null;
|
||||||
|
|
||||||
template <typename T, typename I>
|
void expand();
|
||||||
flat_list<T, I>::flat_list(std::size_t count)
|
};
|
||||||
: data_(count)
|
|
||||||
, size_(count)
|
|
||||||
{}
|
|
||||||
|
|
||||||
template <typename T, typename I>
|
template <typename T, typename Handle>
|
||||||
flat_list<T, I>::flat_list(std::size_t count, T const & value)
|
flat_list<T, Handle>::flat_list(flat_list && other) noexcept
|
||||||
: data_(count, value)
|
: nodes_(std::move(other.nodes_))
|
||||||
, size_(count)
|
, capacity_(other.capacity_)
|
||||||
{}
|
|
||||||
|
|
||||||
template <typename T, typename I>
|
|
||||||
flat_list<T, I>::flat_list(flat_list const & other) = default;
|
|
||||||
|
|
||||||
template <typename T, typename I>
|
|
||||||
flat_list<T, I>::flat_list (flat_list && other)
|
|
||||||
: data_(std::move(other.data_))
|
|
||||||
, size_(other.size_)
|
, size_(other.size_)
|
||||||
, first_free_(other.first_free_)
|
, first_(other.first_)
|
||||||
{
|
{
|
||||||
|
other.capacity_ = 0;
|
||||||
other.size_ = 0;
|
other.size_ = 0;
|
||||||
other.first_free_ = nil;
|
other.first_ = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T, typename I>
|
template <typename T, typename Handle>
|
||||||
flat_list<T, I>::flat_list(std::initializer_list<T> init)
|
flat_list<T, Handle> & flat_list<T, Handle>::operator = (flat_list && other) noexcept
|
||||||
: data_(std::move(init))
|
|
||||||
, size_(data_.size())
|
|
||||||
{}
|
|
||||||
|
|
||||||
template <typename T, typename I>
|
|
||||||
flat_list<T, I>::~flat_list() = default;
|
|
||||||
|
|
||||||
template <typename T, typename I>
|
|
||||||
std::size_t flat_list<T, I>::size() const
|
|
||||||
{
|
{
|
||||||
return size_;
|
flat_list(std::move(other)).swap(*this);
|
||||||
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename T, typename Handle>
|
||||||
|
flat_list<T, Handle>::~flat_list()
|
||||||
|
{
|
||||||
|
clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, typename Handle>
|
||||||
|
Handle flat_list<T, Handle>::insert(T const & value)
|
||||||
|
{
|
||||||
|
if (first_ == null)
|
||||||
|
expand();
|
||||||
|
|
||||||
|
auto handle = first_;
|
||||||
|
auto next = nodes_[first_].next;
|
||||||
|
new (&nodes_[first_].value) T{value};
|
||||||
|
first_ = next;
|
||||||
|
++size_;
|
||||||
|
return handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, typename Handle>
|
||||||
|
Handle flat_list<T, Handle>::insert(T && value)
|
||||||
|
{
|
||||||
|
if (first_ == null)
|
||||||
|
expand();
|
||||||
|
|
||||||
|
auto handle = first_;
|
||||||
|
auto next = nodes_[first_].next;
|
||||||
|
new (&nodes_[first_].value) T{std::move(value)};
|
||||||
|
first_ = next;
|
||||||
|
++size_;
|
||||||
|
return handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, typename Handle>
|
||||||
|
void flat_list<T, Handle>::erase(Handle handle)
|
||||||
|
{
|
||||||
|
nodes_[handle].value.~T();
|
||||||
|
nodes_[handle].next = first_;
|
||||||
|
first_ = handle;
|
||||||
|
--size_;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, typename Handle>
|
||||||
|
void flat_list<T, Handle>::reserve(std::size_t size)
|
||||||
|
{
|
||||||
|
if (size <= capacity_) return;
|
||||||
|
|
||||||
|
bool const fast_reserve = (size_ == capacity_);
|
||||||
|
|
||||||
|
std::unique_ptr<node[]> new_nodes(new node[size]);
|
||||||
|
for (Handle h = first_; h != null;)
|
||||||
|
{
|
||||||
|
auto next = nodes_[h].next;
|
||||||
|
new_nodes[h].next = (next == null) ? static_cast<Handle>(capacity_) : next;
|
||||||
|
h = next;
|
||||||
|
}
|
||||||
|
for (std::size_t i = capacity_; i + 1 < size; ++i)
|
||||||
|
new_nodes[i].next = static_cast<Handle>(i + 1);
|
||||||
|
new_nodes[size - 1].next = null;
|
||||||
|
|
||||||
|
for (std::size_t i = 0; i < capacity_; ++i)
|
||||||
|
{
|
||||||
|
// optimized for the case of full container
|
||||||
|
if (!fast_reserve && (new_nodes[i].next == null)) continue;
|
||||||
|
new (&new_nodes[i].value) T{std::move(nodes_[i].value)};
|
||||||
|
nodes_[i].value.~T();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (first_ == null)
|
||||||
|
first_ = static_cast<Handle>(capacity_);
|
||||||
|
|
||||||
|
capacity_ = size;
|
||||||
|
nodes_ = std::move(new_nodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, typename Handle>
|
||||||
|
void flat_list<T, Handle>::clear()
|
||||||
|
{
|
||||||
|
if (size_ == 0)
|
||||||
|
{
|
||||||
|
nodes_.reset();
|
||||||
|
capacity_ = 0;
|
||||||
|
first_ = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Handle h = first_; h != null;)
|
||||||
|
{
|
||||||
|
auto next = nodes_[h].next;
|
||||||
|
new (&nodes_[h].value) T;
|
||||||
|
h = next;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (std::size_t i = 0; i < capacity_; ++i)
|
||||||
|
nodes_[i].value.~T();
|
||||||
|
|
||||||
|
nodes_.reset();
|
||||||
|
capacity_ = 0;
|
||||||
|
size_ = 0;
|
||||||
|
first_ = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, typename Handle>
|
||||||
|
void flat_list<T, Handle>::swap(flat_list & other)
|
||||||
|
{
|
||||||
|
std::swap(nodes_, other.nodes_);
|
||||||
|
std::swap(capacity_, other.capacity_);
|
||||||
|
std::swap(size_, other.size_);
|
||||||
|
std::swap(first_, other.first_);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, typename Handle>
|
||||||
|
void flat_list<T, Handle>::expand()
|
||||||
|
{
|
||||||
|
if (capacity_ == 0)
|
||||||
|
reserve(16);
|
||||||
|
else
|
||||||
|
reserve(2 * capacity_);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
114
libs/util/tests/flat_list.cpp
Normal file
114
libs/util/tests/flat_list.cpp
Normal file
|
|
@ -0,0 +1,114 @@
|
||||||
|
#include <psemek/test/test.hpp>
|
||||||
|
|
||||||
|
#include <psemek/util/flat_list.hpp>
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <random>
|
||||||
|
#include <list>
|
||||||
|
|
||||||
|
using namespace psemek::util;
|
||||||
|
|
||||||
|
test_case(util_flat__list_empty)
|
||||||
|
{
|
||||||
|
flat_list<int> l;
|
||||||
|
expect(l.empty());
|
||||||
|
expect_equal(l.size(), 0);
|
||||||
|
expect_equal(l.capacity(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
test_case(util_flat__list_sequential)
|
||||||
|
{
|
||||||
|
auto p = std::make_shared<int>(42);
|
||||||
|
flat_list<std::shared_ptr<int>> l;
|
||||||
|
|
||||||
|
std::vector<decltype(l)::handle_type> h;
|
||||||
|
|
||||||
|
for (std::size_t i = 0; i < 1024*1024; ++i)
|
||||||
|
{
|
||||||
|
h.push_back(l.insert(p));
|
||||||
|
expect_equal(*l[h.back()], *p);
|
||||||
|
expect_equal(l.size(), i + 1);
|
||||||
|
expect_gequal(l.capacity(), l.size());
|
||||||
|
expect_equal(p.use_count(), i + 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (std::size_t i = 0; i < h.size(); ++i)
|
||||||
|
{
|
||||||
|
l.erase(h[i]);
|
||||||
|
expect_equal(l.size(), h.size() - i - 1);
|
||||||
|
expect_gequal(l.capacity(), l.size());
|
||||||
|
expect_equal(p.use_count(), h.size() - i);
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(l.empty());
|
||||||
|
|
||||||
|
l.clear();
|
||||||
|
expect(l.empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
test_case(util_flat__list_shuffle)
|
||||||
|
{
|
||||||
|
auto p = std::make_shared<int>(42);
|
||||||
|
flat_list<std::shared_ptr<int>> l;
|
||||||
|
|
||||||
|
std::vector<decltype(l)::handle_type> h;
|
||||||
|
|
||||||
|
for (std::size_t i = 0; i < 1024*1024; ++i)
|
||||||
|
{
|
||||||
|
h.push_back(l.insert(p));
|
||||||
|
expect_equal(l[h.back()], p);
|
||||||
|
expect_equal(l.size(), i + 1);
|
||||||
|
expect_gequal(l.capacity(), l.size());
|
||||||
|
expect_equal(p.use_count(), i + 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::default_random_engine rng{42};
|
||||||
|
std::shuffle(h.begin(), h.end(), rng);
|
||||||
|
|
||||||
|
for (std::size_t i = 0; i < h.size(); ++i)
|
||||||
|
{
|
||||||
|
l.erase(h[i]);
|
||||||
|
expect_equal(l.size(), h.size() - i - 1);
|
||||||
|
expect_gequal(l.capacity(), l.size());
|
||||||
|
expect_equal(p.use_count(), h.size() - i);
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(l.empty());
|
||||||
|
|
||||||
|
l.clear();
|
||||||
|
expect(l.empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
test_case(util_flat__list_random)
|
||||||
|
{
|
||||||
|
auto p = std::make_shared<int>(42);
|
||||||
|
flat_list<std::shared_ptr<int>> l;
|
||||||
|
|
||||||
|
std::vector<decltype(l)::handle_type> h;
|
||||||
|
|
||||||
|
std::default_random_engine rng{42};
|
||||||
|
std::uniform_int_distribution<int> action{0, 1};
|
||||||
|
|
||||||
|
for (std::size_t i = 0; i < 1024*1024; ++i)
|
||||||
|
{
|
||||||
|
if (h.empty() || action(rng) == 0)
|
||||||
|
{
|
||||||
|
h.push_back(l.insert(p));
|
||||||
|
expect_equal(l[h.back()], p);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
auto index = std::uniform_int_distribution<std::size_t>{0, h.size() - 1}(rng);
|
||||||
|
expect_equal(l[h[index]], p);
|
||||||
|
l.erase(h[index]);
|
||||||
|
h.erase(h.begin() + index);
|
||||||
|
}
|
||||||
|
|
||||||
|
expect_equal(l.size(), h.size());
|
||||||
|
expect_gequal(l.capacity(), l.size());
|
||||||
|
expect_equal(p.use_count(), h.size() + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
l.clear();
|
||||||
|
expect(l.empty());
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue