Odpowiednik C++ range / xrange w STL czy boost?

Czy istnieje odpowiednik C++ dla python xrange generator w STL lub boost?

Xrange zasadniczo generuje zwiększoną liczbę przy każdym wywołaniu do operatora++. konstruktor wygląda tak:

xrange(first, last, increment)

Miałem nadzieję zrobić coś takiego używając boost dla każdego:

foreach(int i, xrange(N))

Jestem świadomy pętli for. moim zdaniem są za duże.

Dzięki

Moje powody:

Moim głównym powodem, dla którego chcę to zrobić, jest to, że używam mowy do tekstu oprogramowanie i pętla programowania zwykle jest trudna, nawet jeśli używa się dopełnienia kodu. Znacznie wydajniejsze jest posiadanie zaimków.

Wiele pętli zaczyna się od zera i zwiększa się o jeden, co jest domyślne dla zakresu. Uważam, że Python construct jest bardziej intuicyjny

 for(int i = 0; i < N; ++i)
 foreach(int i, range(N))

Funkcje, które muszą przyjmować range jako argument:

 Function(int start, int and, int inc);
 function(xrange r);

Rozumiem różnice między językami, jednak jeśli konkretna konstrukcja w Pythonie jest dla mnie bardzo przydatna i może być zaimplementowana wydajnie w C++, nie widzę powodu, aby go nie używać. Dla każdego konstruktu jest obce C++, jak również, jednak ludzie go używać.

Umieściłem swoją implementację na dole strony, a także przykładowe użycie.

W mojej domenie pracuję z tablicami wielowymiarowymi, często tensorami rangi 4. tak więc często kończę z 4 zagnieżdżonymi pętlami z różnymi zakresami/przyrostami, aby obliczyć normalizację, indeksy itp. niekoniecznie są to pętle wydajności, a bardziej interesuje mnie poprawność czytelność i możliwość modyfikacji.

Na przykład

int function(int ifirst, int ilast, int jfirst, int jlast, ...);
versus
int function(range irange, range jrange, ...);

W powyższym przypadku, jeśli potrzebne są różne strids, musisz przekazać więcej zmiennych, zmodyfikować pętle itp. ostatecznie kończy się z masą liczb całkowitych / prawie identycznych pętli.

Foreach i range dokładnie rozwiązują mój problem. znajomość przeciętnego programisty C++ nie jest wysoko na mojej liście problemów - domena problemowa jest raczej niejasna, jest dużo meta-programowania, SSE, generowanego kodu.
Author: Anycorn, 2009-12-30

10 answers

Boost ma counting_iterator z tego co wiem, co wydaje się pozwalać tylko na zwiększanie w krokach 1. Dla pełnej funkcjonalności xrange może być konieczne zaimplementowanie podobnego iteratora samodzielnie.

W sumie może to wyglądać tak (edit: dodano iterator dla trzeciego przeciążenia xrange, aby bawić się z fasadą iteratora Boosta): {]}
#include <iostream>
#include <boost/iterator/counting_iterator.hpp>
#include <boost/range/iterator_range.hpp>
#include <boost/foreach.hpp>
#include <boost/iterator/iterator_facade.hpp>
#include <cassert>

template <class T>
boost::iterator_range<boost::counting_iterator<T> > xrange(T to)
{
    //these assertions are somewhat problematic:
    //might produce warnings, if T is unsigned
    assert(T() <= to);
    return boost::make_iterator_range(boost::counting_iterator<T>(0), boost::counting_iterator<T>(to));
}

template <class T>
boost::iterator_range<boost::counting_iterator<T> > xrange(T from, T to)
{
    assert(from <= to);
    return boost::make_iterator_range(boost::counting_iterator<T>(from), boost::counting_iterator<T>(to));
}

