Fix SkipList iterators (#1635)

Fix SkipList iterators and find methods to be as expected by normal C++ iterator usage
This commit is contained in:
Gareth Andrew Lloyd 2024-01-23 15:31:28 +00:00 committed by GitHub
parent 071df2f439
commit e7f6a5f4f4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 124 additions and 39 deletions

View File

@ -1,4 +1,4 @@
// Copyright 2023 Memgraph Ltd.
// Copyright 2024 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@ -591,13 +591,20 @@ class SkipList final : detail::SkipListNode_base {
Iterator(TNode *node) : node_(node) {}
public:
TObj &operator*() const { return node_->obj; }
using value_type = TObj;
using difference_type = std::ptrdiff_t;
TObj *operator->() const { return &node_->obj; }
Iterator() = default;
Iterator(Iterator const &) = default;
Iterator(Iterator &&) = default;
Iterator &operator=(Iterator const &) = default;
Iterator &operator=(Iterator &&) = default;
bool operator==(const Iterator &other) const { return node_ == other.node_; }
value_type &operator*() const { return node_->obj; }
bool operator!=(const Iterator &other) const { return node_ != other.node_; }
value_type *operator->() const { return &node_->obj; }
friend bool operator==(Iterator const &lhs, Iterator const &rhs) { return lhs.node_ == rhs.node_; }
Iterator &operator++() {
while (true) {
@ -610,8 +617,14 @@ class SkipList final : detail::SkipListNode_base {
}
}
Iterator operator++(int) {
Iterator old = *this;
++(*this);
return old;
}
private:
TNode *node_;
TNode *node_{nullptr};
};
class ConstIterator final {
@ -621,15 +634,22 @@ class SkipList final : detail::SkipListNode_base {
ConstIterator(TNode *node) : node_(node) {}
public:
using value_type = TObj const;
using difference_type = std::ptrdiff_t;
ConstIterator() = default;
ConstIterator(ConstIterator const &) = default;
ConstIterator(ConstIterator &&) = default;
ConstIterator &operator=(ConstIterator const &) = default;
ConstIterator &operator=(ConstIterator &&) = default;
ConstIterator(const Iterator &it) : node_(it.node_) {}
const TObj &operator*() const { return node_->obj; }
value_type &operator*() const { return node_->obj; }
const TObj *operator->() const { return &node_->obj; }
value_type *operator->() const { return &node_->obj; }
bool operator==(const ConstIterator &other) const { return node_ == other.node_; }
bool operator!=(const ConstIterator &other) const { return node_ != other.node_; }
friend bool operator==(ConstIterator const &lhs, ConstIterator const &rhs) { return lhs.node_ == rhs.node_; }
ConstIterator &operator++() {
while (true) {
@ -642,6 +662,12 @@ class SkipList final : detail::SkipListNode_base {
}
}
ConstIterator operator++(int) {
ConstIterator old = *this;
++(*this);
return old;
}
private:
TNode *node_;
};
@ -653,6 +679,10 @@ class SkipList final : detail::SkipListNode_base {
explicit Accessor(SkipList *skiplist) : skiplist_(skiplist), id_(skiplist->gc_.AllocateId()) {}
public:
using value_type = TObj;
using iterator = Iterator;
using const_iterator = ConstIterator;
~Accessor() {
if (skiplist_ != nullptr) skiplist_->gc_.ReleaseId(id_);
}
@ -695,7 +725,7 @@ class SkipList final : detail::SkipListNode_base {
/// @return bool indicating whether the item exists
template <typename TKey>
bool contains(const TKey &key) const {
return skiplist_->template contains(key);
return skiplist_->contains(key);
}
/// Finds the key in the list and returns an iterator to the item.
@ -703,8 +733,17 @@ class SkipList final : detail::SkipListNode_base {
/// @return Iterator to the item in the list, will be equal to `end()` when
/// the key isn't found
template <typename TKey>
Iterator find(const TKey &key) const {
return skiplist_->template find(key);
Iterator find(const TKey &key) {
return skiplist_->find(key);
}
/// Finds the key in the list and returns an iterator to the item.
///
/// @return ConstIterator to the item in the list, will be equal to `cend()` when
/// the key isn't found
template <typename TKey>
ConstIterator find(const TKey &key) const {
return skiplist_->find(key);
}
/// Finds the key or the first larger key in the list and returns an
@ -713,8 +752,18 @@ class SkipList final : detail::SkipListNode_base {
/// @return Iterator to the item in the list, will be equal to `end()` when
/// no items match the search
template <typename TKey>
Iterator find_equal_or_greater(const TKey &key) const {
return skiplist_->template find_equal_or_greater(key);
Iterator find_equal_or_greater(const TKey &key) {
return skiplist_->find_equal_or_greater(key);
}
/// Finds the key or the first larger key in the list and returns an
/// iterator to the item.
///
/// @return ConstIterator to the item in the list, will be equal to `end()` when
/// no items match the search
template <typename TKey>
ConstIterator find_equal_or_greater(const TKey &key) const {
return skiplist_->find_equal_or_greater(key);
}
/// Estimates the number of items that are contained in the list that are
@ -727,7 +776,7 @@ class SkipList final : detail::SkipListNode_base {
/// @return uint64_t estimated count of identical items in the list
template <typename TKey>
uint64_t estimate_count(const TKey &key, int max_layer_for_estimation = kSkipListCountEstimateDefaultLayer) const {
return skiplist_->template estimate_count(key, max_layer_for_estimation);
return skiplist_->estimate_count(key, max_layer_for_estimation);
}
/// Estimates the number of items that are contained in the list that are
@ -742,7 +791,7 @@ class SkipList final : detail::SkipListNode_base {
uint64_t estimate_range_count(const std::optional<utils::Bound<TKey>> &lower,
const std::optional<utils::Bound<TKey>> &upper,
int max_layer_for_estimation = kSkipListCountEstimateDefaultLayer) const {
return skiplist_->template estimate_range_count(lower, upper, max_layer_for_estimation);
return skiplist_->estimate_range_count(lower, upper, max_layer_for_estimation);
}
/// Estimates the average number of objects in the list that have the same
@ -759,7 +808,7 @@ class SkipList final : detail::SkipListNode_base {
template <typename TCallable>
uint64_t estimate_average_number_of_equals(
const TCallable &equal_cmp, int max_layer_for_estimation = kSkipListCountEstimateDefaultLayer) const {
return skiplist_->template estimate_average_number_of_equals(equal_cmp, max_layer_for_estimation);
return skiplist_->estimate_average_number_of_equals(equal_cmp, max_layer_for_estimation);
}
/// Removes the key from the list.
@ -767,7 +816,7 @@ class SkipList final : detail::SkipListNode_base {
/// @return bool indicating whether the removal was successful
template <typename TKey>
bool remove(const TKey &key) {
return skiplist_->template remove(key);
return skiplist_->remove(key);
}
/// Returns the number of items contained in the list.
@ -787,6 +836,10 @@ class SkipList final : detail::SkipListNode_base {
explicit ConstAccessor(const SkipList *skiplist) : skiplist_(skiplist), id_(skiplist->gc_.AllocateId()) {}
public:
using value_type = TObj;
using iterator = ConstIterator;
using const_iterator = ConstIterator;
~ConstAccessor() {
if (skiplist_ != nullptr) skiplist_->gc_.ReleaseId(id_);
}
@ -812,35 +865,35 @@ class SkipList final : detail::SkipListNode_base {
template <typename TKey>
bool contains(const TKey &key) const {
return skiplist_->template contains(key);
return skiplist_->contains(key);
}
template <typename TKey>
ConstIterator find(const TKey &key) const {
return skiplist_->template find(key);
return skiplist_->find(key);
}
template <typename TKey>
ConstIterator find_equal_or_greater(const TKey &key) const {
return skiplist_->template find_equal_or_greater(key);
return skiplist_->find_equal_or_greater(key);
}
template <typename TKey>
uint64_t estimate_count(const TKey &key, int max_layer_for_estimation = kSkipListCountEstimateDefaultLayer) const {
return skiplist_->template estimate_count(key, max_layer_for_estimation);
return skiplist_->estimate_count(key, max_layer_for_estimation);
}
template <typename TKey>
uint64_t estimate_range_count(const std::optional<utils::Bound<TKey>> &lower,
const std::optional<utils::Bound<TKey>> &upper,
int max_layer_for_estimation = kSkipListCountEstimateDefaultLayer) const {
return skiplist_->template estimate_range_count(lower, upper, max_layer_for_estimation);
return skiplist_->estimate_range_count(lower, upper, max_layer_for_estimation);
}
template <typename TCallable>
uint64_t estimate_average_number_of_equals(
const TCallable &equal_cmp, int max_layer_for_estimation = kSkipListCountEstimateDefaultLayer) const {
return skiplist_->template estimate_average_number_of_equals(equal_cmp, max_layer_for_estimation);
return skiplist_->estimate_average_number_of_equals(equal_cmp, max_layer_for_estimation);
}
uint64_t size() const { return skiplist_->size(); }
@ -1018,26 +1071,33 @@ class SkipList final : detail::SkipListNode_base {
}
template <typename TKey>
bool contains(const TKey &key) const {
TNode *preds[kSkipListMaxHeight], *succs[kSkipListMaxHeight];
int layer_found = find_node(key, preds, succs);
return (layer_found != -1 && succs[layer_found]->fully_linked.load(std::memory_order_acquire) &&
!succs[layer_found]->marked.load(std::memory_order_acquire));
}
template <typename TKey>
Iterator find(const TKey &key) const {
SkipListNode<TObj> *find_(const TKey &key) const {
TNode *preds[kSkipListMaxHeight], *succs[kSkipListMaxHeight];
int layer_found = find_node(key, preds, succs);
if (layer_found != -1 && succs[layer_found]->fully_linked.load(std::memory_order_acquire) &&
!succs[layer_found]->marked.load(std::memory_order_acquire)) {
return Iterator{succs[layer_found]};
return succs[layer_found];
}
return Iterator{nullptr};
return nullptr;
}
template <typename TKey>
Iterator find_equal_or_greater(const TKey &key) const {
bool contains(const TKey &key) const {
return find_(key) != nullptr;
}
template <typename TKey>
Iterator find(const TKey &key) {
return {find_(key)};
}
template <typename TKey>
ConstIterator find(const TKey &key) const {
return {find_(key)};
}
template <typename TKey>
Iterator find_equal_or_greater_(const TKey &key) const {
TNode *preds[kSkipListMaxHeight], *succs[kSkipListMaxHeight];
find_node(key, preds, succs);
if (succs[0] && succs[0]->fully_linked.load(std::memory_order_acquire) &&
@ -1047,6 +1107,16 @@ class SkipList final : detail::SkipListNode_base {
return Iterator{nullptr};
}
template <typename TKey>
Iterator find_equal_or_greater(const TKey &key) {
return {find_equal_or_greater_(key)};
}
template <typename TKey>
ConstIterator find_equal_or_greater(const TKey &key) const {
return {find_equal_or_greater_(key)};
}
template <typename TKey>
uint64_t estimate_count(const TKey &key, int max_layer_for_estimation) const {
MG_ASSERT(max_layer_for_estimation >= 1 && max_layer_for_estimation <= kSkipListMaxHeight,

1
tests/e2e/interactive_mg_runner.py Normal file → Executable file
View File

@ -1,3 +1,4 @@
#!/usr/bin/env python3
# Copyright 2022 Memgraph Ltd.
#
# Use of this software is governed by the Business Source License

View File

@ -1,4 +1,4 @@
// Copyright 2022 Memgraph Ltd.
// Copyright 2024 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@ -18,6 +18,20 @@
#include "utils/skip_list.hpp"
#include "utils/timer.hpp"
#include <iterator>
template <typename It, typename ConstIt>
concept CompatibleIterators = std::forward_iterator<It> && std::forward_iterator<ConstIt> &&
requires(It it, ConstIt cit) {
{ it == cit } -> std::same_as<bool>;
{ it != cit } -> std::same_as<bool>;
{ cit == it } -> std::same_as<bool>;
{ cit != it } -> std::same_as<bool>;
};
using sut_t = memgraph::utils::SkipList<int64_t>::Accessor;
static_assert(CompatibleIterators<sut_t::iterator, sut_t::const_iterator>);
TEST(SkipList, Int) {
memgraph::utils::SkipList<int64_t> list;
{