Python 생성기 패턴에 해당하는 C++
저는 C++에서 모방해야 할 파이썬 코드의 예를 몇 가지 가지고 있습니다.저는 특정 솔루션(코루틴 기반 수율 솔루션과 같은)이 필요하지 않습니다. 저는 그저 어떤 식으로든 의미론을 재현할 필요가 있습니다.
파이썬
이것은 기본 시퀀스 생성기이며, 구체화된 버전을 저장하기에는 분명히 너무 큽니다.
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++ 코루틴과 함께 하지만, 나는 이것을 하는 방법에 대한 좋은 참고 자료를 찾지 못했습니다.저는 또한 이 문제에 대한 대안적인 (일반적이지 않은) 해결책에도 관심이 있습니다.패스 사이의 시퀀스 복사본을 보관할 메모리 예산이 부족합니다.
생성기는 다음과 같은 다른 이름으로 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++이 조금 더 장황할 것 같아요 :)
C++에는 반복기가 있지만 반복기를 구현하는 것은 쉽지 않습니다. 반복기 개념을 참조하고 이를 구현하기 위해 새로운 반복기 클래스를 신중하게 설계해야 합니다.고맙게도 Boost에는 반복기와 반복기 호환 생성기를 구현하는 데 도움이 될 reterator_facade 템플릿이 있습니다.
때때로 스택리스 코루틴을 사용하여 반복기를 구현할 수 있습니다.
추신. 두 가지 모두를 언급하는 이 기사를 참조하십시오.switch
크리스토퍼 M. 콜호프와 부스트에 의한 해킹.올리버 코왈크의 코루틴.올리버 코왈크의 작품은 부스트의 후속작입니다.조반니 P의 코루틴.데레타
추신: 저는 람다로 일종의 발전기를 작성할 수도 있다고 생각합니다.
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;
추신: 여기 모르도르 코루틴으로 구현된 발전기가 있습니다.
#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;
}
부스트 때부터.코루틴2는 이제 그것을 매우 잘 지원합니다(정확히 동일하게 해결하고 싶어서 찾았습니다).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
(" " " 또 는 람 사 용 하 만 인 수 의 나 합 다 니 ( / ) 는 함 객 를 야 해 생 성 체 수 하 취 다 를 하 여 ▁that of ▁should ▁or ▁object ) / ▁be ▁a ▁( ▁lambda ▁function 또 ▁argument ▁a ▁onlypush_type
), 로 전달될 때.coro_t::pull_type
시공자
자신의 반복기를 쓰는 것과 관련된 모든 답은 완전히 틀렸습니다.이러한 답변은 Python 생성기(언어의 가장 훌륭하고 고유한 기능 중 하나)의 요점을 완전히 놓치고 있습니다.발전기의 가장 중요한 점은 실행이 중단된 부분부터 시작한다는 것입니다.반복기에는 이러한 현상이 발생하지 않습니다.대신 연산자++ 또는 연산자*를 새로 호출할 때 다음 함수 호출의 맨 처음에 올바른 정보가 배치되도록 상태 정보를 수동으로 저장해야 합니다.이것이 바로 여러분만의 C++ 반복기를 쓰는 것이 엄청난 고통인 이유입니다. 반면에 발전기는 우아하고 읽기 쉽고 쓰기 쉽습니다.
네이티브 C++에서 파이썬 생성기를 위한 좋은 아날로그는 적어도 아직 없다고 생각합니다(C++17에서 양보할 것이라는 소문이 있습니다).타사(예: Yongwei's 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();
}
이 솔루션에는 몇 가지 단점이 있습니다.
- 스레드는 "비싼" 것입니다.대부분의 사람들은 특히 제너레이터가 매우 단순할 때 이것을 스레드의 "바보적인" 사용으로 간주합니다.
- 기억해야 할 정리 작업이 몇 가지 있습니다.이들은 자동화될 수 있지만, 더 많은 인프라가 필요할 것이며, 이 역시 "너무 사치스러운" 것으로 간주될 가능성이 높습니다.어쨌든 필요한 정리는 다음과 같습니다.
- 아웃->클로즈 »
- generator.vmdk
- 이렇게 하면 제너레이터를 중지할 수 없습니다.당신은 그 기능을 추가하기 위해 약간의 수정을 할 수 있지만, 그것은 코드에 혼란을 더합니다.Python의 수익률 진술만큼 깨끗하지는 않을 것입니다.
- 2 외에도 제너레이터 개체를 "인스턴스"할 때마다 필요한 보일러 플레이트 조각이 있습니다.
- 채널* 출력 매개 변수
- 주요인의 추가 변수: 쌍, 추가 변수
range-v3 사용:
#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;
}
Visual Studio 2015에서 std::experimental의 생성기를 확인해야 합니다(예: https://blogs.msdn.microsoft.com/vcblog/2014/11/12/resumable-functions-in-c/ ).
저는 그것이 바로 당신이 찾고 있는 것이라고 생각합니다.전체 생성기는 C++17에서 사용할 수 있어야 합니다. 이는 Microsoft VC의 실험 기능일 뿐이기 때문입니다.
상대적으로 적은 수의 특정 생성기에만 이 작업을 수행하면 멤버 데이터가 Python 생성기 함수의 로컬 변수와 동일한 클래스로 각각을 구현할 수 있습니다.그런 다음 생성기가 다음에 생성할 결과를 반환하고 내부 상태를 업데이트하는 다음 함수가 있습니다.
이것은 기본적으로 파이썬 생성기가 구현되는 방식과 유사하다고 생각합니다.주요 차이점은 제너레이터 기능의 바이트 코드 오프셋을 "내부 상태"의 일부로 기억할 수 있다는 것입니다. 즉, 제너레이터는 수율을 포함하는 루프로 기록될 수 있습니다.대신 이전 값에서 다음 값을 계산해야 합니다.의 당신경는의 .pair_sequence
그건 꽤 사소한 일입니다.복잡한 발전기가 아닐 수도 있습니다.
또한 종료를 표시하는 방법이 필요합니다.반환하는 항목이 "포인트와 유사한" 항목이고 NULL이 유효한 수율 값이 아닐 경우 NULL 포인터를 종료 표시기로 사용할 수 있습니다.그렇지 않으면 대역 외 신호가 필요합니다.
이와 같은 것은 매우 유사합니다.
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;
}
연산자()를 사용하는 것은 이 생성기로 무엇을 할 것인지에 대한 문제일 뿐입니다. 예를 들어 스트림으로 빌드하여 istream_iterator에 맞게 조정할 수도 있습니다.
오늘 저는 C++11에서 쉬운 수집 구현을 찾고 있었습니다.사실 저는 실망했습니다. 왜냐하면 제가 발견한 모든 것이 파이썬 생성기나 C# 수율 연산자와 너무 멀리 떨어져 있기 때문입니다.너무 복잡할 수도 있습니다.
그 목적은 필요할 때만 아이템을 방출하는 컬렉션을 만드는 것입니다.
저는 다음과 같이 되길 원했습니다.
auto emitter = on_range<int>(a, b).yield(
[](int i) {
/* do something with i */
return i * 2;
});
저는 이 게시물을 찾았습니다, IMHO 최고의 답변은 우용웨이의 boost.coroutine2에 관한 것이었습니다.작가가 원했던 것과 가장 가깝기 때문입니다.
boost courtines를 배울 가치가 있습니다.그리고 주말에 할 겁니다.하지만 지금까지는 아주 작은 구현체를 사용하고 있습니다.다른 사람에게 도움이 되길 바랍니다.
다음은 사용 및 구현의 예입니다.
예제.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;
}
발전기.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;
};
이 대답은 C에서 작동합니다(그래서 저는 C++에서도 작동한다고 생각합니다).
#include<stdint.h>
//#include<stdio.h>
#define MAX (1ll << 32) //2^32
typedef struct {
uint64_t i, j;
} Pair;
int generate_pairs(Pair* p)
{
static uint64_t i = 0;
static uint64_t j = 0;
p->i = i;
p->j = j;
if(++j == MAX)
{
j = 0;
if(++i == MAX)
{
return -1; // return -1 to indicate generator finished.
}
}
return 1; // return non -1 to indicate generator not finished.
}
int main()
{
while(1)
{
Pair p;
int fin = generate_pairs(&p);
//printf("%lld, %lld\n", p.i, p.j);
if(fin == -1)
{
//printf("end");
break;
}
}
return 0;
}
이것은 생성기를 모방하는 간단하고 객체 지향적인 방법입니다.이것은 저에게 예상대로 작동했습니다.
편집: 이전 코드가 잘못되어 업데이트했습니다.
참고: 이 코드는 주어진 질문에 대해 unt64_t 대신 unt32_t만 사용하도록 개선될 수 있습니다.
단순 이동 문으로 수율을 조정할 수 있습니다.간단하기 때문에 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;
}
이와 같은 것:
사용 예:
using ull = unsigned long long;
auto main() -> int {
for (ull val : range_t<ull>(100)) {
std::cout << val << std::endl;
}
return 0;
}
0부터 99까지의 숫자를 인쇄합니다.
함수가 스택의 개념을 시뮬레이션하는 것처럼, 생성자는 대기열의 개념을 시뮬레이션합니다.나머지는 의미론입니다.
참고로 데이터 대신 연산 스택을 사용하여 항상 스택이 있는 대기열을 시뮬레이션할 수 있습니다.실제로 의미하는 것은 쌍을 반환함으로써 큐와 같은 동작을 구현할 수 있다는 것입니다. 두 번째 값은 다음에 호출할 함수가 있거나 값이 부족함을 나타냅니다.하지만 이것은 수익률 대 수익률보다 더 일반적입니다.전체 내부 큐를 유지하지 않고 생성기에서 예상하는 동종 값이 아닌 모든 값의 큐를 시뮬레이션할 수 있습니다.
보다 구체적으로, C++는 큐에 대한 자연스러운 추상화를 가지고 있지 않기 때문에 큐를 내부적으로 구현하는 구조체를 사용해야 합니다.따라서 반복자에 대한 예를 제시한 답은 이 개념의 적절한 구현입니다.
이것이 실제로 의미하는 바는 빠른 것을 원하는 경우 베어본 대기열 기능을 사용하여 무언가를 구현한 다음 생성기에서 산출된 값을 사용하는 것처럼 대기열의 값을 사용할 수 있다는 것입니다.
언급URL : https://stackoverflow.com/questions/9059187/equivalent-c-to-python-generator-pattern
'programing' 카테고리의 다른 글
부분 문자열 형식 지정 (0) | 2023.07.19 |
---|---|
Python 3에서 수백만 개의 정규식 교체 속도 향상 (0) | 2023.07.19 |
그리드 보기 정렬:방향 정렬 항상 오름차순 (0) | 2023.07.19 |
파이썬의 다른 함수 안에서 호출자 함수 이름을 가져오는 중? (0) | 2023.07.19 |
산점도 점을 선으로 연결 - Python (0) | 2023.07.19 |