27

Based on the following question: Check if one string is a rotation of other string

I was thinking of making a cyclic iterator type that takes a range, and would be able to solve the above problem like so:

std::string s1 = "abc" ;
std::string s2 = "bca" ;
std::size_t n = 2; // number of cycles
cyclic_iterator it(s2.begin(),s2.end(),n);
cyclic_iterator end;

if (std::search(it, end, s1.begin(),s1.end()) != end)
{
   std::cout << "s1 is a rotation of s2" << std::endl;
}

My question, Is there already something like this available? I've checked Boost and STL and neither have an exact implementation.

I've got a simple hand-written (derived from a std::forward_iterator_tag specialised version of std::iterator) one but would rather use an already made/tested implementation.

River
  • 8,585
  • 14
  • 54
  • 67
Hippicoder
  • 1,565
  • 3
  • 17
  • 21
  • 2
    There is no such thing in the c++ Standard, if that is what you meant by "standard" in your question title. –  Apr 11 '10 at 10:05
  • 1
    @Neil: I was hoping an authorative library such at STL or Boost or something similar might have something like it. +1 for the comment though. – Hippicoder Apr 11 '10 at 10:13
  • I've made one aswell. Interesting thing about it that **operator<** is implemented as~ **(*this != other)**, still all stl alorithms workperfectly for any range. – Viktor Sehr Apr 11 '10 at 10:23
  • @Viktor: Does your implementation provide a cycle count? – Hippicoder Apr 11 '10 at 10:27
  • @Hippicode, No, havent even thought about that, it simply wraps if an iterator reaches C::end(). Thinking about it, I dont think std::stable_sort works with it. – Viktor Sehr Apr 11 '10 at 11:25
  • 5
    @Victor: I doubt any of the stl algorithms uses `operator<` (generally one checks "am I there yet" with `!=`). – UncleBens Apr 11 '10 at 11:28
  • 3
    I guess the real difficulty would be that would wish your iterator to be of the same category that the one used for building it... and thus the operations supported to change depending on that ;) I suppose the `Boost.Iterator` library could help a lot too. – Matthieu M. Apr 11 '10 at 11:37

6 Answers6

14

There is nothing like this in the standard. Cycles don't play well with C++ iterators because a sequence representing the entire cycle would have first == last and hence be the empty sequence.

Possibly you could introduce some state into the iterator, a Boolean flag to represent "not done yet." The flag participates in comparison. Set it true before iterating and to false upon increment/decrement.

But it might just be better to manually write the algorithms you need. Once you've managed to represent the whole cycle, representing an empty sequence might have become impossible.

EDIT: Now I notice that you specified the number of cycles. That makes a big difference.

