web-dev-qa-db-ja.com

複数の「for」ループを作成するきれいな方法

複数の次元を持つ配列の場合、通常、その次元ごとにforループを記述する必要があります。例えば:

vector< vector< vector<int> > > A;

for (int k=0; k<A.size(); k++)
{
    for (int i=0; i<A[k].size(); i++)
    {
        for (int j=0; j<A[k][i].size(); j++)
        {
            do_something_on_A(A[k][i][j]);
        }
    }
}

double B[10][8][5];
for (int k=0; k<10; k++)
{
    for (int i=0; i<8; i++)
    {
        for (int j=0; j<5; j++)
        {
            do_something_on_B(B[k][i][j]);
        }
    }
}

この種のfor-for-forループは、コードに頻繁に見られます。マクロを使用してfor-for-forループを定義すると、この種のコードを毎回書き換える必要がなくなります。これを行うためのより良い方法はありますか?

97
C. Wang

最初のことは、そのようなデータ構造を使用しないことです。 3次元マトリックスが必要な場合は、次のように定義します。

class Matrix3D
{
    int x;
    int y;
    int z;
    std::vector<int> myData;
public:
    //  ...
    int& operator()( int i, int j, int k )
    {
        return myData[ ((i * y) + j) * z + k ];
    }
};

または、[][][]を使用してインデックスを作成する場合は、プロキシを返すoperator[]が必要です。

これを行った後、提示したとおりに繰り返し処理する必要がある場合は、それをサポートするイテレーターを公開します。

class Matrix3D
{
    //  as above...
    typedef std::vector<int>::iterator iterator;
    iterator begin() { return myData.begin(); }
    iterator end()   { return myData.end();   }
};

次に、あなたは書くだけです:

for ( Matrix3D::iterator iter = m.begin(); iter != m.end(); ++ iter ) {
    //  ...
}

(あるいは単に:

for ( auto& elem: m ) {
}

c ++ 11を使用している場合。)

また、そのような反復中に3つのインデックスが必要な場合は、それらを公開するイテレータを作成できます。

class Matrix3D
{
    //  ...
    class iterator : private std::vector<int>::iterator
    {
        Matrix3D const* owner;
    public:
        iterator( Matrix3D const* owner,
                  std::vector<int>::iterator iter )
            : std::vector<int>::iterator( iter )
            , owner( owner )
        {
        }
        using std::vector<int>::iterator::operator++;
        //  and so on for all of the iterator operations...
        int i() const
        {
            ((*this) -  owner->myData.begin()) / (owner->y * owner->z);
        }
        //  ...
    };
};
280
James Kanze

マクロを使用してforループを非表示にすることは、ほんの少しの文字を保存するだけで、非常に混乱する可能性があります。代わりに range-for ループを使用します:

for (auto& k : A)
    for (auto& i : k)
        for (auto& j : i)
            do_something_on_A(j);

もちろん、auto& with const auto&実際にデータを変更していない場合。

43
Shoe

このような何かが役立ちます:

 template <typename Container, typename Function>
 void for_each3d(const Container &container, Function function)
 {
     for (const auto &i: container)
         for (const auto &j: i)
             for (const auto &k: j)
                 function(k);
 }

 int main()
 {
     vector< vector< vector<int> > > A;     
     for_each3d(A, [](int i){ std::cout << i << std::endl; });

     double B[10][8][5] = { /* ... */ };
     for_each3d(B, [](double i){ std::cout << i << std::endl; });
 }

N項にするには、テンプレートマジックが必要です。まず、SFINAE構造を作成して、この値かコンテナかを区別する必要があります。値のデフォルト実装、および配列と各コンテナタイプの特殊化。 @Zetaは、ネストされたiteratorタイプによって標準コンテナを決定できることを示しています(理想的には、タイプが範囲ベースforで使用できるかどうかを確認する必要があります)。

 template <typename T>
 struct has_iterator
 {
     template <typename C>
     constexpr static std::true_type test(typename C::iterator *);

     template <typename>
     constexpr static std::false_type test(...);

     constexpr static bool value = std::is_same<
         std::true_type, decltype(test<typename std::remove_reference<T>::type>(0))
     >::value;
 };

 template <typename T>
 struct is_container : has_iterator<T> {};

 template <typename T>
 struct is_container<T[]> : std::true_type {};

 template <typename T, std::size_t N>
 struct is_container<T[N]> : std::true_type {}; 

 template <class... Args>
 struct is_container<std::vector<Args...>> : std::true_type {};

