Add util::flat_list and tests

This commit is contained in:
Nikita Lisitsa 2020-11-25 13:23:27 +03:00
parent 91faa2423e
commit e3c42627c4
2 changed files with 295 additions and 51 deletions

View file

@ -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_);
}
}

View 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());
}