//iterator that can do increments in steps (positive and negative)
template <class T>
class xrange_iterator:
    public boost::iterator_facade<xrange_iterator<T>, const T, std::forward_iterator_tag>
{
    T value, incr;
public:
    xrange_iterator(T value, T incr = T()): value(value), incr(incr) {}
private:
    friend class boost::iterator_core_access;
    void increment() { value += incr; }
    bool equal(const xrange_iterator& other) const
    {
        //this is probably somewhat problematic, assuming that the "end iterator"
        //is always the right-hand value?
        return (incr >= 0 && value >= other.value) || (incr < 0 && value <= other.value);
    }
    const T& dereference() const { return value; }
};

template <class T>
boost::iterator_range<xrange_iterator<T> > xrange(T from, T to, T increment)
{
    assert((increment >= T() && from <= to) || (increment < T() && from >= to));
    return boost::make_iterator_range(xrange_iterator<T>(from, increment), xrange_iterator<T>(to));
}

int main()
{
    BOOST_FOREACH(int i, xrange(10)) {
        std::cout << i << ' ';
    }
    BOOST_FOREACH(int i, xrange(10, 20)) {
        std::cout << i << ' ';
    }
    std::cout << '\n';
    BOOST_FOREACH(int i, xrange(0, 46, 5)) {
        std::cout << i << ' ';
    }
    BOOST_FOREACH(int i, xrange(10, 0, -1)) {
        std::cout << i << ' ';
    }
}

Jak mawiają inni, nie widzę, żeby to kupowało ci za dużo nad normalną pętlą for.

 20
Author: UncleBens,
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
2009-12-30 12:33:52

Boost irange powinno być odpowiedź (ThxPaul Brannan)

Dodaję swoją odpowiedź, aby podać przekonujący przykład bardzo Ważne przypadki użycia, które nie są dobrze obsługiwane przez ręczne zapętlanie:

#include <boost/range/adaptors.hpp>
#include <boost/range/algorithm.hpp>
#include <boost/range/irange.hpp>

using namespace boost::adaptors;

static int mod7(int v) 
    { return v % 7; }

int main() 
{
    std::vector<int> v;

    boost::copy(
            boost::irange(1,100) | transformed(mod7), 
            std::back_inserter(v));

    boost::sort(v);

    boost::copy(
            v | reversed | uniqued, 
            std::ostream_iterator<int>(std::cout, ", "));
}

Wyjście: 6, 5, 4, 3, 2, 1, 0,