for_eachの実装は簡単です。デフォルトの関数はfunctionを呼び出します:

 template <typename Value, typename Function>
 typename std::enable_if<!is_container<Value>::value, void>::type
 rfor_each(const Value &value, Function function)
 {
     function(value);
 }

そして、特殊化はそれ自身を再帰的に呼び出します:

 template <typename Container, typename Function>
 typename std::enable_if<is_container<Container>::value, void>::type
 rfor_each(const Container &container, Function function)
 {
     for (const auto &i: container)
         rfor_each(i, function);
 }

そして出来上がり:

 int main()
 {
     using namespace std;
     vector< vector< vector<int> > > A;
     A.resize(3, vector<vector<int> >(3, vector<int>(3, 5)));
     rfor_each(A, [](int i){ std::cout << i << ", "; });
     // 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,

     std::cout << std::endl;
     double B[3][3] = { { 1. } };
     rfor_each(B, [](double i){ std::cout << i << ", "; });
     // 1, 0, 0, 0, 0, 0, 0, 0, 0,
 }

また、これはポインター(ヒープに割り当てられた配列)では機能しません。

21
fasked

ほとんどの答えは、C++を理解できない構文拡張機能、IMHOに変換する方法を示しています。

任意のテンプレートまたはマクロを定義することにより、他のプログラマーに、他の難読化されたコードを隠すように設計された難読化されたコードを理解させるだけです。
明確なセマンティクスでオブジェクトを定義するという仕事を避けるために、コードを読むすべての人にテンプレートの専門知識を持たせます。

3次元配列のような生データを使用することに決めた場合は、そのまま使用するか、データに理解可能な意味を与えるクラスを定義します。

for (auto& k : A)
for (auto& i : k)
for (auto& current_A : i)
    do_something_on_A(current_A);

明示的なセマンティクスを持たないintのvectorのvectorのvectorの不可解な定義と一致しています。

17
kuroi neko
#include "stdio.h"

#define FOR(i, from, to)    for(int i = from; i < to; ++i)
#define TRIPLE_FOR(i, j, k, i_from, i_to, j_from, j_to, k_from, k_to)   FOR(i, i_from, i_to) FOR(j, j_from, j_to) FOR(k, k_from, k_to)

int main()
{
    TRIPLE_FOR(i, j, k, 0, 3, 0, 4, 0, 2)
    {
        printf("i: %d, j: %d, k: %d\n", i, j, k);
    }
    return 0;
}

更新:あなたはそれを要求したことを知っていますが、それを使用しない方が良いでしょう:)

10
FreeNickname

1つのアイデアは、インデックスを作成するすべてのマルチインデックスタプルのセットを「含む」反復可能な擬似コンテナクラスを作成することです。ここには実装に時間がかかりすぎるため、実装はありませんが、アイデアはあなたが書くことができるはずだということです...

multi_index mi (10, 8, 5);
  //  The pseudo-container whose iterators give {0,0,0}, {0,0,1}, ...

for (auto i : mi)
{
  //  In here, use i[0], i[1] and i[2] to access the three index values.
}
5
Steve314

ここには、入力がコンテナであるかどうかを検出して、再帰的に機能する多くの回答があります。代わりに、現在のレイヤーが関数と同じタイプかどうかを検出しないのはなぜですか?それははるかに単純で、より強力な機能を可能にします:

//This is roughly what we want for values
template<class input_type, class func_type> 
void rfor_each(input_type&& input, func_type&& func) 
{ func(input);}

