等效于 C 到 Python 生成器模式

新手上路,请多包涵

我有一些需要在 C++ 中模仿的示例 Python 代码。我不需要任何特定的解决方案(例如基于协同程序的产量解决方案,尽管它们也是可以接受的答案),我只需要以某种方式重现语义。

Python

这是一个基本的序列生成器,显然太大而无法存储物化版本。

 def pair_sequence():
    for i in range(2**32):
        for j in range(2**32):
            yield (i, j)

目标是维护上述序列的两个实例,并以半同步的方式迭代它们,但以块的形式进行。在下面的示例中, first_pass 使用对序列来初始化缓冲区,而 second_pass 重新生成 相同的精确序列 并再次处理缓冲区。

 def run():
    seq1 = pair_sequence()
    seq2 = pair_sequence()

    buffer = [0] * 1000
    first_pass(seq1, buffer)
    second_pass(seq2, buffer)
    ... repeat ...

C++

对于 C++ 中的解决方案,我唯一能找到的就是模仿 yield 与 C++ 协程,但我还没有找到任何关于如何做到这一点的好的参考。我也对这个问题的替代(非通用)解决方案感兴趣。我没有足够的内存预算来保留传递之间序列的副本。

原文由 Noah Watkins 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 650
2 个回答

生成器存在于 C++ 中,只是以另一个名称: _输入迭代器_。例如,从 std::cin 读取类似于 char 的生成器。

您只需要了解生成器的作用:

  • 有一团数据:局部变量定义了一个 状态
  • 有一个init方法
  • 有一个“下一个”方法
  • 有一种方法可以发出终止信号

在您的简单示例中,这很容易。从概念上讲:

 struct State { unsigned i, j; };

State make();

void next(State&);

bool isDone(State const&);

当然,我们将其包装为一个适当的类:

 class PairSequence:
    // (implicit aliases)
    public std::iterator<
        std::input_iterator_tag,
        std::pair<unsigned, unsigned>
    >
{
  // C++03
  typedef void (PairSequence::*BoolLike)();
  void non_comparable();
public:
  // C++11 (explicit aliases)
  using iterator_category = std::input_iterator_tag;
  using value_type = std::pair<unsigned, unsigned>;
  using reference = value_type const&;
  using pointer = value_type const*;
  using difference_type = ptrdiff_t;

  // C++03 (explicit aliases)
  typedef std::input_iterator_tag iterator_category;
  typedef std::pair<unsigned, unsigned> value_type;
  typedef value_type const& reference;
  typedef value_type const* pointer;
  typedef ptrdiff_t difference_type;

  PairSequence(): done(false) {}

  // C++11
  explicit operator bool() const { return !done; }

  // C++03
  // Safe Bool idiom
  operator BoolLike() const {
    return done ? 0 : &PairSequence::non_comparable;
  }

  reference operator*() const { return ij; }
  pointer operator->() const { return &ij; }

  PairSequence& operator++() {
    static unsigned const Max = std::numeric_limts<unsigned>::max();

    assert(!done);

    if (ij.second != Max) { ++ij.second; return *this; }
    if (ij.first != Max) { ij.second = 0; ++ij.first; return *this; }

    done = true;
    return *this;
  }

  PairSequence operator++(int) {
    PairSequence const tmp(*this);
    ++*this;
    return tmp;
  }

private:
  bool done;
  value_type ij;
};

所以嗯嗯…可能是 C++ 有点冗长:)

原文由 Matthieu M. 发布,翻译遵循 CC BY-SA 3.0 许可协议

可以使用简单的 goto 语句进行 yield 评论。因为很简单,所以我用 C 写的。

您在生成器函数中所要做的就是:

  • 所有变量都声明为静态
  • 最后的产量出口用标签记忆
  • 变量在函数结束时重新初始化

例子 :

 #include <stdio.h>

typedef struct {
    int i, j;
} Pair;

// the function generate_pairs  can generate values in successive calls.
// - all variables are declared as static
// - last yield exit is memorized with a label
// - variables are reinitialized at the end of function
Pair* generate_pairs(int imax, int jmax)
{
    // all local variable are declared static. So they are declared at the beginning
    static int i = 0;
    static int j = 0;
    static Pair p;
    // the exit position is marked with a label
    static enum {EBEGIN, EYIELD1} tag_goto = EBEGIN;

    // I goto to the last exit position
    if (tag_goto == EYIELD1)
        goto TYIELD1;


    for (i=0; i<imax; i++)   {
        for (j=0; j<jmax; j++)   {
            p.i = i;   p.j = -j;

            // I manage the yield comportment
            tag_goto = EYIELD1;
            return &p;
            TYIELD1 : ;
        }
        j = 0;
    }

    // reinitialization of variables
    i = 0;   j = 0;   // in fact this reinitialization is not useful in this example
    tag_goto = EBEGIN;

    // NULL means ends of generator
    return NULL;
}

int main()
{
    for (Pair *p = generate_pairs(2,4); p != NULL; p = generate_pairs(2,4))
    {
        printf("%d,%d\n",p->i,p->j);
    }
    printf("end\n");
    return 0;
}

原文由 Arno Bozo 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题