web-dev-qa-db-ja.com

Python generator patternと同等のC ++

いくつかの例がありますPython C++で模倣する必要があるコード。特定のソリューションは必要ありません(コルーチンベースのyieldソリューションなど、受け入れられる答えでもありますが) )、私は単に何らかの方法でセマンティクスを再現する必要があります。

Python

これは基本的なシーケンスジェネレータであり、明らかにマテリアライズバージョンを格納するには大きすぎます。

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

目標は、上記のシーケンスの2つのインスタンスを維持し、それらを半ロックステップでチャンクで反復することです。以下の例では、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++のソリューションで見つけることができる唯一のことは、C++コルーチンを使用してyieldを模倣することですが、これを行う方法に関する適切なリファレンスは見つかりませんでした。また、この問題の代替(一般的ではない)ソリューションにも興味があります。パス間でシーケンスのコピーを保持するのに十分なメモリバジェットがありません。

98
Noah Watkins

ジェネレーターは、C++の別の名前であるInput Iteratorsにあります。たとえば、std::cinは、charのジェネレーターを持つことに似ています。

ジェネレーターの機能を理解するだけです。

  • データのblobがあります:ローカル変数はstateを定義します
  • 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++はもう少し冗長です:)

66
Matthieu M.

C++にはイテレータがありますが、イテレータの実装は簡単ではありません。 イテレータの概念 を調べて、新しいイテレータクラスを慎重に設計して実装する必要があります。ありがたいことに、Boostには iterator_facade テンプレートがあり、イテレーターとイテレーター互換ジェネレーターの実装に役立ちます。

時々 スタックレスコルーチンを使用してイテレータを実装できます

追伸 この記事 も参照してください。これは、Christopher M. KohlhoffによるswitchハックとOliver Kowalkeによる Boost.Coroutine の両方に言及しています。 Oliver Kowalkeの作品 フォローアップ on Boost.Coroutine by Giovanni P. Deretta.

追伸一種のジェネレーターを書くこともできると思います with lambdas

std::function<int()> generator = []{
  int i = 0;
  return [=]() mutable {
    return i < 10 ? i++ : -1;
  };
}();
int ret = 0; while ((ret = generator()) != -1) std::cout << "generator: " << ret << std::endl;

またはファンクターを使って:

struct generator_t {
  int i = 0;
  int operator() () {
    return i < 10 ? i++ : -1;
  }
} generator;
int ret = 0; while ((ret = generator()) != -1) std::cout << "generator: " << ret << std::endl;

追伸 Mordor コルーチンで実装されたジェネレーターは次のとおりです。

#include <iostream>
using std::cout; using std::endl;
#include <mordor/coroutine.h>
using Mordor::Coroutine; using Mordor::Fiber;

void testMordor() {
  Coroutine<int> coro ([](Coroutine<int>& self) {
    int i = 0; while (i < 9) self.yield (i++);
  });
  for (int i = coro.call(); coro.state() != Fiber::TERM; i = coro.call()) cout << i << endl;
}
43
ArtemGr

Boost.Coroutine2 は非常によくサポートしているため(まったく同じyield問題を解決したかったので見つけました)、元の意図に一致するC++コードを投稿しています。

#include <stdint.h>
#include <iostream>
#include <memory>
#include <boost/coroutine2/all.hpp>

typedef boost::coroutines2::coroutine<std::pair<uint16_t, uint16_t>> coro_t;

void pair_sequence(coro_t::Push_type& yield)
{
    uint16_t i = 0;
    uint16_t j = 0;
    for (;;) {
        for (;;) {
            yield(std::make_pair(i, j));
            if (++j == 0)
                break;
        }
        if (++i == 0)
            break;
    }
}

int main()
{
    coro_t::pull_type seq(boost::coroutines2::fixedsize_stack(),
                          pair_sequence);
    for (auto pair : seq) {
        print_pair(pair);
    }
    //while (seq) {
    //    print_pair(seq.get());
    //    seq();
    //}
}

この例では、pair_sequenceは追加の引数を取りません。必要な場合は、std::bindまたはラムダを使用して、Push_typeコンストラクターに渡されるときに(coro_t::pull_typeの)1つの引数のみをとる関数オブジェクトを生成する必要があります。

19
Yongwei Wu

独自のイテレータの作成に関係するすべての答えは完全に間違っています。このような答えは、Pythonジェネレーター(言語の最大かつユニークな機能の1つ))のポイントを見逃しています。ジェネレーターに関する最も重要なことは、実行が中断したところから再開することです。代わりに、operator ++またはoperator *が新たに呼び出されたときに、正しい情報がの先頭にあるように、状態情報を手動で保存する必要があります次の関数呼び出し:これが、独自のC++イテレータを書くのが非常に面倒な理由です;一方、ジェネレータはエレガントで、読み書きが簡単です。

