Skip to content
Snippets Groups Projects
Commit 06be3ec6 authored by Vasileios Karakasis's avatar Vasileios Karakasis Committed by Sam Yates
Browse files

Cyclic iterators and cyclic range views (#119)

Add cyclic iterators and cyclic range view.

Cyclic iterators wrap around when the reach the end of the underlying range over which they are defined.
parent 368d3284
No related branches found
No related tags found
No related merge requests found
#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
......@@ -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
......
#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);
}
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment