I feel like this question must have been asked and solved many times, because this seems to me a quite generic scenario, but I could not find anything that pointed me in the direction of a solution.
I'm trying to implement a generic iterable Generator
object that produces a sequence of numbers up until a certain termination condition is met, signaling that such condition has been reached in order to stop the iteration.
The basic idea is, essentially, to have something similar to Python's generators where an object yields values until it has no more to yield and then a StopIteration
exception is raised to inform the outside loop that the sequence is finished.
From what I understand, the problem splits into creating the sequence-generating object an then obtaining an iterator over it.
For the sequence-generating object, I thought I'd define a base Generator
class which is then extended to provide specific behaviours (e.g., get values from a set of ranges, or from a list of fixed values, etc). All Generaor
s produce a new value at each call of operator()
or throw a ValuesFinishedException
if the generator ran to the end of the sequence.
I implemented this as such (I show the single-range subclass as example, but I need to be able to model more types of sequences):
struct ValuesFinishedException : public std::exception { };
template <typename T>
class Generator
{
public:
Generator() { };
~Generator() { };
virtual T operator()() = 0; // return the new number or raise a ValuesFinishedException
};
template <typename T>
class RangeGenerator : public Generator<T>
{
private:
T m_start;
T m_stop;
T m_step;
T m_next_val;
public:
RangeGenerator(T start, T stop, T step) :
m_start(start),
m_stop(stop),
m_step(step),
m_next_val(start)
{ }
T operator()() override
{
if (m_next_val >= m_stop)
throw ValuesFinishedException();
T retval = m_next_val;
m_next_val += m_step;
return retval;
}
void setStep(T step) { m_step = step; }
T step() { return m_step; }
};
For the iterator part, though, I'm stuck.
I have researched any combination I could think of of "Iterator", "Generator" and synonyms, but all I find only considers the case where a generator function has an unlimited number of values (see for example boost's generator_iterator). I thought about writing a Generator::iterator
class myself, but I only found examples of trivial iterators (linked lists, array reimplementations) where end
is well-defined. I don't know in advance when the end will be reached, I only know that if the generator I'm iterating over raises the exception, I need to set the iterator's current value to "end()", but I don't know how to represent it.
The reason for this class is to have a flexible sequence object I can loop over:
RangeGenerator gen(0.25f, 95.3f, 1.2f);
for(auto v : gen)
{
// do something with v
}
The example of the range is just the simplest one. I will have at least three actual use-cases:
For each of these I'm planning on having a Generator
subclass, with the iterator defined for the abstract Generator
.
To create an object/class as an iterator you have to implement the methods __iter__() and __next__() to your object. As you have learned in the Python Classes/Objects chapter, all classes have a function called __init__() , which allows you to do some initializing when the object is being created.
Repeating identical or similar tasks without making errors is something that computers do well and people do poorly. Repeated execution of a set of statements is called iteration. Because iteration is so common, Python provides several language features to make it easier.
Iterators are the objects that use the next() method to get the next value of the sequence. A generator is a function that produces or yields a sequence of values using a yield statement. Classes are used to Implement the iterators. Functions are used to implement the generator.
In JavaScript an iterator is an object which defines a sequence and potentially a return value upon its termination. Specifically, an iterator is any object which implements the Iterator protocol by having a next() method that returns an object with two properties: value. The next value in the iteration sequence.
You should use a C++ idiom: the forward iterator. This let you use C++ syntactic sugar and support the standard library. Here is a minimal example:
template<int tstart, int tstop, int tstep = 1>
class Range {
public:
class iterator {
int start;
int stop;
int step;
int current;
public:
iterator(int start, int stop, int step = 0, int current = tstart) : start(start), stop(stop), step(step == 0 ? (start < stop ? 1 : -1) : step), current(current) {}
iterator& operator++() {current += step; return *this;}
iterator operator++(int) {iterator retval = *this; ++(*this); return retval;}
bool operator==(iterator other) const {return std::tie(current, step, stop) == std::tie(other.current, other.step, other.stop);}
bool operator!=(iterator other) const {return !(*this == other);}
long operator*() {return current;}
// iterator traits
using difference_type = int;
using value_type = int;
using pointer = const int*;
using reference = const int&;
using iterator_category = std::forward_iterator_tag;
};
iterator begin() {return iterator{tstart, tstop, tstep};}
iterator end() {return iterator{tstart, tstop, tstep, tstop};}
};
It can be used with the C++98 way:
using range = Range<0, 10, 2>;
auto r = range{};
for (range::iterator it = r.begin() ; it != r.end() ; ++it) {
std::cout << *it << '\n';
}
Or with the new range loop:
for (auto n : Range<0, 10, 2>{}) {
std::cout << n << '\n';
}
In cunjunction with the stl:
std::copy(std::begin(r), std::end(r), std::back_inserter(v));
Demo: http://coliru.stacked-crooked.com/a/35ad4ce16428e65d
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With