web-dev-qa-db-ja.com

STLまたはブーストで同等のC ++範囲/ xrange?

STLまたはブーストのいずれかにpython Xrangeジェネレーターに相当するC++はありますか?

xrangeは基本的に、++演算子を呼び出すたびに増分数を生成します。コンストラクターは次のようになります。

xrange(first, last, increment)

それぞれにブーストを使用して、このようなことをしたいと思っていました。

foreach(int i, xrange(N))

I.forループを認識しています。私の意見では、それらは定型的すぎます。

ありがとう

私の理由:

私がそうしたい主な理由は、音声認識ソフトウェアを使用しており、コード補完を使用しても、通常の方法でループをプログラミングするのが難しいためです。はっきりとした構成を持つ方がはるかに効率的です。

多くのループはゼロから始まり、範囲のデフォルトである1ずつ増加します。 pythonより直感的に構築できる

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

引数として範囲を取る必要がある関数:

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

言語間の違いは理解していますが、pythonの特定の構成が私にとって非常に有用であり、C++で効率的に実装できる場合、それを使用しない理由はわかりません。各構成についてはC++にとっても外国人ですが、人々はそれを使用しています。

実装例と使用例をページの下部に配置しました。

私のドメインでは、多次元配列を使用しています。多くの場合、ランク4のテンソルです。そのため、正規化やインデックスなどを計算するために、範囲/増分が異なる4つのネストされたループになってしまうことがよくあります。これらは必ずしもパフォーマンスループではなく、正確さの可読性と変更機能に関心があります。

例えば

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

上記では、異なるストライドが必要な場合は、より多くの変数を渡したり、ループを変更したりする必要があります。最終的には、整数の質量/ほぼ同一のループになります。

foreachとrangeは私の問題を正確に解決します。平均的なC++プログラマーへの精通度は、私の懸念事項のリストでは高くありません。問題ドメインはかなりあいまいで、メタプログラミングがたくさんあります。SSE組み込みの、生成されたコード。

37
Anycorn

Boostには counting_iterator があります。これは、1ステップの増分のみを許可しているようです。完全なxrange機能を使用するには、同様のイテレーターを自分で実装する必要があります。

全体として、次のようになります(編集:ブーストのイテレーターファサードで遊ぶために、xrangeの3番目のオーバーロード用のイテレーターを追加しました):

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

他の人が言っているように、私はこれが通常のforループをはるかに超えてあなたを買うとは思わない。

20
UncleBens

ブーストirangeが本当に答えになるはずです(ThxPaul Brannan)

答えを追加して、very有効なユースの説得力のある例を示します-手動ループではうまく機能しないケース:

#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,

これがジェネレーター/内包表記(関数型言語)および列挙型(C#)にどのように似ているかに注意してください

更新 C++ 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;
52
sehe

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"を出力します。

3
ephemient

さて、これは私が書いたものです。ないようです。ジェネレータは、単一の整数以外の内部ストレージを使用しません。 rangeオブジェクトは、ネストされたループで渡して使用できます。

小さなテストケースがあります。

#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
Anycorn

私がそうしたい主な理由は、音声認識ソフトウェアを使用しており、コード補完を使用しても、通常の方法でループをプログラミングするのが難しいためです。はっきりとした構成を持つ方がはるかに効率的です。

それは理にかなっている。しかし、単純なマクロでこの問題を解決することはできませんか? #define for_i_to(N, body) for (int i = 0; i < N; ++i) { body }

または同様のもの。または、ループを完全に回避し、標準ライブラリアルゴリズムを使用します。 (std::for_each(range.begin(), rang.end(), myfunctor())は発音しやすいようです)

多くのループはゼロから始まり、範囲のデフォルトである1ずつ増加します。 pythonより直感的に構築できる

あなたが間違っている。 PythonバージョンはPythonプログラマーにとってより直感的です。そしてそれはmayプログラマー以外の人にとってより直感的です。しかしあなたは'C++コードを書いています。あなたの目標はC++プログラマーが直感的に理解できるようにすることです。C++プログラマーはfor-ループを知っており、標準のライブラリアルゴリズムを知っています。それらの使用に固執します(またはPythonの記述に固執します)。 )

引数として範囲を取る必要がある関数:

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

または慣用的なC++バージョン:

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

C++では、範囲はイテレーターのペアで表されます。整数ではありません。新しい言語でコードを書く場合は、その言語の規則を尊重してください。たとえそれがあなたがいくつかの習慣を適応させそして変える必要があることを意味するとしても。

あなたがそれをする気がないならば、あなたが知っている言語に固執してください。

言語Xを言語Yに変えようとすることは、常に間違ったことです。それ自体は機能せず、コードを維持する(または単に読む)言語Xプログラマーを混乱させます。

1
jalf

すべての反復でBOOST_FOREACHを使用し始めたので(おそらく誤った考えですが、それは別の話です)、aaaの範囲クラスの別の使用法は次のとおりです。

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

(はい、範囲はテンプレート化する必要があります。そうすれば、ユーザー定義の整数型を使用できます)

そしてここに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 ...
}
1
Paul Brannan

C++ 20の範囲ヘッダーには iota_view これを行います:

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

出力:

1 2 3 4 5 6 7 8 9
1 2 3 4 5 6 7 8 9
0
Mike Weller

実際にこれを何に使用したいかはわからないので、テストケースは代表的なものだと思います。そして、単純なforループは、はるかに単純で読みやすくなります。

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

C++プログラマーは、他の場所で複雑なクラスを検索しなくても、通りから入って理解この関数を使用できます。そして、60行ではなく5行です。もちろん、これらとまったく同じ400のループがある場合は、そうです。範囲オブジェクトを使用することで、労力を節約できます。または、これら2つのループをヘルパー関数内にラップして、必要なときにいつでもthatを呼び出すこともできます。

単純なforループの何が問題なのか、または何が適切な代替となるのかを説明するのに十分な情報がありません。ここでのループは、サンプル実装よりもはるかに少ない複雑さとはるかに少ないコード行で問題を解決します。これが悪い解決策である場合は、要件を教えてください(「C++でPythonスタイルのループが必要」ではなく、解決する必要のある問題など)

0
jalf