From e3c42627c48846d25d1759ff6ebb33e1c330c583 Mon Sep 17 00:00:00 2001 From: lisyarus Date: Wed, 25 Nov 2020 13:23:27 +0300 Subject: [PATCH] Add util::flat_list and tests --- libs/util/include/psemek/util/flat_list.hpp | 232 +++++++++++++++----- libs/util/tests/flat_list.cpp | 114 ++++++++++ 2 files changed, 295 insertions(+), 51 deletions(-) create mode 100644 libs/util/tests/flat_list.cpp diff --git a/libs/util/include/psemek/util/flat_list.hpp b/libs/util/include/psemek/util/flat_list.hpp index 7ec30713..c016eba0 100644 --- a/libs/util/include/psemek/util/flat_list.hpp +++ b/libs/util/include/psemek/util/flat_list.hpp @@ -1,77 +1,207 @@ #pragma once -#include -#include -#include +#include +#include namespace psemek::util { - template + template 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 init); + using handle_type = Handle; + + static constexpr Handle null = static_cast(-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; + union node + { + Handle next; + T value; - static constexpr std::size_t nil = static_cast(-1); + node() + : next{null} + {} - // Invariant: data_.size() >= size_ - std::vector data_; + node(Handle next) + : next{next} + {} + + ~node() {} + }; + + std::unique_ptr nodes_; + std::size_t capacity_ = 0; std::size_t size_ = 0; - std::size_t first_free_ = nil; + Handle first_ = null; + + void expand(); }; - template - flat_list::flat_list() = default; - - template - flat_list::flat_list(std::size_t count) - : data_(count) - , size_(count) - {} - - template - flat_list::flat_list(std::size_t count, T const & value) - : data_(count, value) - , size_(count) - {} - - template - flat_list::flat_list(flat_list const & other) = default; - - template - flat_list::flat_list (flat_list && other) - : data_(std::move(other.data_)) + template + flat_list::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 - flat_list::flat_list(std::initializer_list init) - : data_(std::move(init)) - , size_(data_.size()) - {} - - template - flat_list::~flat_list() = default; - - template - std::size_t flat_list::size() const + template + flat_list & flat_list::operator = (flat_list && other) noexcept { - return size_; + flat_list(std::move(other)).swap(*this); + return *this; } + + template + flat_list::~flat_list() + { + clear(); + } + + template + Handle flat_list::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 + Handle flat_list::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 + void flat_list::erase(Handle handle) + { + nodes_[handle].value.~T(); + nodes_[handle].next = first_; + first_ = handle; + --size_; + } + + template + void flat_list::reserve(std::size_t size) + { + if (size <= capacity_) return; + + bool const fast_reserve = (size_ == capacity_); + + std::unique_ptr new_nodes(new node[size]); + for (Handle h = first_; h != null;) + { + auto next = nodes_[h].next; + new_nodes[h].next = (next == null) ? static_cast(capacity_) : next; + h = next; + } + for (std::size_t i = capacity_; i + 1 < size; ++i) + new_nodes[i].next = static_cast(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(capacity_); + + capacity_ = size; + nodes_ = std::move(new_nodes); + } + + template + void flat_list::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 + void flat_list::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 + void flat_list::expand() + { + if (capacity_ == 0) + reserve(16); + else + reserve(2 * capacity_); + } + } diff --git a/libs/util/tests/flat_list.cpp b/libs/util/tests/flat_list.cpp new file mode 100644 index 00000000..7bbaa2c2 --- /dev/null +++ b/libs/util/tests/flat_list.cpp @@ -0,0 +1,114 @@ +#include + +#include + +#include +#include +#include + +using namespace psemek::util; + +test_case(util_flat__list_empty) +{ + flat_list 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(42); + flat_list> l; + + std::vector 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(42); + flat_list> l; + + std::vector 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(42); + flat_list> l; + + std::vector h; + + std::default_random_engine rng{42}; + std::uniform_int_distribution 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{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()); +}