//This is roughly what we want for containers
template<class input_type, class func_type>
void rfor_each(input_type&& input, func_type&& func) 
{ for(auto&& i : input) rfor_each(i, func);}

ただし、これにより(明らかに)あいまいなエラーが発生します。 SFINAEを使用して、現在の入力が関数に収まるかどうかを検出します

//Compiler knows to only use this if it can pass input to func
template<class input_type, class func_type>
auto rfor_each(input_type&& input, func_type&& func) ->decltype(func(input)) 
{ return func(input);}

//Otherwise, it always uses this one
template<class input_type, class func_type>
void rfor_each(input_type&& input, func_type&& func) 
{ for(auto&& i : input) rfor_each(i, func);}

これにより、コンテナが正しく処理されるようになりましたが、コンパイラーは、関数に渡すことができるinput_typesのあいまいさを依然として考慮しています。そこで、標準のC++ 03トリックを使用して、2番目よりも最初の関数を優先させ、ゼロを渡し、受け入れてintを優先させ、他の関数を優先させます...

template<class input_type, class func_type>
auto rfor_each(input_type&& input, func_type&& func, int) ->decltype(func(input)) 
{ return func(input);}

//passing the zero causes it to look for a function that takes an int
//and only uses ... if it absolutely has to 
template<class input_type, class func_type>
void rfor_each(input_type&& input, func_type&& func, ...) 
{ for(auto&& i : input) rfor_each(i, func, 0);}

それでおしまい。 6つの比較的単純なコード行。他のすべての回答とは異なり、値、行、またはその他のサブユニットを反復処理できます。

#include <iostream>
int main()
 {

     std::cout << std::endl;
     double B[3][3] = { { 1.2 } };
     rfor_each(B[1], [](double&v){v = 5;}); //iterate over doubles
     auto write = [](double (&i)[3]) //iterate over rows
         {
             std::cout << "{";
             for(double d : i) 
                 std::cout << d << ", ";
             std::cout << "}\n";
         };
     rfor_each(B, write );
 };

コンパイルと実行の証明 here および here

C++ 11でより便利な構文が必要な場合は、マクロを追加できます。 (以下はテストされていません)

template<class container>
struct container_unroller {
    container& c;
    container_unroller(container& c_) :c(c_) {}
    template<class lambda>
    void operator <=(lambda&& l) {rfor_each(c, l);}
};
#define FOR_NESTED(type, index, container) container_unroller(container) <= [](type& index) 
//note that this can't handle functions, function pointers, raw arrays, or other complex bits

int main() {
     double B[3][3] = { { 1.2 } };
     FOR_NESTED(double, v, B) {
         std::cout << v << ", ";
     }
}
4
Mooing Duck

この答えは次のステートメントで警告します:これは実際の配列で操作している場合にのみ機能します-std::vectorを使用した例では機能しません。

各アイテムの位置を気にせずに、多次元配列のすべての要素で同じ操作を実行している場合、配列が連続したメモリ位置に配置されているという事実を利用して、全体を1つとして扱うことができます大きな一次元配列。たとえば、2番目の例ですべての要素に2.0を掛けたい場合:

double B[3][3][3];
// ... set the values somehow
double* begin = &B[0][0][0];     // get a pointer to the first element
double* const end = &B[3][0][0]; // get a (const) pointer past the last element
for (; end > begin; ++begin) {
    (*begin) *= 2.0;
}

上記のアプローチを使用すると、いくつかの「適切な」C++テクニックも使用できることに注意してください。

double do_something(double d) {
    return d * 2.0;
}

...

double B[3][3][3];
// ... set the values somehow
double* begin = &B[0][0][0];  // get a pointer to the first element
double* end = &B[3][0][0];    // get a pointer past the last element

std::transform(begin, end, begin, do_something);

Iこのアプローチは一般的にお勧めしません(ジェフリーの答えのようなものを好む)、それはあなたの配列のために定義されたサイズに依存します役立つことがあります。

3
icabod

作業を行うための算術マジックベースのループを誰も提案していないことに、私はちょっとショックを受けました。 C。Wang はネストされたループのないソリューションを探しているため、私は1つを提案します:

double B[10][8][5];
int index = 0;

while (index < (10 * 8 * 5))
{
    const int x = index % 10,
              y = (index / 10) % 10,
              z = index / 100;

    do_something_on_B(B[x][y][z]);
    ++index;
}

さて、このアプローチはエレガントで柔軟ではないため、すべてのプロセスをテンプレート関数にまとめることができます。

template <typename F, typename T, int X, int Y, int Z>
void iterate_all(T (&xyz)[X][Y][Z], F func)
{
    const int limit = X * Y * Z;
    int index = 0;

    while (index < limit)
    {
        const int x = index % X,
                  y = (index / X) % Y,
                  z = index / (X * Y);

        func(xyz[x][y][z]);
        ++index;
    }
}

このテンプレート関数は、ネストされたループの形式でも表現できます。

template <typename F, typename T, int X, int Y, int Z>
void iterate_all(T (&xyz)[X][Y][Z], F func)
{
    for (auto &yz : xyz)
    {
        for (auto &z : yz)
        {
            for (auto &v : z)
            {
                func(v);
            }
        }
    }
}

また、任意のサイズと関数名の3D配列を提供するために使用できます。これにより、パラメーターの演theにより各次元のサイズを数えるのが困難になります。

int main()
{
    int A[10][8][5] = {{{0, 1}, {2, 3}}, {{4, 5}, {6, 7}}};
    int B[7][99][8] = {{{0, 1}, {2, 3}}, {{4, 5}, {6, 7}}};

    iterate_all(A, do_something_on_A);
    iterate_all(B, do_something_on_B);

    return 0;
}

より汎用的に

しかし、3D配列でのみ機能するため、柔軟性が不足していますが、SFINAEを使用すると、任意の次元の配列で作業を行うことができます。最初に rank の配列を反復するテンプレート関数が必要です1:

template<typename F, typename A>
typename std::enable_if< std::rank<A>::value == 1 >::type
iterate_all(A &xyz, F func)
{
    for (auto &v : xyz)
    {
        func(v);
    }
}

そして、再帰を実行して、任意のランクの配列を反復する別のもの:

template<typename F, typename A>
typename std::enable_if< std::rank<A>::value != 1 >::type
iterate_all(A &xyz, F func)
{
    for (auto &v : xyz)
    {
        iterate_all(v, func);
    }
}

これにより、任意の次元の任意のサイズの配列のすべての次元のすべての要素を繰り返すことができます。


std::vectorの操作

複数のネストされたベクトルの場合、ソリューションは任意のサイズの任意のサイズの配列の1つに似ていますが、SFINAEなしでは、最初にstd::vectorsを反復して目的の関数を呼び出すテンプレート関数が必要です。

template <typename F, typename T, template<typename, typename> class V>
void iterate_all(V<T, std::allocator<T>> &xyz, F func)
{
    for (auto &v : xyz)
    {
        func(v);
    }
}

そして、ベクトルのあらゆる種類のベクトルを反復し、自分自身を呼び出す別のテンプレート関数:

template <typename F, typename T, template<typename, typename> class V> 
void iterate_all(V<V<T, std::allocator<T>>, std::allocator<V<T, std::allocator<T>>>> &xyz, F func)
{
    for (auto &v : xyz)
    {
        iterate_all(v, func);
    }
}

ネストレベルに関係なく、iterate_allは、値のベクトルバージョンがより良い一致であり、再帰性を終了しない限り、ベクトルのベクトルバージョンを呼び出します。

int main()
{
    using V0 = std::vector< std::vector< std::vector<int> > >;
    using V1 = std::vector< std::vector< std::vector< std::vector< std::vector<int> > > > >;

    V0 A0 =   {{{0, 1}, {2, 3}}, {{4, 5}, {6, 7}}};
    V1 A1 = {{{{{9, 8}, {7, 6}}, {{5, 4}, {3, 2}}}}};

    iterate_all(A0, do_something_on_A);
    iterate_all(A1, do_something_on_A);

    return 0;
}

