Lygiaverčio Python generatoriaus struktūra C ++

Turiu „Python“ kodą, kurį turiu imituoti C ++. Man nereikia jokio konkretaus sprendimo (pvz., Sprendimai, grindžiami bendru požiūriu, nors jie taip pat bus priimtini atsakymai), tiesiog turiu kažkaip atgaminti semantiką.

Python

Tai yra pagrindinis sekos generatorius, akivaizdžiai per didelis, kad būtų išsaugota materializuota versija.

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

Tikslas yra išlaikyti du pirmiau minėtos sekos atvejus ir rūšiuoti juos per pusiau blokuojančius, bet gabalus. Toliau pateiktame pavyzdyje first_pass naudoja porų seką, kad inicijuotų buferį, o second_pass tą pačią seką ir vėl apdoroja buferį.

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

C ++

Vienintelis dalykas, kurį galiu išspręsti C + +, yra imituoti yield su C ++ coroutines, bet aš neradau jokių gerų nuorodų, kaip tai padaryti. Mane domina ir alternatyvūs (ne bendri) šios problemos sprendimai. Aš neturiu pakankamai atminties biudžeto, kad išlaikytumėte sekos kopiją tarp leidimų.

74
30 янв. Noah Watkins nustatė sausio 30 d 2012-01-30 06:58 '12 at 6:58 2012-01-30 06:58
@ 9 atsakymai

Generatoriai egzistuoja C + +, tiesiog po kitu pavadinimu: Input Iterators. Pavyzdžiui, skaitymas iš std::cin panašus į char generatorių.

Jums tereikia suprasti, ką daro generatorius:

  • yra duomenų blob: vietiniai kintamieji apibrėžia būseną
  • yra init metodas
  • yra toks metodas
  • yra signalų perdavimas

Jūsų trivialiame pavyzdyje tai gana paprasta. Konceptualiai:

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

Žinoma, baigiame tai kaip teisingą klasę:

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

Taigi, taip ... gal C ++ yra šiek tiek išsamesnė :)

50
30 янв. Matthieu M. atsakymas 2012-01-30 10:34 '12, 10:34, 2012-01-30 10:34

C ++ turi iteratorių, tačiau iteratoriaus įgyvendinimas nėra paprastas: reikia kreiptis į iterator_facade modelį, kuris turėtų padėti įgyvendinti iteratorius ir iteratorių suderinamus generatorius.

Kartais kartotinė coroutine gali būti naudojama iteratoriui įgyvendinti .

PS Taip pat žr. Šį straipsnį , kuris vadinamas Christophero M. Kolhoffo ir Boost.Coroutine perjungimu iš Oliverio Kowalke. Oliver Kowalke darbas yra tęsinys Boost.Coroutine Giovanni P. Deretta.

PS Manau, galite rašyti ir generatorius su 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; 
border=0

Arba su funkcija:

 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 Čia su „ Mordor“ įdiegtas generatorius lydės:

 #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 окт. atsakymas pateiktas ArtemGr 05 okt. 2012-10-05 00:08 '12 - 0:08 2012-10-05 00:08

Kadangi „ Boost.Coroutine2“ dabar jį palaiko labai gerai (aš jį rasiu, nes norėjau išspręsti tą pačią yield problemą), aš siunčiu kodą į C ++, kuris atitinka jūsų pradinį ketinimą:

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

Šiame pavyzdyje pair_sequence nepateikia jokių papildomų argumentų. Jei reikia, std::bind arba lambda turėtų būti naudojamas funkcijų objektui sukurti, kuris užima tik vieną argumentą (iš push_type ), kai jis perduodamas konstruktoriui coro_t::pull_type .

15
06 авг. Atsakymas pateikiamas Yongwei Wu 06 rug. 2016-08-06 11:51 '16 at 11:51 am 2016-08-06 11:51

Turėtumėte tikrinti generatorius „std :: experimental“ sistemoje „Visual Studio 2015“, pavyzdžiui: https://blogs.msdn.microsoft.com/vcblog/2014/11/12/resumable-functions-in-c/

Manau, kad būtent tai jūs ieškote. Generiniai generatoriai turėtų būti prieinami C ++ 17, nes tai tik „Microsoft VC“ eksperimentinė funkcija.

3
08 февр. Atsakymas pateikiamas Ogen Shobai 08 vasario 8 d. 2016-02-08 08:30 '16, 8:30, 2016-02-08 08:30

Jei tai reikia padaryti tik nedideliam skaičiui specifinių generatorių, galite juos įgyvendinti kaip klasę, kur elementų duomenys yra lygūs Python generatoriaus funkcijos vietiniams kintamiesiems. Tada turite šią funkciją, kuri grąžina kitą dalyką, kurį generatorius duos, atnaujindamas vidinę būseną.

Iš esmės tai panaši į tai, kaip įgyvendinami „Python“ generatoriai. Pagrindinis skirtumas yra tas, kad jie gali prisiminti generatoriaus funkcijos, kaip „vidinės būsenos“, kompensuojamąjį kodą, o tai reiškia, kad generatoriai gali būti rašomi kaip ciklai su išėjimais. Turėtumėte apskaičiuoti kitą vertę iš ankstesnio. Jei tai yra pair_sequence , tai yra gana trivialus. Tai gali būti ne sudėtingiems generatoriams.

Taip pat reikia nurodyti užbaigimo būdą. Jei grįšite į „rodyklę“, o NULL neturėtų būti tinkama galiojanti vertė, galite naudoti NULL rodyklę kaip užbaigimo indikatorių. Priešingu atveju jums reikia signalo iš diapazono ribų.