PythonネイティブC++のジェネレーターには良いアナログはないと思います。少なくともまだありません( yieldはC++ 17に到達する)という噂があります =)。サードパーティに頼ることで(例えばYongweiのBoost提案)、または独自のものをローリングすることで、似たようなものを得ることができます。

ネイティブC++で最も近いのはスレッドです。スレッドは、一時停止したローカル変数のセットを維持し、ジェネレーターと非常によく似た中断したところから実行を継続できますが、ジェネレーターオブジェクトとその呼び出し元の間の通信をサポートするには、少し追加のインフラストラクチャをロールする必要があります。例えば。

// Infrastructure

template <typename Element>
class Channel { ... };

// Application

using IntPair = std::pair<int, int>;

void yield_pairs(int end_i, int end_j, Channel<IntPair>* out) {
  for (int i = 0; i < end_i; ++i) {
    for (int j = 0; j < end_j; ++j) {
      out->send(IntPair{i, j});  // "yield"
    }
  }
  out->close();
}

void MyApp() {
  Channel<IntPair> pairs;
  std::thread generator(yield_pairs, 32, 32, &pairs);
  for (IntPair pair : pairs) {
    UsePair(pair);
  }
  generator.join();
}

ただし、このソリューションにはいくつかの欠点があります。

  1. スレッドは「高価」です。ほとんどの人は、特にジェネレータが非常に単純な場合、これをスレッドの「過酷な」使用と見なします。
  2. 覚えておく必要があるクリーンアップアクションがいくつかあります。これらは自動化することもできますが、さらに多くのインフラストラクチャが必要になりますが、これも「非常に贅沢すぎる」と見なされる可能性があります。とにかく、必要なクリーンアップは次のとおりです。
    1. out-> close()
    2. generator.join()
  3. これにより、ジェネレータを停止することはできません。その機能を追加するためにいくつかの変更を加えることもできますが、コードが混乱します。 Pythonのyieldステートメントほどきれいではありません。
  4. 2に加えて、ジェネレーターオブジェクトを「インスタンス化」するたびに必要なボイラープレートの他のビットがあります。
    1. Channel *出力パラメーター
    2. メインの追加変数:ペア、ジェネレーター
4
allyourcode

Visual Studio 2015では、おそらくstd :: experimentalのジェネレーターを確認する必要があります。例: https://blogs.msdn.Microsoft.com/vcblog/2014/11/12/resumable-functions-in-c/ =

まさにあなたが探しているものだと思います。これは実験的なMicrosoft VC機能です。

比較的少数の特定のジェネレーターに対してのみこれを行う必要がある場合は、それぞれをクラスとして実装できます。メンバーデータはPythonジェネレーター関数のローカル変数と同等です。ジェネレーターが次に生成するものを返すnext関数があり、内部状態を更新します。

これは基本的にPythonジェネレーターの実装方法と似ています。大きな違いは、ジェネレーター関数のバイトコードへのオフセットを「内部状態」の一部として覚えられることです。ジェネレーターは、yieldを含むループとして記述できますが、代わりに前の値から次の値を計算する必要がありますpair_sequenceの場合、それは非常に簡単です。

終了を示す何らかの方法も必要です。返すものが「ポインターのような」ものであり、NULLが有効な降伏可能な値ではない場合、NULLポインターを終了インジケーターとして使用できます。それ以外の場合は、帯域外信号が必要です。

2
Ben

このようなものは非常に似ています:

struct pair_sequence
{
    typedef pair<unsigned int, unsigned int> result_type;
    static const unsigned int limit = numeric_limits<unsigned int>::max()

    pair_sequence() : i(0), j(0) {}

    result_type operator()()
    {
        result_type r(i, j);
        if(j < limit) j++;
        else if(i < limit)
        {
          j = 0;
          i++;
        }
        else throw out_of_range("end of iteration");
    }

    private:
        unsigned int i;
        unsigned int j;
}

Operator()の使用は、このジェネレーターで何をしたいのかという質問に過ぎません。たとえば、ストリームとしてビルドし、istream_iteratorに適合させることもできます。

1
lip

range-v を使用:

#include <iostream>
#include <Tuple>
#include <range/v3/all.hpp>

using namespace std;
using namespace ranges;

auto generator = [x = view::iota(0) | view::take(3)] {
    return view::cartesian_product(x, x);
};

int main () {
    for (auto x : generator()) {
        cout << get<0>(x) << ", " << get<1>(x) << endl;
    }

    return 0;
}
1
Engineerist

this :のようなもの

使用例:

using ull = unsigned long long;

auto main() -> int {
    for (ull val : range_t<ull>(100)) {
        std::cout << val << std::endl;
    }

    return 0;
}