template< class I >
class cyclic_iterator
 /* : public iterator< bidirectional, yadda yadda > */ {
    I it, beg, end;
    int cnt;
    cyclic_iterator( int c, I f, I l )
        : it( f ), beg( f ), end( l ), cnt( c ) {}
public:
    cyclic_iterator() : it(), beg(), end(), cnt() {}

    cyclic_iterator &operator++() {
        ++ it;
        if ( it == end ) {
            ++ cnt;
            it = beg;
        }
    } // etc for --, post-operations

    friend bool operator==
        ( cyclic_iterator const &lhs, cyclic_iterator const &rhs )
        { return lhs.it == rhs.it && lhs.cnt == rhs.cnt; } // etc for !=

    friend pair< cyclic_iterator, cyclic_iterator > cycle_range
        ( int c, I f, I l ) {//factory function, better style outside this scope
        return make_pair( cyclic_iterator( 0, f, l ),
                          cyclic_iterator( c, f, l ) );
    }
};
Potatoswatter
  • 134,909
  • 25
  • 265
  • 421
  • 1
    I think for cyclic iterators, `last` would be inequal to any other iterator, since it’s a (pseudo-)infinite sequence … – Konrad Rudolph Apr 11 '10 at 12:59
  • @Konrad: then everything in `` would be useless, and indeed you wouldn't have a compliant iterator at all. – Potatoswatter Apr 11 '10 at 13:13
  • 7
    @Potatocorn: “everything in `` would be useless.” Yes, that’s the nature of cyclic/infinite sequences. But *other* things work very well with them. A lot of frameworks use them without problem. “you wouldn't have a compliant iterator at all.” What gives you that idea? Nothing in the standard says so. In fact, input iterators *are* pseudo-infinite and have many of the same properties. – Konrad Rudolph Apr 11 '10 at 13:22
  • @Konrad: The standard does require iterators to be equal to themselves, so never being equal to anything would be noncompliant. Forward iterator sequences are typically terminated by a singular value, ie `my_fwd_iterator()`. Iterators and sequences go hand-in-hand, so failing to account for the empty sequence is going to be risky. – Potatoswatter Apr 11 '10 at 13:50
  • 2
    By the way, I don’t want to come over as contrary: I actually agree that the C++ standard library doesn’t support the usage of infinite iterators well. That’s a pity though, since they make a lot of complex logic very tractable. The introduction of ranges in C++0x would have changed that and we’d have seen “lazy” algorithms similar to Haskell’s/Python’s sequences or .NET’s Linq library. Unfortunately, that was scrapped but influential people such as Andrei Alexandrescu continue to support the idea, read – Konrad Rudolph Apr 11 '10 at 13:54
  • @Potatoswatter: That’s why I wrote “… inequal to **any other** iterator.” For a compliant, working example, see http://pastie.org/914078 – Konrad Rudolph Apr 11 '10 at 14:00
  • @Konrad: fine, an iterator is also equal to a copy of itself. Your cyclic iterator exhibits what I'm talking about. No pair [first, last) of cyclic iterators can represent the entire cycle. – Potatoswatter Apr 11 '10 at 14:21
  • 2
    “Your cyclic iterator exhibits what I'm talking about.” Fine, so what are we arguing about? :-) “No pair [first, last) of cyclic iterators can represent the entire cycle.” Of course not, since a cycle by definition has no end. – Konrad Rudolph Apr 11 '10 at 14:22
  • @Konrad: Ranges are what we're arguing about. If you want an infinite sequence, you can use a cyclic iterator. But if you want a finite sequence, you can't predicate a loop on a comparison of cyclic iterators. That makes me question the value of paying any lip service to C++ iterators. – Potatoswatter Apr 11 '10 at 14:30
  • 2
    @Potatoswatter: The cycle count could be a template value, not necassairly need to be apart of the constructor. But i think it would be better if it were a constructor param. – Hippicoder Apr 11 '10 at 15:20
5

This should provide some ideas/solutions: 2 renditions, the second is a little lighter in weight. Both tested using a subrange of a vector and a list ...

#include <vector>

template <typename T, typename Container = std::vector<T>, typename Iterator = Container::iterator>
class RingIterator : public std::iterator <std::bidirectional_iterator_tag, T, ptrdiff_t>
{
  Container& data;

  Iterator   cursor;
  Iterator   begin;
  Iterator   end;

  public:

    RingIterator (Container& v) : data(v), cursor(v.begin()), begin(v.begin()), end(v.end()) {}

    RingIterator (Container& v, const Iterator& i) : data(v), cursor(i), begin(v.begin()), end(v.end()) {}

    RingIterator (Container& v, const Iterator& i, const Iterator& j) : data(v), cursor(i), begin(i), end(j) {}

    RingIterator (Container& v, size_t i) : data(v), cursor(v.begin() + i % v.size()), begin(v.begin()), end(v.end()) {}

    bool operator == (const RingIterator& x) const 
    { 
      return cursor == x.cursor; 
    }

    bool operator != (const RingIterator& x) const 
    {
      return ! (*this == x); 
    }

    reference operator*() const 
    {
      return *cursor; 
    }

    RingIterator& operator++() 
    {
      ++cursor;
      if (cursor == end)
        cursor = begin;
      return *this;
    }

    RingIterator operator++(int) 
    {
      RingIterator ring = *this;
      ++*this;
      return ring;
    }

    RingIterator& operator--() 
    {
      if (cursor == begin)
        cursor = end;
      --cursor;
      return *this;
    }

    RingIterator operator--(int) 
    {
      RingIterator ring = *this;
      --*this; 
      return ring;
    }

    RingIterator insert (const T& x)
    {
      return RingIterator (data, data.insert (cursor, x));
    }

    RingIterator erase() 
    {
      return RingIterator (data, data.erase (cursor));
    }
};

template <typename T, typename Iterator>
class CyclicIterator : public std::iterator <std::bidirectional_iterator_tag, T, ptrdiff_t>
{
  Iterator   cursor;
  Iterator   begin;
  Iterator   end;

  public:

    CyclicIterator (const Iterator& i, const Iterator& j) : cursor(i), begin(i), end(j) {}