関数本体は非常にシンプルで簡単だと思います...コンパイラがこのループを展開できるかどうか疑問に思います(ほとんどのコンパイラが最初の例を展開できると確信しています)。

ライブデモはこちら を参照してください。

それが役に立てば幸い。

2
PaperBirdMaster

これらの行に沿って何かを使用します(その擬似コードですが、考え方は変わりません)。パターンを抽出して1回ループし、毎回異なる関数を適用します。

doOn( structure A, operator o)
{
    for (int k=0; k<A.size(); k++)
    {
            for (int i=0; i<A[k].size(); i++)
            {
                for (int j=0; j<A[k][i].size(); j++)
                {
                        o.actOn(A[k][i][j]);
                }
            }
    }
}

doOn(a, function12)
doOn(a, function13)
1
RobAu

ネストされたforループに固執します!

ここで提案するすべての方法には、可読性または柔軟性の点で不利な点があります。

外部ループの処理に内部ループの結果を使用する必要がある場合はどうなりますか?内部ループ内の外部ループの値が必要な場合はどうなりますか?ほとんどの「カプセル化」メソッドはここで失敗します。

私を信じて、ネストされたforループを「クリーンアップ」するいくつかの試みを見てきましたが、最終的にはネストされたループが実際に最もクリーンで柔軟なソリューションであることがわかりました。

1
James Anderson

最も内側のループにステートメントがあるだけで、コードの過度に冗長な性質に関する懸念がある場合に試してみたいことがあるのは、別の空白スキームを使用することです。これは、すべてが1行に収まるようにforループを十分にコンパクトに記述できる場合にのみ機能します。

最初の例では、次のように書き直します。

vector< vector< vector<int> > > A;
int i,j,k;
for(k=0;k<A.size();k++) for(i=0;i<A[k].size();i++) for(j=0;j<A[k][i].size();j++) {
    do_something_on_A(A[k][i][j]);
}

これは、外側のループで関数を呼び出しているため、それをプッシュすることです。不要な空白をすべて削除しましたが、無難な場合があります。

2番目の例の方がはるかに優れています。

double B[10][8][5];
int i,j,k;

for(k=0;k<10;k++) for(i=0;i<8;i++) for(j=0;j<5;j++) {
    do_something_on_B(B[k][i][j]);
}

これは、使用するのとは異なる空白の規則かもしれませんが、C/C++を超える知識(マクロ規則など)を必要とせず、マクロのようなトリックを必要としないコンパクトな結果を実現します。

マクロが本当に必要な場合は、次のような方法でこれをさらに一歩進めることができます。

#define FOR3(a,b,c,d,e,f,g,h,i) for(a;b;c) for(d;e;f) for(g;h;i)

2番目の例を次のように変更します。

double B[10][8][5];
int i,j,k;

FOR3(k=0,k<10,k++,i=0,i<8,i++,j=0,j<5,j++) {
    do_something_on_B(B[k][i][j]);
}

そして、最初の例もより良い運賃:

vector< vector< vector<int> > > A;
int i,j,k;
FOR3(k=0,k<A.size(),k++,i=0,i<A[k].size(),i++,j=0,j<A[k][i].size(),j++) {
    do_something_on_A(A[k][i][j]);
}

どの文がどの文に対応するかをかなり簡単に伝えることができれば幸いです。また、コンマに注意してください。コンマは、forsの単一の句で使用できません。

0
Michael

私が使用したテクニックの1つはテンプレートです。例えば。:

_template<typename T> void do_something_on_A(std::vector<T> &vec) {
    for (auto& i : vec) { // can use a simple for loop in C++03
        do_something_on_A(i);
    }
}

void do_something_on_A(int &val) {
    // this is where your `do_something_on_A` method goes
}
_

次に、メインコードでdo_something_on_A(A)を呼び出すだけです。テンプレート関数は、ディメンションごとに1回作成され、最初は_T = std::vector<std::vector<int>>_で、2回目は_T = std::vector<int>_で作成されます。

