여러 'for'루프를 작성하는 깔끔한 방법


98

여러 차원이있는 배열의 경우 일반적으로 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매번 이런 종류의 코드를 다시 작성할 필요가 없도록 매크로를 사용하여 루프 를 정의하는 방법은 무엇입니까? 이 작업을 수행하는 더 좋은 방법이 있습니까?


62
분명한 대답은 당신이 그렇지 않다는 것입니다. 매크로 (또는 다른 기술)를 사용하여 새 언어를 만들지 않습니다. 뒤에 오는 사람은 코드를 읽을 수 없습니다.
James Kanze 2014 년

17
벡터로 구성된 벡터를 가지고 있다면 그것은 잘못된 디자인의 신호입니다.
Maroun 2014 년

5
@Nim : 1 개의 평면 배열로 할 수 있습니다 (더 나은지 확실하지 않음).
Jarod42

16
난 당신이 잠재적 인 숨기려하지 않을 생각 O(n) = n^3... 코드
POY

36
@ TC1 : 그러면 읽기가 더 어려워집니다. 그것은 모두 개인적인 취향의 문제이며 실제로 여기에서 당면한 질문에 도움이되지 않습니다.
ereOn

답변:


281

첫 번째는 이러한 데이터 구조를 사용하지 않는다는 것입니다. 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이있는 경우.)

그리고 그러한 반복 중에 세 개의 인덱스가 필요한 경우이를 노출하는 반복자를 생성 할 수 있습니다.

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);
        }
        //  ...
    };
};

21
이 답변은 문제의 실제 원인을 다루는 유일한 답변이므로 훨씬 더 찬성되어야합니다.
ereOn

5
그것은 corret 대답 일 수 있지만 좋은 대답이라는 데 동의하지 않습니다. 아마도 x10 배 느린 컴파일 시간과 x10 느린 디버그 (더 많을 수도 있음) 코드를 가진 많은 비밀 템플릿 코드. 나를 위해 definitly 원래 코드는 ... 나에게 방법이 더 분명하다
Gorkem

10
@beehorf ... 그리고 훨씬 더 느립니다. C 및 C ++의 다차원 배열은 외부 차원이 중첩 배열에 대한 포인터를 저장한다는 점에서 실제로 중첩 배열이기 때문입니다. 이러한 중첩 배열은 메모리에 임의로 분산되어 프리 페치 및 캐싱을 효과적으로 무효화합니다. 누군가 vector<vector<vector<double> > >가 3 차원 필드를 나타내는 데 사용하는 코드를 작성한 예를 알고 있습니다. 위의 솔루션에 해당하는 코드를 다시 작성하면 속도가 10으로 향상되었습니다.
Michael Wild

5
@beehorf 템플릿 코드는 어디에 있습니까? (실제로 Matrix3D는 템플릿이어야하지만 매우 간단한 템플릿입니다.) 그리고 Matrix3D3D 매트릭스가 필요할 때마다 디버그 만하면되는 것은 아니므 로 디버깅 시간을 엄청나게 절약 할 수 있습니다. 명확성에 관해서는 : 어떻게 std::vector<std::vector<std::vector<int>>>더 명확 Matrix3D합니까? Matrix3D그것은 당신이 행렬을 가지고 있다는 사실 을 강요하는 것은 말할 것도없고 , 중첩 된 벡터는 비정형 일 수 있고, 위의 것은 아마도 훨씬 더 빠를 것입니다.
James Kanze 2014 년

10
@MichaelWild 물론 내 접근 방식의 진정한 장점은 클라이언트 코드를 수정하지 않고도 환경에서 더 빠른 항목에 따라 표현을 변경할 수 있다는 것입니다. 좋은 성능의 핵심은 적절한 캡슐화이므로 전체 응용 프로그램을 다시 작성하지 않고도 프로파일 러가 필요로하는 변경 사항을 적용 할 수 있습니다.
James Kanze

44

매크로를 사용하여 for루프 를 숨기는 것은 몇 개의 문자를 저장하기 위해 매우 혼란 스러울 수 있습니다. 내가 사용하는 거라고 범위-에 대한 대신 루프 :

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

물론 당신은 대체 할 수 auto&와 함께 const auto&당신이 경우, 사실, 데이터를 수정하지.


3
OP가 C ++ 11을 사용할 수 있다고 가정합니다.
Jarod42

1
@herohuyongtao 반복자의 경우. 여기에서는 좀 더 관용적 일 수 있지만 세 가지 int변수를 원하는 경우가 있습니다 .
James Kanze 2014 년

1
그리고 그게 아니야 do_something_on_A(*j)?
James Kanze 2014 년