    bool operator == (const CyclicIterator& x) const 
    { 
      return cursor == x.cursor; 
    }

    bool operator != (const CyclicIterator& x) const 
    {
      return ! (*this == x); 
    }

    reference operator*() const 
    {
      return *cursor; 
    }

    CyclicIterator& operator++() 
    {
      ++cursor;
      if (cursor == end)
        cursor = begin;
      return *this;
    }

    CyclicIterator operator++(int) 
    {
      CyclicIterator ring = *this;
      ++*this;
      return ring;
    }

    CyclicIterator& operator--() 
    {
      if (cursor == begin)
        cursor = end;
      --cursor;
      return *this;
    }

    CyclicIterator operator--(int) 
    {
      CyclicIterator ring = *this;
      --*this; 
      return ring;
    }
};

#include <iostream>
#include <iomanip>

#include <list>

enum { CycleSize = 9, ContainerSize };

template <typename cyclicIterator>
void test (cyclicIterator& iterator, size_t mn)
{
  int m = mn;
  while (m--)
    for (int n = mn; n--; ++iterator)
      std::cout << std::setw(3) << *iterator << ' ';
  --iterator;
  m = mn;
  while (m--)
    for (int n = mn; n--; --iterator)
      std::cout << std::setw(3) << *iterator << ' ';
}

template <typename containers>
void load (containers& container)
{
  while (container.size() < ContainerSize)
    container.push_back (container.size());
}

void main (void)
{
  typedef std::vector<int>     vContainer;
  typedef vContainer::iterator vIterator;
  typedef std::list<int>       lContainer;
  typedef lContainer::iterator lIterator;

  vContainer v;  load (v);
  vIterator vbegin = v.begin() + 1;

  RingIterator <int, vContainer, vIterator> vring (v, vbegin, v.end());
  CyclicIterator <int, vIterator> vcycle (vbegin, v.end());

  lContainer l;  load (l);
  lIterator lbegin = l.begin(); ++lbegin;

  RingIterator <int, lContainer, lIterator> lring (l, lbegin, l.end());
  CyclicIterator <int, lIterator> lcycle (lbegin, l.end());

  test (vring, CycleSize);
  test (vcycle, CycleSize);
  test (lring, CycleSize);
  test (lcycle, CycleSize);
}
Mikal Keenan
  • 51
  • 1
  • 2
2

The CGAL library defines Circulators. They are used like this.

template<class Circulator, class T> 
bool contains(Circulator c, Circulator d, const T& value) { 
  if (c != 0) { 
    do { 
      if (*c == value) 
        return true; 
    } while (++c != d); 
  } 
  return false; 
} 

Note that they look like iterators at first glance but note that the logic (and the structure of the loop) is different than for iterators). if(not empty) do{..}while() instead of while(){...}.

alfC
  • 14,261
  • 4
  • 67
  • 118
1

Eric Niebler's ranges-v3 library (on which the upcoming C++20 ranges is based) has ranges::view::cycle. This adapts its source range into an endlessly repeating infinite range. However we require a single repeat which may be easily achieved using ranges::view::concat.

#include <ranges/v3/all.hpp>

int main() {
    std::string s1 = "abc";
    std::string s2 = "bca";

    auto s2s2 = ranges::view::concat(s2, s2);
    auto i = std::search(s2s2.begin(), s2s2.end(), s1.begin(), s1.end());
    if (i != s2s2.end() && s1.size() == s2.size()) {
        std::cout << "s1 is a rotation of s2\n";
    }
}
Richard Forrest
  • 601
  • 1
  • 7
  • 9
0

You’re maybe looking for Boost’s Circular Buffer. But if you’ve already checked Boost, it might not be the one you want.

Debilski
  • 66,976
  • 12
  • 110
  • 133
0

On the other hand, the very idea of cyclic iterator is not compatible to STL container ideology. You should not want cyclic iterator, as the user of this iterator may be surprized by its unusual behavior. Usually in STL you are iterating from the beginning to the end of container. Infinite loop in that case. Because the end is not reachable.

After all, obviously, you are not going to do more than 2 cycles to solve your task. No reason to have special iterator with confusing behavior. That is better to iterate usual linear container twice or may be even less then twice.

coodan
  • 1
  • That is the point, if, eventually, one has a cyclic container then it will not have a `begin` and `end`, it will have another type of interface. For example, it can have a `any` or `some` special point and one can generate a circulator from it, with a different logic than an iterator (see my answer). – alfC Mar 01 '19 at 05:59