0から99までの数字を印刷します

0
smac89

さて、今日はC++ 11での簡単なコレクションの実装も探していました。私が見つけたものはすべてpythonジェネレーター、またはC#のyield演算子...などのようなものから遠すぎるか、複雑すぎるので、私は失望しました。

目的は、必要な場合にのみアイテムを発行するコレクションを作成することです。

私はそれがこのようになりたかった:

auto emitter = on_range<int>(a, b).yield(
    [](int i) {
         /* do something with i */
         return i * 2;
    });

私はこの投稿を見つけました、私見のベストアンサーはboost.coroutine2についてでした Yongwei W によって。それは著者が望んだものに最も近いからです。

ブーストルーチンを学習する価値があります。そして、おそらく週末に行います。しかし、これまでのところ、非常に小さな実装を使用しています。それが誰かに役立つことを願っています。

以下は、使用例と実装です。

Example.cpp

#include <iostream>
#include "Generator.h"
int main() {
    typedef std::pair<int, int> res_t;

    auto emitter = Generator<res_t, int>::on_range(0, 3)
        .yield([](int i) {
            return std::make_pair(i, i * i);
        });

    for (auto kv : emitter) {
        std::cout << kv.first << "^2 = " << kv.second << std::endl;
    }

    return 0;
}

Generator.h

template<typename ResTy, typename IndexTy>
struct yield_function{
    typedef std::function<ResTy(IndexTy)> type;
};

template<typename ResTy, typename IndexTy>
class YieldConstIterator {
public:
    typedef IndexTy index_t;
    typedef ResTy res_t;
    typedef typename yield_function<res_t, index_t>::type yield_function_t;

    typedef YieldConstIterator<ResTy, IndexTy> mytype_t;
    typedef ResTy value_type;

    YieldConstIterator(index_t index, yield_function_t yieldFunction) :
            mIndex(index),
            mYieldFunction(yieldFunction) {}

    mytype_t &operator++() {
        ++mIndex;
        return *this;
    }

    const value_type operator*() const {
        return mYieldFunction(mIndex);
    }

    bool operator!=(const mytype_t &r) const {
        return mIndex != r.mIndex;
    }

protected:

    index_t mIndex;
    yield_function_t mYieldFunction;
};

template<typename ResTy, typename IndexTy>
class YieldIterator : public YieldConstIterator<ResTy, IndexTy> {
public:

    typedef YieldConstIterator<ResTy, IndexTy> parent_t;

    typedef IndexTy index_t;
    typedef ResTy res_t;
    typedef typename yield_function<res_t, index_t>::type yield_function_t;
    typedef ResTy value_type;

    YieldIterator(index_t index, yield_function_t yieldFunction) :
            parent_t(index, yieldFunction) {}

    value_type operator*() {
        return parent_t::mYieldFunction(parent_t::mIndex);
    }
};

template<typename IndexTy>
struct Range {
public:
    typedef IndexTy index_t;
    typedef Range<IndexTy> mytype_t;

    index_t begin;
    index_t end;
};

template<typename ResTy, typename IndexTy>
class GeneratorCollection {
public:

    typedef Range<IndexTy> range_t;

    typedef IndexTy index_t;
    typedef ResTy res_t;
    typedef typename yield_function<res_t, index_t>::type yield_function_t;
    typedef YieldIterator<ResTy, IndexTy> iterator;
    typedef YieldConstIterator<ResTy, IndexTy> const_iterator;

    GeneratorCollection(range_t range, const yield_function_t &yieldF) :
            mRange(range),
            mYieldFunction(yieldF) {}

    iterator begin() {
        return iterator(mRange.begin, mYieldFunction);
    }

    iterator end() {
        return iterator(mRange.end, mYieldFunction);
    }

    const_iterator begin() const {
        return const_iterator(mRange.begin, mYieldFunction);
    }

    const_iterator end() const {
        return const_iterator(mRange.end, mYieldFunction);
    }

private:
    range_t mRange;
    yield_function_t mYieldFunction;
};

template<typename ResTy, typename IndexTy>
class Generator {
public:
    typedef IndexTy index_t;
    typedef ResTy res_t;
    typedef typename yield_function<res_t, index_t>::type yield_function_t;

    typedef Generator<ResTy, IndexTy> mytype_t;
    typedef Range<IndexTy> parent_t;
    typedef GeneratorCollection<ResTy, IndexTy> finalized_emitter_t;
    typedef  Range<IndexTy> range_t;

protected:
    Generator(range_t range) : mRange(range) {}
public:
    static mytype_t on_range(index_t begin, index_t end) {
        return mytype_t({ begin, end });
    }

    finalized_emitter_t yield(yield_function_t f) {
        return finalized_emitter_t(mRange, f);
    }
protected:

    range_t mRange;
};      
0