1
@Jefffrey 아, 그래. 유형을 철자하는 또 다른 이유. (나는의 사용 추측 auto에 대한을 k하고 i정당화 될 수있다 여전히 잘못된 수준에서 문제를 해결하는 것을 제외하고, 진짜 문제는 그가 중첩 된 벡터를 사용하여 점이다..)
제임스 간제

2
@Dhara k는 인덱스 가 아닌 벡터의 전체 벡터 (참조)입니다.
Yakk-Adam Nevraumont 2014 년

21

다음과 같은 것이 도움이 될 수 있습니다.

 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-ary로 만들려면 템플릿 마법이 필요합니다. 우선이 값인지 컨테이너인지 구별하기 위해 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,
 }

또한 이것은 포인터 (힙에 할당 된 배열)에 대해서는 작동하지 않습니다.


@herohuyongtao 제약 조건으로 우리는 Container다른 사람을 위해 두 가지 전문화를 구현할 수 있습니다.
fasked

1
@herohuyongtao K-ary foreach의 예를 만들었습니다.
fasked

1
@fasked : is_container : has_iterator<T>::value내 대답에서 사용 하면 모든 컨테이너에 iteratortypedef 가 있어야하므로 모든 유형에 대한 전문화를 작성할 필요가 없습니다 . 내 대답의 모든 것을 자유롭게 사용하십시오. 당신의 대답은 이미 더 좋습니다.
Zeta 2014 년

@Zeta +1. 또한 내가 언급했듯이 Container개념이 도움이 될 것입니다.

::iterator반복 가능한 범위를 만들지 않습니다. 전문화가 무엇을해야하는지 잘 모르겠 기 int x[2][3][4]때문에 완벽하게 반복 할 수 있습니까? struct foo { int x[3]; int* begin() { return x; } int* end() { return x+3; } };T[]
Yakk-Adam Nevraumont 2014 년

17

대부분의 답변은 단순히 C ++가 이해할 수없는 구문 확장 인 IMHO로 왜곡 될 수 있음을 보여줍니다.

템플릿이나 매크로를 정의하면 다른 프로그래머가 난독 화 된 코드의 다른 비트를 숨기도록 설계된 난독 화 된 코드의 비트를 이해하게 할 수 있습니다.
명확한 의미론으로 객체를 정의하는 일을 피하기 위해 코드를 읽는 모든 사람이 템플릿 전문 지식을 갖도록 강요합니다.

3 차원 배열과 같은 원시 데이터를 사용하기로 결정한 경우 그대로 사용하거나 데이터에 이해할 수있는 의미를 제공하는 클래스를 정의하십시오.

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

이것은 명시적인 의미가없는 int 벡터 벡터의 비밀 정의와 일치합니다.


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

업데이트 : 나는 당신이 그것을 요청했지만 그것을 사용하지 않는 것이 좋습니다 :)


