如何延迟生成已完成的项目序列并对其进行迭代

How to lazily generate a finished sequence of items and iterate over it

本文关键字:迭代 项目 何延迟 延迟 已完成      更新时间:2023-10-16

我觉得这个问题一定被问过很多次并解决了,因为在我看来这是一个很普遍的场景,但我找不到任何为我指明解决方案方向的东西。

我正在尝试实现一个通用的可迭代Generator对象,该对象生成一系列数字,直到满足某个终止条件,表明已达到该条件以停止迭代。

从本质上讲,基本思想是具有类似于 Python 生成器的东西,其中对象生成值,直到它没有更多要生成的值,然后引发StopIteration异常以通知外部循环序列已完成。

据我了解,问题分为创建序列生成对象,然后获取其迭代器。

对于序列生成对象,我想我会定义一个基Generator类,然后对其进行扩展以提供特定行为(例如,从一组范围或固定值列表中获取值等(。所有Generaor在每次调用operator()时都会产生一个新值,或者如果生成器运行到序列的末尾,则抛出ValuesFinishedException。 我这样实现了这一点(我以单范围子类为例,但我需要能够对更多类型的序列进行建模(:

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; }
};

不过,对于迭代器部分,我被卡住了。 我已经研究了我能想到的"迭代器"、"生成器"和同义词的任何组合,但我发现的只是考虑生成器函数具有无限数量值的情况(参见示例 boost 的generator_iterator(。我想过自己写一个Generator::iterator类,但我只找到了简单迭代器(链表、数组重新实现(的例子,其中end是明确定义的。我事先不知道什么时候会到达终点,我只知道如果我迭代的生成器引发异常,我需要将迭代器的当前值设置为"end((",但我不知道如何表示它。

编辑:添加预期的用例

此类的原因是有一个可以循环的灵活序列对象:

RangeGenerator gen(0.25f, 95.3f, 1.2f);
for(auto v : gen)
{
// do something with v
}

范围的示例只是最简单的示例。我将至少有三个实际用例:

  • 简单范围(带可变步长(
  • 多个范围的串联
  • 存储在向量中的常量值序列

对于其中的每一个,我计划都有一个Generator子类,并为抽象Generator定义迭代器。

你应该使用一个C++习语:前向迭代器。这使您可以使用C++语法糖并支持标准库。下面是一个最小示例:

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};}
};

它可以与C++98方式一起使用:

using range = Range<0, 10, 2>;
auto r = range{};
for (range::iterator it = r.begin() ; it != r.end() ; ++it) {
std::cout << *it << 'n';
}

或者使用新的范围循环:

for (auto n : Range<0, 10, 2>{}) {
std::cout << n << 'n';
}

在与 stl 的交界处:

std::copy(std::begin(r), std::end(r), std::back_inserter(v));

演示:http://coliru.stacked-crooked.com/a/35ad4ce16428e65d

如果你想要你最初要求的通用生成器(而不是稍后添加的更简单的用例(,可以设置这样的东西:

template <typename T>
struct Generator {
Generator() {}
explicit Generator(std::function<std::optional<T>()> f_) : f(f_), v(f()) {}
Generator(Generator<T> const &) = default;
Generator(Generator<T> &&) = default;
Generator<T>& operator=(Generator<T> const &) = default;
Generator<T>& operator=(Generator<T> &&) = default;
bool operator==(Generator<T> const &rhs) {
return (!v) && (!rhs.v); // only compare equal if both at end
}
bool operator!=(Generator<T> const &rhs) { return !(*this == rhs); }
Generator<T>& operator++() {
v = f();
return *this;
}
Generator<T> operator++(int) {
auto tmp = *this;
++*this;
return tmp;
}
// throw `std::bad_optional_access` if you try to dereference an end iterator
T const& operator*() const {
return v.value();
}
private:
std::function<std::optional<T>()> f;
std::optional<T> v;
};

如果您有 C++17(如果没有,请使用 Boost 或手动跟踪有效性(。使用它所需的开始/结束函数看起来像

template <typename T>
Generator<T> generate_begin(std::function<std::optional<T>()> f) { return Generator<T>(f); }
template <typename T>
Generator<T> generate_end(std::function<std::optional<T>()>) { return Generator<T>(); }

现在对于合适的函数foo您可以像普通输入运算符一样使用它:

auto sum = std::accumulate(generate_begin(foo), generate_end(foo), 0);

我省略了应该在 YSC 的答案中Generator定义的迭代器特征 - 它们应该是如下所示的东西(operator*应该返回reference,你应该添加operator->等。

// iterator traits
using difference_type = int;
using value_type = T;
using pointer = const T*;
using reference = const T&;
using iterator_category = std::input_iterator_tag;

基于范围 for 循环是关于迭代器实现 begin((、end(( 和 operator++。

所以生成器必须实现它们。

template<typename T>
struct generator {
T first;
T last;
struct iterator {
using iterator_category = std::input_iterator_tag;
using value_type = T;
using difference_type = std::ptrdiff_t;
using pointer = T *;
using reference = T &;
T value;
iterator(T &value) : value(value) {}
iterator &operator++() {
++value;
return *this;
}
iterator operator++(int) = delete;
bool operator==(const iterator &rhs) { return value == rhs.value; }
bool operator!=(const iterator &rhs) { return !(*this == rhs); }
const reference operator *() { return value; }
const pointer operator->() const { return std::addressof(value); }
};
iterator begin() { return iterator(first); }
iterator end() { return iterator(last); }
};

然后添加一个实例化生成器的函数,你就完成了

template<typename T>
generator<T> range(T start, T end) {
return generator<T>{ start, end };
}
for (auto i : range(0, 10))
{
}

您描述的用例(范围串联等(可能会证明对库的依赖是合理的,因此这里有一个基于 range-v3 的解决方案,即将进入 C++20。您可以轻松地遍历整数值,此处从 0 到 10,步长为 2,

#include <range/v3/all.hpp>
using namespace ranges;
for (auto i : view::ints(0, 11) | view::stride(2))
std::cout << i << "n";

或者用浮点值实现一个类似的循环(注意 [from, to] 在这里是一个封闭范围,第三个参数表示步数(

for (auto f : view::linear_distribute(1.25f, 2.5f, 10))
std::cout << f << "n";

在串联方面,库开始大放异彩:

const std::vector world{32, 119, 111, 114, 108, 100};
for (auto i : view::concat("hello", world))
std::cout << char(i);
std::cout << "n";

请注意,上面的代码片段使用-std=c++17编译。库仅为标头。