开发者

C++ range/xrange equivalent in STL or boost?

Is there C++ equivalent for python Xrange generator in either STL or boost?

xra开发者_StackOverflownge basically generates incremented number with each call to ++ operator. the constructor is like this:

xrange(first, last, increment)

was hoping to do something like this using boost for each:

foreach(int i, xrange(N))

I. am aware of the for loop. in my opinion they are too much boilerplate.

Thanks

my reasons:

my main reason for wanting to do so is because i use speech to text software, and programming loop usual way is difficult, even if using code completion. It is much more efficient to have pronounceable constructs.

many loops start with zero and increment by one, which is default for range. I find python construct more intuitive

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

functions which need to take range as argument:

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

I understand differences between languages, however if a particular construct in python is very useful for me and can be implemented efficiently in C++, I do not see a reason not to use it. For each construct is foreign to C++ as well however people use it.

I put my implementation at the bottom of the page as well the example usage.

in my domain i work with multidimensional arrays, often rank 4 tensor. so I would often end up with 4 nested loops with different ranges/increments to compute normalization, indexes, etc. those are not necessarily performance loops, and I am more concerned with correctness readability and ability to modify.

for example

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

In the above, if different strids are needed, you have to pass more variables, modify loops, etc. eventually you end up with a mass of integers/nearly identical loops.

foreach and range solve my problem exactly. familiarity to average C++ programmer is not high on my list of concerns - problem domain is a rather obscure, there is a lot of meta-programming, SSE intrinsic, generated code.


Boost irange should really be the answer (ThxPaul Brannan)

I'm adding my answer to provide a compelling example of very valid use-cases that are not served well by manual looping:

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

Output: 6, 5, 4, 3, 2, 1, 0,

Note how this resembles generators/comprehensions (functional languages) and enumerables (C#)

Update I just thought I'd mention the following (highly inflexible) idiom that C++11 allows:

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

of course you could marry it with irange:

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


Boost has counting_iterator as far as I know, which seems to allow only incrementing in steps of 1. For full xrange functionality you might need to implement a similar iterator yourself.

All in all it could look like this (edit: added an iterator for the third overload of xrange, to play around with boost's iterator facade):

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

As others are saying, I don't see this buying you much over a normal for loop.


std::iota (not yet standardized) is kinda like range. Doesn't make things any shorter or clearer than an explicit for loop, though.

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

Compile with g++ -std=c++0x; this prints "1 2 3 4 5 \n".


well, here is what i wrote, since there does not seem to be one. the generator does not use any internal storage besides single integer. range object can be passed around and used in nested loops.

there is a small test case.

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


my main reason for wanting to do so is because i use speech to text software, and programming loop usual way is difficult, even if using code completion. It is much more efficient to have pronounceable constructs.

That makes sense. But couldn't a simple macro solve this problem? #define for_i_to(N, body) for (int i = 0; i < N; ++i) { body }

or something similar. Or avoid the loop entirely and use the standard library algorithms. (std::for_each(range.begin(), rang.end(), myfunctor()) seems easier to pronounce)

many loops start with zero and increment by one, which is default for range. I find python construct more intuitive

You're wrong. The Python version is more intuitive to a Python programmer. And it may be more intuitive to a non-programmer. But you're writing C++ code. Your goal should be to make it intuitive to a C++ programmer. And C++ programmer know for-loops and they know the standard library algorithms. Stick to using those. (Or stick to writing Python)

functions which need to take range as argument:

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

Or the idiomatic C++ version:

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

In C++, ranges are represented by iterator pairs. Not integers. If you're going to write code in a new language, respect the conventions of that language. Even if it means you have to adapt and change some habits.

If you're not willing to do that, stick with the language you know.

Trying to turn language X into language Y is always the wrong thing to do. It own't work, and it'll confuse the language X programmers who are going to maintain (or just read) your code.


Since I've started to use BOOST_FOREACH for all my iteration (probably a misguided idea, but that's another story), here's another use for aaa's range class:

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

(yes, range should be templatized so I can use user-defined integral types with it)

And here's make_range():

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

See also:

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

and:

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

which propose similar solutions.

And I've just found boost::integer_range; with the above example, the code would look like:

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


C++ 20's ranges header has iota_view which does this:

#include <ranges>
#include <vector>
#include <iostream>

int main()
{
    for (int i : std::views::iota{1, 10})
        std::cout << i << ' ';

    std::cout << '\n';

    for (int i : std::views::iota(1) | std::views::take(9))
        std::cout << i << ' ';
}

Output:

1 2 3 4 5 6 7 8 9
1 2 3 4 5 6 7 8 9


Since we don't really know what you actually want to use this for, I'm assuming your test case is representative. And then plain simple for loops are a whole lot simpler and more readable:

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

A C++ programmer can walk in from the street and understand this function without having to look up complex classes elsewhere. And it's 5 lines instead of your 60. Of course if you have 400 loops exactly like these, then yes, you'd save some effort by using your range object. Or you could just wrap these two loops inside a helper function, and call that whenever you needed.

We don't really have enough information to say what's wrong with simple for loops, or what would be a suitable replacement. The loops here solve your problem with far less complexity and far fewer lines of code than your sample implementation. If this is a bad solution, tell us your requirements (as in what problem you need to solve, rather than "I want python-style loops in C++")


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_)

and use as;

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


You're trying to bring a python idiom into C++. That's unncessary. Use

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

to achieve this. In Python, the for(i in xrange(init, rng, increment)) form is necessary because Python doesn't provide a simple for loop, only a for-each type construct. So you can iterate only over a sequence or a generator. This is simply unnecessary and almost certainly bad practice in a language that provides a for(;;) syntax.

EDIT: As a completely non-recommended aside, the closest I can get to the for i xrange(first, last, inc) syntax in C++ is:

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

Not that while this loops the correct number of times, i varies from first+inc to last and NOT first to last-inc as in Python. Also, the function can only work reliably with unsigned values, as when i==0, the while loop will exit. Do not use this function. I only added this code here to demonstrate that something of the sort is indeed possible. There are also several other caveats and gotchas (the code won't really work for first!=0 on subsequent function calls, for example)

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