С++ диапазон/эквивалент xrange в STL или boost?

Есть ли эквивалент С++ для генератора Python Xrange в STL или boost?

xrange в основном генерирует добавочное число с каждым вызовом оператора ++. конструктор выглядит так:

xrange(first, last, increment)

надеялся сделать что-то подобное, используя boost для каждого:

foreach(int i, xrange(N))

я. я знаю цикл for. по-моему, они слишком много шаблонов.

Спасибо

мои причины:

Основная причина, по которой я хочу сделать это, - это то, что я использую речь в текстовом программном обеспечении, а обычный цикл программирования затруднен даже при использовании завершения кода. Гораздо эффективнее иметь произносимые конструкции.

много циклов начинаются с нуля и приращения на единицу, что по умолчанию для диапазона. Я нахожу конструкцию python более интуитивной

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

которые должны принимать диапазон в качестве аргумента:

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

Я понимаю различия между языками, однако, если конкретная конструкция в python очень полезна для меня и может быть эффективно реализована на С++, я не вижу причин не использовать ее. Для каждой конструкции чуждо С++, но люди ее используют.

Я помещаю свою реализацию в нижней части страницы, а также пример использования.

в моем домене я работаю с многомерными массивами, часто ранг 4 тензора. поэтому я часто бывал с четырьмя вложенными циклами с разными диапазонами/приращениями для вычисления нормализации, индексов и т.д., это не обязательно петли производительности, и я больше заинтересован в удобочитаемости и способности изменять.

например

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

В приведенном выше случае, если нужны разные strids, вам нужно передать больше переменных, изменить петли и т.д., в итоге вы получите массу целых чисел/почти одинаковых циклов.

foreach и диапазон точно решают мою проблему. знакомство с средним программистом на С++ не очень велико в моем списке проблем - проблемный домен довольно неясен, существует много метапрограмм, собственный SSE-код сгенерированный.

Ответ 1

Насколько я знаю, Boost имеет counting_iterator, который, по-видимому, позволяет только увеличивать с шагом 1. Для полной функциональности xrange вы можете необходимо реализовать аналогичный итератор самостоятельно.

В целом это может выглядеть так: (добавьте итератор для третьей перегрузки xrange, чтобы поиграть с фасадом итератора boost):

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

Как говорят другие, я не вижу, чтобы вы много покупали за нормальный цикл.

Ответ 2

Boost irange должен быть действительно ответом (спасибоPaul Brannan)

Я добавляю свой ответ, чтобы предоставить убедительный пример очень допустимых прецедентов, которые не обслуживаются вручную с помощью ручного цикла:

#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, ", "));
}

Выход: 6, 5, 4, 3, 2, 1, 0,

Обратите внимание, что это похоже на генераторы/понятия (функциональные языки) и перечисления (С#)

Обновление Я просто подумал, что я бы назвал следующую (очень негибкую) идиому, которую С++ 11 позволяет:

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

Конечно, вы можете выйти замуж за него с помощью irange:

for (int x : boost::irange(1,8))
    std::cout << x << std::endl;

Ответ 3

std::iota (еще не стандартизованный) похож на range. Однако не делает вещи короче или яснее, чем явный цикл 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;
}

Скомпилировать с помощью g++ -std=c++0x; это печатает "1 2 3 4 5 \n".

Ответ 4

Хорошо, вот что я написал, поскольку, похоже, нет. генератор не использует никакого внутреннего хранилища, кроме единственного целого. объект диапазона может передаваться и использоваться в вложенных циклах.

существует небольшой тестовый пример.

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

Ответ 5

Основная причина, по которой я хочу сделать это, - это то, что я использую речь в текстовом программном обеспечении, а обычный цикл программирования затруднен даже при использовании завершения кода. Гораздо эффективнее иметь произносимые конструкции.

Это имеет смысл. Но не мог ли простой макрос решить эту проблему? #define for_i_to(N, body) for (int i = 0; i < N; ++i) { body }

или что-то подобное. Или полностью избегайте цикла и используйте стандартные библиотечные алгоритмы. (std::for_each(range.begin(), rang.end(), myfunctor()) кажется проще произносить)

