Ekvivalentna Pythonova struktura generatora za C ++

Imam uzorak Python kod koji trebam oponašati u C + +. Ne treba mi nikakvo posebno rješenje (na primjer, rješenja koja se temelje na zajedničkom pristupu, iako će također biti prihvatljivi odgovori), samo moram nekako reproducirati semantiku.

piton

Ovo je osnovni generator niza, očito prevelik za pohranjivanje materijalizirane verzije.

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

Cilj je održati dva slučaja gore navedenog slijeda i razvrstati ih kroz polu-blokiranje, ali u dijelovima. U primjeru ispod, first_pass koristi niz parova za inicijalizaciju međuspremnika, a second_pass ponovno vraća isti slijed i ponovno obrađuje me uspremnik.

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

C ++

Jedina stvar koju mogu naći riješiti u C + + je emulirati yield s C + + coroutines, ali nisam pronašao nikakve dobre reference o tome kako to učiniti. Zanimaju me i alternativna (ne uobičajena) rješenja za ovaj problem. Nemam dovoljno memorijskog proračuna da zadržim kopiju niza između prijelaza.

74
30 янв. Noah Watkins postavio je 30. siječnja 2012-01-30 06:58 '12 u 6:58 2012-01-30 06:58
@ 9 odgovora

Generatori postoje u C ++, samo pod drugim nazivom: Input Iterators. Na primjer, čitanje iz std::cin slično generatoru char .

Vi samo trebate razumjeti što generator radi:

  • postoji blob podataka: lokalne varijable definiraju stanje
  • postoji metoda init
  • postoji sljedeća metoda
  • postoji način prijenosa signala

U vašem beznačajnom primjeru to je vrlo jednostavno. konceptualno:

 struct State { unsigned i, j; }; State make(); void next(State bool isDone(State const> 

Naravno, završavamo ovo kao ispravnu klasu:

 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 :  } reference operator*() const { return ij; } pointer operator->() const { return  } 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; }; 

Dakle, da ... možda je C ++ malo detaljniji :)

50
30 янв. odgovor Matthieu M. 30. \ t 2012-01-30 10:34 '12 u 10:34 2012-01-30 10:34

C ++ ima iteratore, ali implementacija iteratora nije jednostavna: trebate se pozvati na uzorak iterator_facade , koji bi trebao pomoći implementirati iteratore i generatore koji su kompatibilni s iteratorom.

Ponekad se složeni coroutine može koristiti za implementaciju iteratora .

PS Vidi i ovaj članak , koji se naziva prekidom prekidanja Christopher M. Kolhoff i Boost.Coroutine iz Olivera Kowalkea. Rad Olivera Kowalke nastavak je Boost.Coroutine Giovannija P. Derette.

PS Mislim da možete napisati i vrstu generatora s lambdama :

 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; 
border=0

Ili pomoću funkcije:

 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; 

PS Ovdje će generator implementiran s Mordorom pratiti:

 #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; } 
36
05 окт. odgovor je dat ArtemGr 05 okt. 2012-10-05 00:08 '12 u 0:08 2012-10-05 00:08

Budući da Boost.Coroutine2 sada podržava vrlo dobro (našao sam ga jer sam htio riješiti isti problem s yield ), šaljem kod C + + koji odgovara vašoj izvornoj namjeri:

 #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(); //} } 

U ovom primjeru pair_sequence ne zahtijeva dodatne argumente. Ako je potrebno, std::bind ili lambda bi se trebali koristiti za stvaranje objekta koji uzima samo jedan argument (iz push_type ) kada je proslijeđen konstruktoru coro_t::pull_type .

15
06 авг. Odgovor je dan Yongwei Wu 06 aug. 2016-08-06 11:51 '16 u 11:51 2016-08-06 11:51

Vjerojatno biste trebali provjeriti generatore u std :: experimental u Visual Studio 2015, na primjer: https://blogs.msdn.microsoft.com/vcblog/2014/11/12/resumable-functions-in-c/

Mislim da je to upravo ono što tražite. Generički generatori bi trebali biti dostupni u C ++ 17, jer je to samo eksperimentalna značajka Microsoft VC.

3
08 февр. Odgovor je dat Ogen Shobai 08. veljače. 2016-02-08 08:30 '16 u 8:30 am 2016-02-08 08:30

Ako to trebate učiniti samo za relativno mali broj specifičnih generatora, možete ih implementirati kao klasu u kojoj su podaci o elementu ekvivalentni lokalnim varijablama funkcije Python generatora. Tada imate sljedeću funkciju, koja vraća sljedeću stvar koju će generator dati, ažurirajući unutarnje stanje kada to učini.

U osnovi, ovo je slično načinu na koji su Python generatori implementirani. Glavna razlika je u tome što mogu zapamtiti bajtnu za offset generaciju kao dio "unutarnjeg stanja", što znači da generatori mogu biti zapisani kao ciklusi koji sadrže izlaze. Trebali biste izračunati sljedeću vrijednost iz prethodne. U slučaju vašeg pair_sequence , ovo je prilično trivijalno. Ovo možda nije za složene generatore.

