Odpowiednik C++ w Pythonie wzór generatora

Mam przykładowy kod Pythona, który muszę naśladować w C++. Nie wymagam żadnych konkretnych rozwiązań (takich jak co-routine based yield solutions, chociaż byłyby one akceptowalne odpowiedzi, jak również), po prostu muszę odtworzyć semantykę w jakiś sposób.

Python

Jest to podstawowy generator sekwencji, wyraźnie zbyt duży, aby przechowywać zmaterializowaną wersję.

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

Celem jest utrzymanie dwóch instancji powyższej sekwencji i iteracja nad nimi w semi-lockstep, ale w kawałkach. W poniższym przykładzie first_pass używa sekwencji par do inicjalizacji bufora, a second_pass regeneruje dokładnie tę samą sekwencję i przetwarza bufor ponownie.

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

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

C++

Jedyną rzeczą, jaką mogę znaleźć dla rozwiązania w C++, jest naśladowanie yield z C++ corutines, ale nie znalazłem żadnego dobrego odniesienia, jak to zrobić. Interesują mnie również alternatywne (Nie ogólne) rozwiązania tego problemu. Nie mam wystarczającego budżetu pamięci, aby zachować kopię sekwencji między przejściami.

Author: Noah Watkins, 2012-01-30

10 answers

Generatory istnieją w C++, tylko pod inną nazwą: Iteratory wejściowe . Na przykład odczyt z {[2] } jest podobny do posiadania generatora char.

Po prostu musisz zrozumieć, co robi generator:]}
  • istnieje blob danych: zmienne lokalne definiują stan
  • istnieje metoda init
  • istnieje metoda "next"
  • istnieje sposób na sygnał zakończenia
W Twoim trywialnym przykładzie, to dość proste. Koncepcyjnie:
struct State { unsigned i, j; };

State make();

void next(State&);

bool isDone(State const&);

Oczywiście, zawijamy to jako odpowiednią 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 : &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;
};
Więc nuć tak... może być tak, że C++ jest nieco bardziej wyrazisty:)
 53
Author: Matthieu M.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-10-26 06:49:24

W C++ istnieją Iteratory, ale implementacja iteratora nie jest prosta: należy zapoznać się z koncepcjami iteratora i starannie zaprojektować nową klasę iteratora, aby je zaimplementować. Na szczęście Boost ma szablon iterator_facade , który powinien pomóc w implementacji iteratorów I GENERATORÓW kompatybilnych z iteratorem.

Czasamido zaimplementowania iteratora może być użyty bezstopniowy coroutine.

P. S. Zobacz też Ten artykuł , który wspomina zarówno haker Christopher M. Kohlhoff, jak i Boost.Autor: Oliver Kowalke Praca Olivera Kowalke jest kontynuacją na Boost.Coroutine by Giovanni P. Deretta.

P. S. Myślę, że można też napisać coś w rodzaju generatora z lambda :

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;

Lub z funktorem:

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;

P. S. oto generator zaimplementowany z Mordor corutines:

#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;
}
 37
Author: ArtemGr,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:34:32

Od Coroutine2 Teraz obsługuje go bardzo dobrze (znalazłem go, ponieważ chciałem rozwiązać dokładnie ten sam problem yield), zamieszczam kod C++, który pasuje do pierwotnego zamiaru:

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

W tym przykładzie pair_sequence nie pobiera dodatkowych argumentów. Jeśli zachodzi taka potrzeba, std::bind lub lambda powinny być użyte do wygenerowania obiektu funkcji, który przyjmuje tylko jeden argument (z push_type), gdy jest on przekazywany do konstruktora coro_t::pull_type.

 17
Author: Yongwei Wu,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-08-06 08:51:16

Prawdopodobnie powinieneś sprawdzić generatory w std:: experimental w Visual Studio 2015 np: https://blogs.msdn.microsoft.com/vcblog/2014/11/12/resumable-functions-in-c/

Myślę, że to dokładnie to, czego szukasz. Ogólnie Generatory powinny być dostępne w C++17, ponieważ jest to tylko eksperymentalna funkcja Microsoft VC.

 3
Author: Огњен Шобајић,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-02-08 05:30:51

Jeśli musisz to zrobić tylko dla stosunkowo niewielkiej liczby konkretnych generatorów, możesz zaimplementować każdy z nich jako klasę, w której dane członkowskie są równoważne lokalnym zmiennym funkcji generatora Pythona. Następnie masz następną funkcję, która zwraca następną rzecz, którą generator uzyska, aktualizując stan wewnętrzny, jak to robi.

Jest to zasadniczo podobne do tego, jak generatory Pythona są implementowane, jak sądzę. Główną różnicą jest to, że mogą zapamiętać przesunięcie w bajt dla funkcji generatora jako część "stanu wewnętrznego", co oznacza, że generatory mogą być zapisywane jako pętle zawierające wydajność. Musisz zamiast tego obliczyć następną wartość z poprzedniej. W przypadku twojego pair_sequence, to dość trywialne. Może nie być dla złożonych generatorów.

Musisz też wskazać terminację. Jeśli zwracany wskaźnik jest "podobny do wskaźnika", a wartość NULL nie powinna być poprawną wartością yieldable, możesz użyć wskaźnika NULL jako zakończenia wskaźnik. W przeciwnym razie potrzebujesz sygnału poza pasmem.
 2
Author: Ben,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-01-30 04:43:35

Wszystkie odpowiedzi, które wymagają napisania własnego iteratora, są całkowicie błędne. Takie odpowiedzi całkowicie umykają celowi generatorów Pythona (jednej z największych i unikalnych cech języka). Najważniejszą rzeczą w generatorach jest to, że egzekucja rozpoczyna się tam, gdzie została przerwana. Tak się nie dzieje z iteratorami. Zamiast tego, musisz ręcznie zapisać informacje o stanie, tak aby gdy operator++ lub operator * jest wywoływany od nowa, właściwa informacja jest w miejscu na samym początku następnego wywołanie funkcji. Dlatego pisanie własnego iteratora C++ jest gigantycznym bólem; podczas gdy generatory są eleganckie i łatwe do odczytu+zapisu.

Myślę, że nie ma dobrego analogu dla generatorów Pythona w natywnym C++, przynajmniej jeszcze nie (jest rummor, że yield wyląduje w C++17). Możesz uzyskać coś podobnego, uciekając się do stron trzecich (np. sugestia wzmocnienia Yongwei) lub obracając własną.

Powiedziałbym, że najbliższą rzeczą w natywnym C++ są wątki. Nić może utrzymuj zawieszony zestaw zmiennych lokalnych i możesz kontynuować wykonywanie tam, gdzie zostało przerwane, podobnie jak generatory, ale musisz uruchomić trochę dodatkowej infrastruktury, aby wspierać komunikację między obiektem generatora a jego wywołującym. Np.

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

To rozwiązanie ma jednak kilka wad:

  1. wątki są "drogie". Większość ludzi uważa to za "ekstrawaganckie" użycie wątków, zwłaszcza gdy generator jest tak prosty.
  2. tam to kilka czynności porządkowych, o których musisz pamiętać. Mogą one być zautomatyzowane, ale potrzebujesz jeszcze więcej infrastruktury, co ponownie może być postrzegane jako"zbyt ekstrawaganckie". W każdym razie, sprzątanie, które potrzebujesz, to:
    1. out - > close ()
    2. generator.join ()
  3. to nie pozwala zatrzymać generator. Możesz wprowadzić pewne modyfikacje, aby dodać tę zdolność, ale dodaje bałagan do kodu. Nigdy nie byłby tak czysty jak plon Pythona oświadczenie.
  4. oprócz 2, istnieją inne bity boilerplate, które są potrzebne za każdym razem, gdy chcesz "utworzyć instancję" obiektu generatora:
    1. parametr kanału* out
    2. dodatkowe zmienne w main: pairs, generator
 2
Author: allyourcode,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-01-01 02:18:38

Coś takiego jest bardzo podobne:

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

Użycie operatora () jest tylko kwestią tego, co chcesz zrobić z tym generatorem, możesz również zbudować go jako strumień i upewnić się, że dostosuje się do istream_iterator, na przykład.

 1
Author: lip,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-07-03 00:17:11

Tak jak funkcja symuluje pojęcie stosu, Generatory symulują pojęcie kolejki. Reszta to semantyka.

Na marginesie można zawsze symulować kolejkę za pomocą stosu operacji zamiast danych. Praktycznie oznacza to, że można zaimplementować zachowanie podobne do kolejki, zwracając parę, której druga wartość albo ma wywoływaną następną funkcję, albo wskazuje, że brakuje nam wartości. Ale to jest bardziej ogólne niż to, co daje vs return robi. Pozwala symulować kolejkę dowolnych wartości, a nie jednorodnych wartości, których oczekujesz od generatora, ale bez utrzymywania pełnej kolejki wewnętrznej.

Dokładniej, ponieważ C++ nie ma naturalnej abstrakcji dla kolejki, musisz użyć konstruktów, które implementują kolejkę wewnętrznie. Więc odpowiedzią, która dała przykład z iteratorami, jest przyzwoita implementacja koncepcji.

Praktycznie oznacza to, że można zaimplementować coś z funkcja kolejki bare-bones, jeśli chcesz czegoś szybkiego, a następnie zużywasz wartości queue tak, jak zużywasz wartości uzyskane z generatora.

 0
Author: Dmitry Rubanovich,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-01-01 02:16:20

Coś w rodzaju tego :

Przykładowe użycie:

using ull = unsigned long long;

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

    return 0;
}

Wyświetli liczby od 0 do 99

 0
Author: smac89,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-09 17:07:19

Using 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;
}
 0
Author: Engineerist,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-07-26 19:14:49