много циклов начинаются с нуля и приращения на единицу, что по умолчанию для диапазона. Я нахожу конструкцию python более интуитивной

Ты ошибаешься. Версия Python более интуитивно понятна программисту Python. И может быть более интуитивным для не-программиста. Но вы пишете код на С++. Ваша цель должна быть интуитивной для программиста на С++. И программисты на С++ знают for -loops и знают стандартные алгоритмы библиотеки. Придерживайтесь их использования. (Или придерживаться написания Python)

которые должны принимать диапазон в качестве аргумента:

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

Или идиоматическая версия С++:

template <typename iter_type>
void function(iter_type first, iter_type last);

В С++ диапазоны представлены парами итераторов. Не целые числа. Если вы собираетесь писать код на новом языке, соблюдайте соглашения этого языка. Даже если это означает, что вам нужно адаптироваться и изменить некоторые привычки.

Если вы не хотите этого делать, придерживайтесь языка, который вам известен.

Попытка превратить язык X в язык Y - это всегда неправильно. Это не работает, и это смущает программистов языка X, которые будут поддерживать (или просто читать) ваш код.

Ответ 6

Поскольку я начал использовать BOOST_FOREACH для всей моей итерации (возможно, ошибочной идеи, но эта другая история), здесь другое использование для класса диапазона aaa:

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

(да, диапазон должен быть templatized, поэтому я могу использовать пользовательские интегральные типы с ним)

И здесь make_range():

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

См. также:

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

и

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

которые предлагают подобные решения.

И я только что нашел boost:: integer_range; с приведенным выше примером, код будет выглядеть так:

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

Ответ 7

Вы пытаетесь привести идиому python в С++. Это небрежно. Используйте

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

чтобы достичь этого. В Python форма for(i in xrange(init, rng, increment)) необходима, поскольку Python не предоставляет простой цикл for, а только конструкцию для каждого типа. Таким образом, вы можете выполнять итерацию только по последовательности или генератору. Это просто лишняя и почти наверняка плохая практика на языке, который предоставляет синтаксис for(;;).

РЕДАКТИРОВАТЬ: Как полностью не рекомендуемый в стороне, ближайший я могу получить синтаксис for i xrange(first, last, inc) в С++:

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

Не то, что, пока это повторяет правильное количество раз, я варьируется от first+inc до last и NOT first до last-inc, как в Python. Кроме того, функция может надежно работать только с значениями unsigned, так как при i==0 цикл while будет завершен. Не используйте эту функцию. Я только добавил этот код здесь, чтобы продемонстрировать, что что-то подобное действительно возможно. Есть также несколько других предостережений и gotchas (код, на самом деле, не работает для первого!= 0 при последующих вызовах функций, например)

Ответ 8

Цикл for обрабатывает это почти автоматически:

for(int loop=first;loop < last;loop += increment)
{
  /// Do Stuff.
}

Ответ 9

Поскольку мы действительно не знаем, что вы на самом деле хотите использовать для этого, я предполагаю, что ваш тестовый пример является репрезентативным. И тогда простые простые для циклов намного проще и читабельнее:

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

Программист С++ может ходить с улицы и понимать эту функцию без необходимости искать сложные классы в другом месте. И это 5 строк вместо ваших 60. Конечно, если у вас есть 400 циклов точно так же, как и эти, то да, вы бы сэкономили несколько усилий, используя свой объект диапазона. Или вы можете просто обернуть эти два цикла внутри вспомогательной функции и вызвать это, когда вам нужно.

На самом деле у нас недостаточно информации, чтобы сказать, что неправильно с простыми для циклов, или что было бы подходящей заменой. Петли здесь решают вашу проблему с гораздо меньшей сложностью и гораздо меньшим количеством строк кода, чем ваша реализация образца. Если это плохое решение, сообщите нам свои требования (как в том, какую проблему вам нужно решить, а не "Я хочу циклы стиля python в С++" )

Ответ 10

Держите его простым, сделайте глупый макрос;

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

и использовать как:

for_range(i, 10, 5, -1)
  cout << i << endl;