Također morate odrediti metodu završetka. Ako vratite "pokazivač poput" i NULL ne bi trebao biti valjana valjana vrijednost, možete koristiti NULL pokazivač kao pokazatelj dovršenosti. Inače vam je potreban signal izvan dometa.

2
30 янв. Odgovor je dao Ben 30. siječnja 2012-01-30 07:43 '12 u 7:43 2012-01-30 07:43

Nešto poput ovoga vrlo je slično:

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

Koristeći operator () je samo pitanje onoga što želite učiniti s ovim generatorom, možete ga stvoriti i kao stream i osigurati da se on, primjerice, prilagođava istream_iteratoru.

1
03 июля '13 в 3:17 2013-07-03 03:17 odgovor je dat lipanj 03 '13 u 3:17 2013-07-03 03:17

Svi odgovori vezani za pisanje vlastitog iteratora su potpuno pogrešni. Takvi odgovori u potpunosti propuste bit Python generatora (jedna od najvećih i najjedinstvenijih funkcija jezika). Najvažnija stvar kod generatora je da izvedba zauzima mjesto gdje je stala. To se ne događa s iteratorima. Umjesto toga, morate ručno pohraniti informacije o stanju, tako da kada se pozove operator ++ ili operator *, ispravne informacije su na mjestu na samom početku sljedećeg poziva funkcije. Zato je pisanje vlastitog C + + iteratora velika bol; dok su generatori elegantni i jednostavni za čitanje + pisanje.

Ne mislim da postoji dobar analog za Python generatore u izvornom C ++-u, barem ne još (postoji soba koja donosi zemlje C ++ 17 ). Tako nešto možete dobiti tako što ćete se obratiti autsajderu (na primjer, Yongwei Boost ponuda) ili sklopiti vlastitu.

Rekao bih da su najbliže u izvornom C ++ niti. Nit može održavati obustavljeni skup lokalnih varijabli i može nastaviti prikazivati ​​gdje je zaustavljen, vrlo slično generatorima, ali morate ispustiti dodatnu infrastrukturu kako bi podržali komunikaciju između objekta generatora i njegovog pozivatelja. Na primjer.

 // 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,  for (IntPair pair : pairs) { UsePair(pair); } generator.join(); } 

Ovo rješenje ima nekoliko nedostataka:

  • Teme su "skupe". Većina ljudi ovo smatra "ekstravagantnom" uporabom potoka, pogotovo kada je vaš generator tako jednostavan.
  • Postoji nekoliko akcija čišćenja koje morate zapamtiti. Mogu biti automatizirani, ali trebat će vam još više infrastrukture, što će se vjerojatno smatrati "previše ekstravagantnim". U svakom slučaju, čišćenje koje trebate:
    • za ulicu> zatvori ()
    • generator.join ()
  • To ne dopušta zaustavljanje generatora. Možete dodati neke izmjene da biste dodali ovu mogućnost, ali dodaje neredu kodu. Nikada ne bi bilo tako čisto kao Python izjava o prinosu.
  • Osim 2, postoje i drugi bitovi predloška koji su potrebni svaki put kada želite "stvoriti instancu" objekta generatora:
    • Parametar kanala * out
    • Dodatne varijable u glavnom: parovi, generator
1
01 янв. odgovor je dan svim tvojim kodom 01 jan. 2017-01-01 03:09 '17 u 3:09 2017-01-01 03:09

Baš kao što funkcija oponaša koncept stog, generatori oponašaju koncept reda čekanja. Ostalo je semantika.

Kao dodatna napomena, uvijek možete simulirati red s hrpom pomoću hrpe operacija, a ne podataka. To praktično znači da možete implementirati ponašanje tipa u redu, vraćajući par čija druga vrijednost ima ili sljedeću funkciju koja se zove ili ukazuje da nemamo vrijednosti. Ali to je češće nego ono što daje dohodak u odnosu na povratak. To vam omogućuje da simulirate red svih vrijednosti, a ne homogenih vrijednosti koje očekujete od generatora, ali bez zadržavanja punog internog reda.

Konkretnije, budući da C ++ nema prirodnu apstrakciju za red, morate koristiti konstrukte koji implementiraju red unutar. Stoga je odgovor, koji je dao primjer s iteratorima, vrijedna implementacija koncepta.

To praktično znači da možete postići nešto s funkcijom govorne ljuske, ako samo želite nešto brzo, a zatim potrošiti vrijednosti reda na isti način na koji ćete trošiti vrijednosti dobivene od generatora ,

0
01 янв. Odgovor daje Dmitry Rubanovich 01. siječnja. 2017-01-01 05:16 '17 u 5:16 2017-01-01 05:16

Nešto poput ovoga :

Primjer uporabe:

 using ull = unsigned long long; auto main() -> int { for (ull val : range_t<ull>(100)) { std::cout << val << std::endl; } return 0; } 

Otisnut će se brojevi od 0 do 99.

0
09 мая '17 в 5:20 2017-05-09 05:20 odgovor je dao smac89 Svibanj 09 '17 u 5:20 2017-05-09 05:20

Ostala pitanja o oznakama ili Ask a Question