5
나는 그것이 OP가 요구 한 것임을 알고 있지만, 진지하게 ... 이것은 난독 화의 놀라운 예처럼 보입니다. TRIPLE_FOR일부 헤더에 정의되어 있다고 가정하면 여기에서`TRIPLE_FOR '를 볼 때 무엇을 생각해야할까요?
James Kanze 2014 년

2
네, 맞습니다. :) 매크로를 사용하여이 작업을 수행 할 수 있다는 예로서 여기에 남겨 둘 것입니다.하지만 그렇게하지 않는 것이 낫다는 메모를 추가합니다. :) 방금 깨어났습니다 이 질문을 마음을위한 작은 워밍업으로 사용하기로 결정했습니다.
FreeNickname 2014 년

5

한 가지 아이디어는 인덱싱 할 모든 다중 인덱스 튜플 집합을 "포함"하는 반복 가능한 의사 컨테이너 클래스를 작성하는 것입니다. 너무 오래 걸리기 때문에 여기에서는 구현하지 않지만 아이디어는 작성할 수 있어야한다는 것입니다.

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.
}

여기에 최고의 대답 imo.
davidhigh

4

여기에 재귀 적으로 작동하여 입력이 컨테이너인지 여부를 감지하는 많은 답변이 있습니다. 대신 현재 레이어가 함수가 취하는 것과 동일한 유형인지 감지하지 않는 이유는 무엇입니까? 훨씬 간단하고 더 강력한 기능을 허용합니다.

//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 트릭을 사용하여 두 번째 함수보다 첫 번째 함수를 선호하도록 만들고, 0을 전달하고, 우리가 선호하는 함수를 accept와 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 );
 };

여기여기 에서 컴파일 및 실행 증명

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

3

나는 다음과 같은 문이 답변을 경고 : 이 것 만 일을 당신이 실제 배열에서 작동한다면 - 그것은 사용하여 예를 들어 작동하지 않을 것입니다 std::vector.

각 항목의 위치에 신경 쓰지 않고 다차원 배열의 모든 요소에 대해 동일한 작업을 수행하는 경우 배열이 인접한 메모리 위치에 배치된다는 사실을 활용하고 전체를 하나로 취급 할 수 있습니다. 큰 1 차원 배열. 예를 들어 두 번째 예에서 모든 요소에 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);

나는 일반적 으로이 접근법 (Jefffrey의 대답과 같은 것을 선호)을 권장하지 않습니다. 배열에 대해 정의 된 크기에 의존하기 때문에 어떤 경우에는 유용 ​​할 수 있습니다.



@ecatmur : 흥미 롭습니다-방금 일하기 만했기 때문에 이것을 확인하고 그에 따라 답변을 업데이트 / 삭제하겠습니다. 감사.
icabod 2014 년

@ecatmur : C ++ 11 표준 (섹션 8.3.4)을 살펴 보았고 내가 작성한 내용이 작동해야하며 불법으로 보이지 않습니다. 제공 한 링크는 정의 된 배열 크기 외부의 멤버에 액세스하는 것과 관련이 있습니다. 배열 바로 지난 주소를 얻는 것은 사실이지만 데이터에 액세스하는 것은 아닙니다. 이것은 포인터를 반복자로 사용할 수있는 것과 동일한 방식으로 "끝"이 하나의 과거 인 "끝"을 제공하기위한 것입니다. 마지막 요소.
icabod

에 대해 효과적으로 액세스 B[0][0][i]하고 있습니다 i >= 3. (내부) 배열 외부에 액세스하기 때문에 허용되지 않습니다.
ecatmur 2014 년

1
IMO에 end를 할당하는 더 명확한 방법은 end = start + (xSize * ySize * zSize)
noggin182

2

나는 아무도 그 작업을 수행하기 위해 어떤 산술 마술 기반 루프를 제안하지 않았다는 사실에 다소 충격을 받았습니다. C. Wang 은 중첩 루프가없는 솔루션을 찾고 있으므로 하나를 제안합니다.

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 배열을 제공하여 매개 변수 추론을 통해 각 차원의 크기를 세는 어려운 작업을 수행 할 수 있습니다.

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를 사용하면 임의의 차원 배열에 대한 작업을 수행 할 수 있습니다. 먼저 순위 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

다중 중첩 벡터의 경우 솔루션은 임의 차원의 임의 크기 배열 중 하나와 비슷하지만 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;
}

함수 본문이 매우 간단하고 간단하다고 생각합니다. 컴파일러가이 루프를 풀 수 있는지 궁금합니다 (대부분의 컴파일러가 첫 번째 예제를 풀 수 있다고 거의 확신합니다).

여기에서 라이브 데모를 참조 하십시오 .

도움이되기를 바랍니다.


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

중첩 된 for 루프를 사용하십시오!

여기에 제시된 모든 방법은 가독성이나 유연성 측면에서 단점이 있습니다.

외부 루프의 처리를 위해 내부 루프의 결과를 사용해야하는 경우 어떻게됩니까? 내부 루프 내에서 외부 루프의 값이 필요하면 어떻게됩니까? 대부분의 "캡슐화"방법은 여기서 실패합니다.

저를 믿으세요. 중첩 된 for 루프를 "정리"하려는 시도를 여러 번 보았습니다. 결국 중첩 된 루프가 실제로 가장 깨끗하고 가장 유연한 솔루션이라는 것이 밝혀졌습니다.


0

제가 사용한 기술 중 하나는 템플릿입니다. 예 :

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)기본 코드 를 호출 하기 만하면 됩니다. 템플릿 함수는 각 차원에 대해 한 번 생성됩니다. 처음에는으로 T = std::vector<std::vector<int>>, 두 번째에는T = std::vector<int> 됩니다.

std::function원하는 경우 두 번째 인수로 (또는 C ++ 03의 함수와 유사한 객체)를 사용하여보다 일반적으로 만들 수 있습니다.

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

다음과 같이 하나의 루프에서 인덱스를 생성 할 수 있습니다 (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;
}

동의합니다. 특별히 3 차원으로 설계되었습니다.;)
janek 2014 년

1
말할 것도없이 엄청나게 느립니다!
noggin182

@ noggin182 : 질문은 속도가 아니라 중첩 된 for 루프를 피하는 것이 었습니다. 게다가 거기에 불필요한 분할이 있습니다. i / (B * C)는 a로 대체 될 수 있습니다.
janek 2014 년

좋습니다, 이것은 아마도 더 효율적인 대안입니다 (javascript) : for (var i = 0, j = 0, k = 0; i <A; i + = (j == B-1 && k == C- 1)? 1 : 0, j = (k == C-1)? ((j == B-1)? 0 : j + 1) : j, k = (k == C-1)? 0 : k + 1) {console.log (i + ""+ j + ""+ k); }
janek 2014-01-16

0

가장 안쪽의 루프에만 명령문이 있고 코드의 지나치게 장황한 특성에 대한 우려가있는 경우 시도해 볼 수있는 한 가지는 다른 공백 체계를 사용하는 것입니다. 이것은 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]);
}

이것은 외부 루프에서 명령문을 넣는 것과 동일한 기능을 호출하기 때문에 약간 밀어 넣는 것입니다. 불필요한 공백을 모두 제거했으며 통과 할 수 있습니다.

두 번째 예가 훨씬 낫습니다.

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)

두 번째 예는 다음과 같이 변경됩니다.

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의 단일 절에서 사용할 수 없습니다 .


1
이것들의 가독성은 끔찍합니다. 하나 개 이상의 걸림 for라인에 루프하는 것은 그것을 만드는 더 읽기하지 않습니다 적은 .

0

다음은 반복 가능한 모든 것을 처리하는 C ++ 11 구현입니다. 다른 솔루션은 ::iteratortypedef 또는 배열 이있는 컨테이너로 제한 됩니다. 그러나 a for_each는 컨테이너가 아니라 반복에 관한 것입니다.

또한 SFINAE를 is_iterable특성 의 단일 지점으로 격리합니다 . 디스 패칭 (요소와 이터 러블 사이)은 태그 디스 패칭을 통해 수행되며, 이것이 더 명확한 솔루션입니다.

용기 및 소자에인가하는 기능을 모두 완벽 둘 수 전달 const및 비 const범위와 펑에 액세스.

#include <utility>
#include <iterator>

구현중인 템플릿 기능입니다. 다른 모든 것은 세부 사항 네임 스페이스에 들어갈 수 있습니다.

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

태그 발송은 SFINAE보다 훨씬 깨끗합니다. 이 두 가지는 반복 가능한 객체와 반복 불가능한 객체에 각각 사용됩니다. 첫 번째의 마지막 반복은 완벽한 전달을 사용할 수 있지만 게으르다.

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유효 표현이다 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>() );
}        

라이브 예

이것은 맨 아래에 있습니다. 단단하고 상단 답변을 자유롭게 밀렵하십시오. 나는 단지 몇 가지 더 나은 기술을 사용하기를 원했습니다!


-2

첫째, 벡터로 구성된 벡터를 사용해서는 안됩니다. 각 벡터는 연속적인 메모리를 갖도록 보장되지만 벡터 벡터의 "전역"메모리는 그렇지 않습니다 (아마도 없을 것입니다). C 스타일 배열 대신 표준 라이브러리 유형 배열을 사용해야합니다.

using std::array;

array<array<array<double, 5>, 8>, 10> B;
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]);

// or, if you really don't like that, at least do this:

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

하지만 더 나은 방법은 간단한 3D 행렬 클래스를 정의 할 수 있다는 것입니다.

#include <stdexcept>
#include <array>

using std::size_t;

template <size_t M, size_t N, size_t P>
class matrix3d {
    static_assert(M > 0 && N > 0 && P > 0,
                  "Dimensions must be greater than 0.");
    std::array<std::array<std::array<double, P>, N>, M> contents;
public:
    double& at(size_t i, size_t j, size_t k)
    { 
        if (i >= M || j >= N || k >= P)
            throw out_of_range("Index out of range.");
        return contents[i][j][k];
    }
    double& operator(size_t i, size_t j, size_t k)
    {
        return contents[i][j][k];
    }
};

int main()
{
    matrix3d<10, 8, 5> B;
        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(i,j,k));
    return 0;
}

더 나아가서 완전히 const-correct로 만들 수 있고, 행렬 곱셈 (적절한 요소와 요소 별), 벡터 곱하기 등을 추가 할 수 있습니다. 다른 유형으로 일반화 할 수도 있습니다 (주로 double을 사용하는 경우 템플릿으로 만들 것입니다). .

프록시 개체를 추가하여 B [i] 또는 B [i] [j]를 수행 할 수도 있습니다. 그들은 (수학적 의미에서) 벡터와 double &로 가득 찬 행렬을 반환 할 수 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.