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
|
||||
|
||||
#include <vector>
|
||||
#include <type_traits>
|
||||
#include <initializer_list>
|
||||
#include <memory>
|
||||
#include <cstdint>
|
||||
|
||||
namespace psemek::util
|
||||
{
|
||||
|
||||
template <typename T, typename IndexType = std::size_t>
|
||||
template <typename T, typename Handle = std::size_t>
|
||||
struct flat_list
|
||||
{
|
||||
flat_list();
|
||||
explicit flat_list(std::size_t count);
|
||||
flat_list(std::size_t count, T const & value);
|
||||
flat_list(flat_list const &);
|
||||
flat_list(flat_list &&);
|
||||
flat_list(std::initializer_list<T> init);
|
||||
using handle_type = Handle;
|
||||
|
||||
static constexpr Handle null = static_cast<Handle>(-1);
|
||||
|
||||
flat_list() = default;
|
||||
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();
|
||||
|
||||
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:
|
||||
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_
|
||||
std::vector<item> data_;
|
||||
node(Handle next)
|
||||
: next{next}
|
||||
{}
|
||||
|
||||
~node() {}
|
||||
};
|
||||
|
||||
std::unique_ptr<node[]> nodes_;
|
||||
std::size_t capacity_ = 0;
|
||||
std::size_t size_ = 0;
|
||||
std::size_t first_free_ = nil;
|
||||
Handle first_ = null;
|
||||
|
||||
void expand();
|
||||
};
|
||||
|
||||
template <typename T, typename I>
|
||||
flat_list<T, I>::flat_list() = default;
|
||||
|
||||
template <typename T, typename I>
|
||||
flat_list<T, I>::flat_list(std::size_t count)
|
||||
: data_(count)
|
||||
, size_(count)
|
||||
{}
|
||||
|
||||
template <typename T, typename I>
|
||||
flat_list<T, I>::flat_list(std::size_t count, T const & value)
|
||||
: data_(count, value)
|
||||
, size_(count)
|
||||
{}
|
||||
|
||||
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_))
|
||||
template <typename T, typename Handle>
|
||||
flat_list<T, Handle>::flat_list(flat_list && other) noexcept
|
||||
: nodes_(std::move(other.nodes_))
|
||||
, capacity_(other.capacity_)
|
||||
, size_(other.size_)
|
||||
, first_free_(other.first_free_)
|
||||
, first_(other.first_)
|
||||
{
|
||||
other.capacity_ = 0;
|
||||
other.size_ = 0;
|
||||
other.first_free_ = nil;
|
||||
other.first_ = null;
|
||||
}
|
||||
|
||||
template <typename T, typename I>
|
||||
flat_list<T, I>::flat_list(std::initializer_list<T> init)
|
||||
: 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
|
||||
template <typename T, typename Handle>
|
||||
flat_list<T, Handle> & flat_list<T, Handle>::operator = (flat_list && other) noexcept
|
||||
{
|
||||
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