From 06be3ec6c9e821320b09592aea781830612df2aa Mon Sep 17 00:00:00 2001
From: Vasileios Karakasis <vkarak@gmail.com>
Date: Tue, 13 Dec 2016 10:29:19 +0100
Subject: [PATCH] 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.
---
 src/util/cycle.hpp        | 210 +++++++++++++++++++++++++++++++++++++
 tests/unit/CMakeLists.txt |   1 +
 tests/unit/test_cycle.cpp | 211 ++++++++++++++++++++++++++++++++++++++
 3 files changed, 422 insertions(+)
 create mode 100644 src/util/cycle.hpp
 create mode 100644 tests/unit/test_cycle.cpp

diff --git a/src/util/cycle.hpp b/src/util/cycle.hpp
new file mode 100644
index 00000000..a4405bdb
--- /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 75c46c46..45de9716 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 00000000..d022a666
--- /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);
+}
-- 
GitLab