必要に応じて、_std::function_(またはC++ 03の関数のようなオブジェクト)を2番目の引数として使用して、これをより一般的にすることができます。

_template<typename T> void do_something_on_vec(std::vector<T> &vec, std::function &func) {
    for (auto& i : vec) { // can use a simple for loop in C++03
        do_something_on_vec(i, func);
    }
}

template<typename T> void do_something_on_vec(T &val, std::function &func) {
    func(val);
}
_

次に、次のように呼び出します。

_do_something_on_vec(A, std::function(do_something_on_A));
_

最初の関数は、型に_std::vector_が含まれるすべてのものに対してより一致するため、関数は同じシグネチャを持っていますが、これは機能します。

0
JoshG79

次のような1つのループでインデックスを生成できます(A、B、Cは次元です):

int A = 4, B = 3, C = 3;
for(int i=0; i<A*B*C; ++i)
{
    int a = i/(B*C);
    int b = (i-((B*C)*(i/(B*C))))/C;
    int c = i%C;
}
0
janek

以下は、反復可能なすべてを処理するC++ 11実装です。他のソリューションは、_::iterator_ typedefまたは配列を持つコンテナに制限されています。しかし、_for_each_は、コンテナではなく反復に関するものです。

SFINAEを_is_iterable_特性の単一のスポットに分離します。ディスパッチ(要素とイテラブルの間)はタグディスパッチを介して行われますが、これはより明確なソリューションです。

要素に適用されるコンテナと関数はすべて完全に転送され、constとnon _constの両方が範囲とファンクターにアクセスできます。

_#include <utility>
#include <iterator>
_

私が実装しているテンプレート関数。他のすべては、詳細ネームスペースに入れることができます。

_template<typename C, typename F>
void for_each_flat( C&& c, F&& f );
_

タグのディスパッチはSFINAEよりもずっとクリーンです。これら2つは、反復可能オブジェクトと反復不可能オブジェクトにそれぞれ使用されます。最初の最後の反復では完全な転送を使用できますが、私は怠け者です:

_template<typename C, typename F>
void for_each_flat_helper( C&& c, F&& f, std::true_type /*is_iterable*/ ) {
  for( auto&& x : std::forward<C>(c) )
    for_each_flat(std::forward<decltype(x)>(x), f);
}
template<typename D, typename F>
void for_each_flat_helper( D&& data, F&& f, std::false_type /*is_iterable*/ ) {
  std::forward<F>(f)(std::forward<D>(data));
}
_

これは、_is_iterable_を記述するために必要な定型文です。詳細名前空間のbeginおよびendで引数依存のルックアップを行います。これは、for( auto x : y )ループが適切に行うことをエミュレートします。

_namespace adl_aux {
  using std::begin; using std::end;
  template<typename C> decltype( begin( std::declval<C>() ) ) adl_begin(C&&);
  template<typename C> decltype( end( std::declval<C>() ) ) adl_end(C&&);
}
using adl_aux::adl_begin;
using adl_aux::adl_end;
_

TypeSinkは、コードが有効かどうかをテストするのに役立ちます。 _TypeSink< decltype(_ code _) >_を実行し、codeが有効な場合、式はvoidになります。コードが有効でない場合、SFINAEが起動し、スペシャライゼーションがブロックされます。

_template<typename> struct type_sink {typedef void type;};
template<typename T> using TypeSink = typename type_sink<T>::type;

template<typename T, typename=void>
struct is_iterable:std::false_type{};
template<typename T>
struct is_iterable<T, TypeSink< decltype( adl_begin( std::declval<T>() ) ) >>:std::true_type{};
_

beginのみをテストします。 _adl_end_テストも実行できます。

_for_each_flat_の最終的な実装は、非常にシンプルになります。

_template<typename C, typename F>
void for_each_flat( C&& c, F&& f ) {
  for_each_flat_helper( std::forward<C>(c), std::forward<F>(f), is_iterable<C>() );
}        
_

実例

これは一番下の方法です:しっかりしたトップの答えを手に入れることをお気軽に。もっと良いテクニックをいくつか使用したかっただけです!