2
30 янв. Ben atsakymą pateikė sausio 30 d. 2012-01-30 07:43 '12, 7:43, 2012-01-30 07:43

Kažkas panašaus yra labai panašus:

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

Naudojant () operatorių, reikia tik tai, ką norite daryti su šiuo generatoriumi, taip pat galite ją sukurti kaip srautą ir įsitikinti, kad jis prisitaikys prie, pavyzdžiui, srovės_diktatoriaus.

1
03 июля '13 в 3:17 2013-07-03 03:17 atsakymas pateikiamas lūpos liepos 03 d. 13:17 2013-07-03 03:17

Visi atsakymai, susiję su savo iteratoriaus rašymu, yra visiškai klaidingi. Tokie atsakymai visiškai praleidžia Python generatorių esmę (viena iš didžiausių ir unikaliausių kalbos funkcijų). Svarbiausia generatoriuose yra tai, kad spektaklis užima vietą, kur jis sustojo. Tai neįvyksta su iteratoriais. Vietoj to turite rankiniu būdu saugoti būsenos informaciją, kad, kai dar kartą būtų pakviestas „++“ operatorius arba * operatorius, teisinga informacija yra įdiegta pačiame kito funkcijų skambučio pradžioje. Štai kodėl savo C ++ iteratoriaus rašymas yra didžiulis skausmas; kadangi generatoriai yra elegantiški ir lengvai skaitomi + rašomi.

Nemanau, kad yra geras analogas „Python“ generatoriams gimtojoje „C ++“, bent jau dar nėra (yra kambarys, kuris duoda žemes C + + 17 ). Jūs galite gauti kažką panašaus, susisiekdami su pašaliniu asmeniu (pvz., Yongwei Boost pasiūlymu) arba sulenkdami savo.

Sakyčiau, kad artimiausia gimtoji C ++ yra siūlai. Sriegis gali išlaikyti sustabdytą vietinių kintamųjų rinkinį ir gali toliau veikti ten, kur jis buvo sustabdytas, labai panašus į generatorius, tačiau reikia atsisakyti papildomos infrastruktūros, palaikančios ryšį tarp generatoriaus objekto ir jo skambintojo. Pavyzdžiui.

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

Šis sprendimas turi keletą trūkumų:

  • Temos yra „brangios“. Dauguma žmonių mano, kad tai yra „ekstravagantiškas“ srautų naudojimas, ypač kai generatorius yra toks paprastas.
  • Yra keletas valymo veiksmų, kuriuos reikia prisiminti. Jie gali būti automatizuoti, tačiau jums reikės dar daugiau infrastruktūros, kuri vėl gali būti laikoma „pernelyg ekstravagantiška“. Bet kokiu atveju, reikalingas valymas:
    • gatvėje> uždaryti ()
    • generator.join ()
  • Tai neleidžia sustabdyti generatoriaus. Galite pridėti kai kuriuos pakeitimus, kad pridėtumėte šį gebėjimą, tačiau jis papildo kodą. Jis niekada nebūtų toks pat švarus kaip Python derliaus ataskaita.
  • Be 2, yra ir kitų šablonų bitų, kurie reikalingi kiekvieną kartą, kai norite sukurti „generatoriaus objekto pavyzdį“:
    • Kanalo parametras * išjungiamas
    • Papildomi kintamieji pagrindiniuose: poros, generatorius
1
01 янв. atsakymas pateikiamas tik kodo 01 sausio. 2017-01-01 03:09 '17 at 3:09 2017-01-01 03:09

Kaip funkcija imituoja kamino sąvoką, generatoriai imituoja eilės koncepciją. Likusi dalis yra semantika.

Kaip šalutinę pastabą, visada galite imituoti eilę su krūva, naudodami operacijų krūvą, o ne duomenis. Tai praktiškai reiškia, kad galite įgyvendinti tipo eigą eilėje, grąžinti porą, kurios antroji vertė turi tokią funkciją, kuri turi būti vadinama arba rodo, kad mes neturime vertybių. Tačiau tai dažniau nei tai, kas duoda pajamas, palyginti su pajamomis. Tai leidžia jums imituoti bet kokių reikšmių, o ne vienodų vertybių, kurias tikitės iš generatoriaus, eilę, bet neprarandant visos vidinės eilės.

Konkrečiai kalbant, kadangi C ++ neturi natūralaus abstrakcijos eilėje, turite naudoti konstrukcijas, kurios veda eilę viduje. Taigi atsakymas, kuris davė pavyzdį su iteratoriais, yra vertas šios koncepcijos įgyvendinimas.

Tai, ką tai praktiškai reiškia, yra tai, kad jūs galite atlikti kažką su balso apvalkalo funkcija, jei norite ką nors greitai ir tada vartoti eilės reikšmes taip pat, kaip jūs suvartosite iš generatoriaus gautas vertes .

0
01 янв. Atsakymą pateikė Dmitrijus Rubanovičius 01 m. 2017-01-01 05:16 '17 at 5:16 2017-01-01 05:16

Kažkas panašaus:

Naudojimo pavyzdys:

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

Bus spausdinami numeriai nuo 0 iki 99.

0
09 мая '17 в 5:20 2017-05-09 05:20 atsakymą pateikė smac89 gegužės 09 '17, 5:20 2017-05-09 05:20

Kiti klausimai, susiję su žymėmis, arba užduoti klausimą