Zauważ, jak to przypomina Generatory / comprehensions (functional languages) i enumerables (C#)

Update pomyślałem, że wspomnę o następujących (wysoce nieelastyczny) idiom, na który pozwala C++11:

for (int x : {1,2,3,4,5,6,7})
    std::cout << x << std::endl;

Oczywiście, że możesz się z nim ożenić irange:

for (int x : boost::irange(1,8))
    std::cout << x << std::endl;
 51
Author: sehe,
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-01-26 13:42:56

std::iota (jeszcze nie znormalizowana) jest trochę jak range. Nie czyni jednak rzeczy krótszymi ani jaśniejszymi niż wyraźna pętla for.

#include <algorithm>
#include <iostream>
#include <iterator>
#include <numeric>
#include <vector>
int main() {
    std::vector<int> nums(5);
    std::iota(nums.begin(), nums.end(), 1);
    std::copy(nums.begin(), nums.end(),
            std::ostream_iterator<int>(std::cout, " "));
    std::cout << std::endl;
    return 0;
}

Kompilować z g++ -std=c++0x; to drukuje "1 2 3 4 5 \n".

 3
Author: ephemient,
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
2009-12-30 04:03:28

Cóż, oto, co napisałem, ponieważ nie wydaje się być. generator nie używa pamięci wewnętrznej poza pojedynczą liczbą całkowitą. obiekt range może być przekazywany i używany w zagnieżdżonych pętlach.

Jest mały przypadek testowy.

#include "iostream"
#include "foreach.hpp"

#include "boost/iterator/iterator_categories.hpp"

struct range {

  struct iterator_type {
    typedef int value_type;
    typedef int difference_type;
    typedef boost::single_pass_traversal_tag iterator_category;
    typedef const value_type* pointer;
    typedef const value_type & reference;

    mutable value_type value;
    const difference_type increment;

    iterator_type(value_type value, difference_type increment = 0)
      : value(value), increment(increment) {}

    bool operator==(const iterator_type &rhs) const {
      return value >= rhs.value;
    }
    value_type operator++() const { return value += increment; }
    operator pointer() const { return &value; }
  };

  typedef iterator_type iterator;
  typedef const iterator_type const_iterator;

  int first_, last_, increment_;

  range(int last) : first_(0), last_(last), increment_(1) {}
  range(int first, int last, int increment = 1)
    : first_(first), last_(last), increment_(increment) {}

  iterator begin() const {return iterator(first_, increment_);}
  iterator end() const {return iterator(last_);}
};

int test(const range & range0, const range & range1){
  foreach(int i, range0) {
    foreach(int j, range1) {
      std::cout << i << " " << j << "\n";
    }
  }
}

int main() {
  test(range(6), range(3, 10, 3));
}
 2
Author: Anycorn,
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-09-23 07:38:18

Moim głównym powodem jest to, że używam mowy do tekstu, a pętla programowania zwykle jest trudna, nawet jeśli używam uzupełniania kodu. Znacznie wydajniejsze jest posiadanie zaimków.

To ma sens. Ale czy proste makro nie może rozwiązać tego problemu? #define for_i_to(N, body) for (int i = 0; i < N; ++i) { body } Albo coś podobnego. Lub całkowicie unikaj pętli i korzystaj z algorytmów biblioteki standardowej. (std::for_each(range.begin(), rang.end(), myfunctor()) wydaje się łatwiejsze do wymówienia)

Wiele pętli zaczyna się od zera i przyrost o jeden, co jest domyślne dla zakresu. Uważam, że Python construct jest bardziej intuicyjny

Mylisz się. Wersja Pythona jest bardziej intuicyjna dla programisty Pythona. I to Może być bardziej intuicyjne dla nie-programisty. Ale piszesz kod C++. Twoim celem powinno być uczynienie go intuicyjnym dla programisty C++. A programista C++ zna for - pętle i zna standardowe algorytmy biblioteki. Trzymaj się tego. (Lub trzymaj się pisania Pythona)

Funkcje które muszą przyjąć range jako argument:

Function(int start, int and, int inc);
function(xrange r);

Lub idiomatyczna wersja C++:

template <typename iter_type>
void function(iter_type first, iter_type last);
W C++ zakresy są reprezentowane przez pary iteratorów. Nie liczby całkowite. Jeśli masz zamiar pisać kod w nowym języku, szanuj konwencje tego języka. Nawet jeśli oznacza to, że musisz się dostosować i zmienić pewne nawyki. Jeśli nie chcesz tego zrobić, trzymaj się języka, który znasz.

Próba zamiany języka X na język Y jest zawsze niewłaściwą rzeczą zrób. To nie działa i zmyli programistów języka X, którzy będą utrzymywać (lub po prostu czytać) twój kod.

 1
Author: jalf,
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
2009-12-30 04:08:40

Ponieważ zacząłem używać BOOST_FOREACH dla całej mojej iteracji (prawdopodobnie błędny pomysł, ale to już inna historia), oto kolejne użycie klasy range aaa:

std::vector<int> vec;
// ... fill the vector ...
BOOST_FOREACH(size_t idx, make_range(0, vec.size()))
{
  // ... do some stuff ...
}

(tak, zakres powinien być templalizowany, abym mógł używać z nim typów całkowych zdefiniowanych przez użytkownika)

I oto make_range ():

template<typename T>
range<T> make_range(T const & start, T const & end)
{
  return range<T>(start, end);
}

Zobacz także:

Http://groups.google.com/group/boost-list/browse_thread/thread/3e11117be9639bd

I:

Https://svn.boost.org/trac/boost/ticket/3469

Które proponują podobne rozwiązania.

I właśnie znalazłem boost:: integer_range; w powyższym przykładzie kod wyglądałby następująco:

using namespace boost;
std::vector<int> vec;
// ... fill the vector ...
BOOST_FOREACH(size_t idx, make_integer_range(0, vec.size()))
{
  // ... do some stuff ...
}
 1
Author: Paul Brannan,
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
2011-08-31 10:31:23

Pętla for obsługuje prawie automatycznie:

for(int loop=first;loop < last;loop += increment)
{
  /// Do Stuff.
}
 0
Author: Martin York,
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
2009-12-29 22:49:02

Próbujesz wprowadzić idiom Pythona do C++. To nienaturalne. Użycie

for(int i=initVal;i<range;i+=increment) 
{ 
    /*loop body*/
}

Aby to osiągnąć. W Pythonie forma for(i in xrange(init, rng, increment)) jest niezbędna, ponieważ Python nie zapewnia prostej pętli for, tylko konstrukcję typu for-each. Więc możesz iterację tylko nad sekwencją lub generatorem. Jest to po prostu niepotrzebna i prawie na pewno zła praktyka w języku, który zapewnia składnię for(;;).

EDIT: jako zupełnie niezalecany bok, najbliżej mogę dostać się do składni for i xrange(first, last, inc) w C++ jest:

#include <cstdio>

using namespace std;

int xrange(unsigned int last, unsigned int first=0, unsigned int inc=1)
{
    static int i = first;
    return (i<last)?i+=inc:i=0;
}

int main()
{
    while(int i=xrange(10, 0, 1))
        printf("in loop at i=%d\n",i);
}

Nie to, że podczas gdy to pętla prawidłową liczbę razy, i waha się od first+inc do last, A NIE first do last-inc jak w Pythonie. Ponadto funkcja może działać niezawodnie tylko z wartościami unsigned, ponieważ gdy i==0, pętla while zakończy działanie. Czy Nie używać tej funkcji. Dodałem ten kod tylko po to, aby zademonstrować, że coś takiego jest rzeczywiście możliwe. Istnieje również kilka innych zastrzeżeń i gotchas (kod naprawdę nie będzie działać na pierwszy!=0 przy kolejnej funkcji wywołania, na przykład)

 0
Author: Chinmay Kanchi,
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
2009-12-29 23:37:17

Ponieważ nie wiemy, do czego chcesz to wykorzystać, zakładam, że twój test jest reprezentatywny. A wtedy proste dla pętli są o wiele prostsze i bardziej czytelne:

int main() {
  for (int i = 0; i <= 6; ++i){
    for (int j = 3; j <= 10; j += 3){
      std::cout << i << " " << j << "\n";
    }
  }
}

Programista C++ może wejść z ulicy i zrozumieć tę funkcję bez konieczności szukania złożonych klas gdzie indziej. I to 5 linijek zamiast 60. Oczywiście, jeśli masz 400 pętli dokładnie takich jak te, to tak, zaoszczędziłbyś trochę wysiłku, używając swojego zakresu obiekt. Możesz też zawinąć te dwie pętle wewnątrz funkcji pomocniczej i wywołać , Kiedy tylko potrzebujesz.

Nie mamy wystarczająco dużo informacji, aby powiedzieć, co jest nie tak z pętlami simple for, lub co byłoby odpowiednim zamiennikiem. Pętle tutaj rozwiązują problem z dużo mniejszą złożonością i znacznie mniejszą liczbą linii kodu niż przykładowa implementacja. Jeśli jest to złe rozwiązanie, powiedz nam swoje wymagania (jak w jaki problem trzeba rozwiązać, zamiast "chcę python-style loops in C++")

 0
Author: jalf,
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
2009-12-31 00:42:23

Keep it simple, make a stupid macro;

#define for_range(VARNAME, START, STOP, INCREMENT) \
for(int VARNAME = START, int STOP_ = STOP, INCREMENT_ = INCREMENT; VARNAME != STOP_; VARNAME += INCREMENT_)

I używać jako;

for_range(i, 10, 5, -1)
  cout << i << endl;
 -1
Author: Viktor Sehr,
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
2011-06-29 11:43:19