diff --git a/src/util/cycle.hpp b/src/util/cycle.hpp new file mode 100644 index 0000000000000000000000000000000000000000..a4405bdbce6b0d4c391b3e1275c86641faaf3cd5 --- /dev/null +++ b/src/util/cycle.hpp @@ -0,0 +1,210 @@ +#pragma once + +#include <utility> +#include <util/iterutil.hpp> +#include <util/range.hpp> + +namespace nest { +namespace mc { +namespace util { + +template <typename I, typename S = I> +class cyclic_iterator : public iterator_adaptor<cyclic_iterator<I,S>, I> { + using base = iterator_adaptor<cyclic_iterator<I,S>, I>; + friend class iterator_adaptor<cyclic_iterator<I,S>, I>; + + I begin_; + I inner_; + S end_; + typename base::difference_type off_; // offset from begin + + const I& inner() const { + return inner_; + } + + I& inner() { + return inner_; + } + +public: + using value_type = typename base::value_type; + using difference_type = typename base::difference_type; + + cyclic_iterator() = default; + + template <typename Iter, typename Sentinel> + cyclic_iterator(Iter&& iter, Sentinel&& sentinel) + : begin_(std::forward<Iter>(iter)), + inner_(std::forward<Iter>(iter)), + end_(std::forward<Sentinel>(sentinel)), + off_(0) + { } + + cyclic_iterator(const cyclic_iterator& other) + : begin_(other.begin_), + inner_(other.inner_), + end_(other.end_), + off_(other.off_) + { } + + cyclic_iterator(cyclic_iterator&& other) + : begin_(std::move(other.begin_)), + inner_(std::move(other.inner_)), + end_(std::move(other.end_)), + off_(other.off_) + { } + + + cyclic_iterator& operator=(const cyclic_iterator& other) { + if (this != &other) { + inner_ = other.inner_; + begin_ = other.begin_; + end_ = other.end_; + off_ = other.off_; + } + + return *this; + } + + cyclic_iterator& operator=(cyclic_iterator&& other) { + if (this != &other) { + inner_ = std::move(other.inner_); + begin_ = std::move(other.begin_); + end_ = std::move(other.end_); + off_ = other.off_; + } + + return *this; + } + + // forward and input iterator requirements + value_type operator*() const { + return *inner_; + } + + value_type operator[](difference_type n) const { + return *(*this + n); + } + + cyclic_iterator& operator++() { + if (++inner_ == end_) { + // wrap around + inner_ = begin_; + } + + ++off_; + return *this; + } + + cyclic_iterator operator++(int) { + cyclic_iterator iter(*this); + ++(*this); + return iter; + } + + cyclic_iterator& operator--() { + if (inner_ == begin_) { + // wrap around; use upto() to handle efficiently the move to the end + // in case inner_ is a bidirectional iterator + inner_ = upto(inner_, end_); + } + else { + --inner_; + } + + --off_; + return *this; + } + + cyclic_iterator operator--(int) { + cyclic_iterator iter(*this); + --(*this); + return iter; + } + + cyclic_iterator& operator+=(difference_type n) { + // wrap distance + auto size = util::distance(begin_, end_); + + // calculate distance from begin + auto pos = (off_ += n); + if (pos < 0) { + auto mod = -pos % size; + pos = mod ? size - mod : 0; + } + else { + pos = pos % size; + } + + inner_ = std::next(begin_, pos); + return *this; + } + + cyclic_iterator& operator-=(difference_type n) { + return this->operator+=(-n); + } + + bool operator==(const cyclic_iterator& other) const { + return begin_ == other.begin_ && off_ == other.off_; + } + + bool operator!=(const cyclic_iterator& other) const { + return !(*this == other); + } + + cyclic_iterator operator-(difference_type n) const { + cyclic_iterator c(*this); + return c -= n; + } + + difference_type operator-(const cyclic_iterator& other) const { + return off_ - other.off_; + } + + bool operator<(const cyclic_iterator& other) const { + return off_ < other.off_; + } + + // expose inner iterator for testing against a sentinel + template <typename Sentinel> + bool operator==(const Sentinel& s) const { + return inner_ == s; + } + + template <typename Sentinel> + bool operator!=(const Sentinel& s) const { + return !(inner_ == s); + } +}; + +template <typename I, typename S> +cyclic_iterator<I, S> make_cyclic_iterator(const I& iter, const S& sentinel) { + return cyclic_iterator<I, S>(iter, sentinel); +} + + +template < + typename Seq, + typename SeqIter = typename sequence_traits<Seq>::const_iterator, + typename SeqSentinel = typename sequence_traits<Seq>::const_sentinel, + typename = enable_if_t<std::is_same<SeqIter, SeqSentinel>::value> +> +range<cyclic_iterator<SeqIter, SeqSentinel> > cyclic_view(const Seq& s) { + return { make_cyclic_iterator(cbegin(s), cend(s)), + make_cyclic_iterator(cend(s), cend(s)) }; +} + +template < + typename Seq, + typename SeqIter = typename sequence_traits<Seq>::const_iterator, + typename SeqSentinel = typename sequence_traits<Seq>::const_sentinel, + typename = enable_if_t<!std::is_same<SeqIter, SeqSentinel>::value> +> +range<cyclic_iterator<SeqIter, SeqSentinel>, SeqSentinel> +cyclic_view(const Seq& s) { + return { make_cyclic_iterator(cbegin(s), cend(s)), cend(s) }; +} + +} // namespace util +} // namespace mc +} // namespace nest diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index 75c46c46b55db64f158cc85d739d67762698d835..45de971662d68f0972cda67419655ea5c574eb4b 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -14,6 +14,7 @@ set(TEST_SOURCES test_cell.cpp test_compartments.cpp test_counter.cpp + test_cycle.cpp test_either.cpp test_event_queue.cpp test_filter.cpp diff --git a/tests/unit/test_cycle.cpp b/tests/unit/test_cycle.cpp new file mode 100644 index 0000000000000000000000000000000000000000..d022a666b2f03308e6d951dd06aa153eef68a929 --- /dev/null +++ b/tests/unit/test_cycle.cpp @@ -0,0 +1,211 @@ +#include "../gtest.h" + +#include <algorithm> +#include <iterator> +#include <string> + +#include "common.hpp" +#include <util/cycle.hpp> +#include <util/meta.hpp> + +using namespace nest::mc; + +TEST(cycle_iterator, construct) { + std::vector<int> values = { 4, 2, 3 }; + auto cycle_iter = util::make_cyclic_iterator(values.cbegin(), values.cend()); + + { + // copy constructor + auto cycle_iter_copy(cycle_iter); + EXPECT_EQ(cycle_iter, cycle_iter_copy); + } + + { + // copy assignment + auto cycle_iter_copy = cycle_iter; + EXPECT_EQ(cycle_iter, cycle_iter_copy); + } + + { + // move constructor + auto cycle_iter_copy( + util::make_cyclic_iterator(values.cbegin(), values.cend()) + ); + EXPECT_EQ(cycle_iter, cycle_iter_copy); + } +} + + +TEST(cycle_iterator, increment) { + std::vector<int> values = { 4, 2, 3 }; + + { + // test operator++ + auto cycle_iter = util::make_cyclic_iterator(values.cbegin(), + values.cend()); + auto cycle_iter_copy = cycle_iter; + + auto values_size = values.size(); + for (auto i = 0u; i < 2*values_size; ++i) { + EXPECT_EQ(values[i % values_size], *cycle_iter); + EXPECT_EQ(values[i % values_size], *cycle_iter_copy++); + ++cycle_iter; + } + } + + { + // test operator[] + auto cycle_iter = util::make_cyclic_iterator(values.cbegin(), + values.cend()); + + for (auto i = 0u; i < values.size(); ++i) { + EXPECT_EQ(values[i], cycle_iter[values.size() + i]); + } + } + + { + auto cycle_iter = util::make_cyclic_iterator(values.cbegin(), + values.cend()); + EXPECT_NE(cycle_iter + 1, cycle_iter + 10); + } +} + +TEST(cycle_iterator, decrement) { + std::vector<int> values = { 4, 2, 3 }; + + { + // test operator-- + auto cycle_iter = util::make_cyclic_iterator(values.cbegin(), + values.cend()); + auto cycle_iter_copy = cycle_iter; + + auto values_size = values.size(); + for (auto i = 0u; i < 2*values_size; ++i) { + --cycle_iter; + cycle_iter_copy--; + auto val = values[values_size - i%values_size - 1]; + EXPECT_EQ(val, *cycle_iter); + EXPECT_EQ(val, *cycle_iter_copy); + } + } + + { + // test operator[] + auto cycle_iter = util::make_cyclic_iterator(values.cbegin(), + values.cend()); + int values_size = values.size(); + for (int i = 0; i < 2*values_size; ++i) { + auto pos = i % values_size; + pos = pos ? values_size - pos : 0; + EXPECT_EQ(values[pos], cycle_iter[-i]); + } + } + + { + auto cycle_iter = util::make_cyclic_iterator(values.cbegin(), + values.cend()); + EXPECT_NE(cycle_iter - 2, cycle_iter - 5); + EXPECT_NE(cycle_iter + 1, cycle_iter - 5); + } +} + +TEST(cycle_iterator, carray) { + int values[] = { 4, 2, 3 }; + auto cycle_iter = util::make_cyclic_iterator(util::cbegin(values), + util::cend(values)); + auto values_size = util::size(values); + for (auto i = 0u; i < 2*values_size; ++i) { + EXPECT_EQ(values[i % values_size], *cycle_iter++); + } +} + +TEST(cycle_iterator, sentinel) { + using testing::null_terminated; + + auto msg = "hello"; + auto cycle_iter = util::make_cyclic_iterator(msg, null_terminated); + + auto msg_len = std::string(msg).size(); + for (auto i = 0u; i < 2*msg_len; ++i) { + EXPECT_EQ(msg[i % msg_len], *cycle_iter++); + } +} + + +TEST(cycle, cyclic_view) { + std::vector<int> values = { 4, 2, 3 }; + std::vector<int> values_new; + + std::copy_n(util::cyclic_view(values).cbegin(), 10, + std::back_inserter(values_new)); + + EXPECT_EQ(10u, values_new.size()); + + auto i = 0; + for (auto const& v : values_new) { + EXPECT_EQ(values[i++ % values.size()], v); + } +} + +TEST(cycle_iterator, difference) { + int values[] = { 4, 2, 3 }; + + auto cycle = util::cyclic_view(values); + auto c1 = cycle.begin(); + + auto c2 = c1; + EXPECT_EQ(0, c2-c1); + + ++c2; + EXPECT_EQ(1, c2-c1); + + ++c1; + EXPECT_EQ(0, c2-c1); + + c2 += 6; + EXPECT_EQ(6, c2-c1); + + c1 += 2; + EXPECT_EQ(4, c2-c1); + + --c2; + EXPECT_EQ(3, c2-c1); + + c1 -= 3; + EXPECT_EQ(6, c2-c1); +} + +TEST(cycle_iterator, order) { + int values[] = { 4, 2, 3 }; + + auto cycle = util::cyclic_view(values); + auto c1 = cycle.begin(); + auto c2 = c1; + + EXPECT_FALSE(c1 < c2); + EXPECT_FALSE(c2 < c1); + EXPECT_TRUE(c1 <= c2); + EXPECT_TRUE(c1 >= c2); + + c2 += util::size(values); + + EXPECT_TRUE(c1 < c2); + EXPECT_FALSE(c2 < c1); + EXPECT_TRUE(c1 <= c2); + EXPECT_FALSE(c1 >= c2); +} + +TEST(cycle, cyclic_view_sentinel) { + const char *msg = "hello"; + auto cycle = util::cyclic_view( + util::make_range(msg, testing::null_terminated) + ); + + std::string msg_new; + auto msg_new_size = 2*std::string(msg).size(); + for (auto i = 0u; i < msg_new_size; ++i) { + msg_new += cycle[i]; + } + + EXPECT_EQ("hellohello", msg_new); +}