Birden çok "for" döngüsü yazmanın temiz yolları


98

Birden çok boyuta sahip bir dizi için, genellikle forboyutlarının her biri için bir döngü yazmamız gerekir . Örneğin:

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

Bu tür for-for-fordöngüleri kodumuzda sıklıkla görüyorsunuz . for-for-forDöngüleri tanımlamak için makroları nasıl kullanırım, böylece her seferinde bu tür bir kodu yeniden yazmam gerekmez? Bunu yapmanın daha iyi bir yolu var mı?


62
Açık cevap, yapmamanızdır. Makrolar (veya başka herhangi bir teknik) kullanarak yeni bir dil oluşturmazsınız; sizden sonra gelen kişi kodu okuyamayacaktır.
James Kanze

17
Bir vektörün bir vektörüne sahip olduğunuzda, bu kötü bir tasarımın işaretidir.
Maroun

5
@Nim: Bunu 1 düz dizi ile yapabilirsiniz (daha iyi olduğundan emin değilim).
Jarod42

16
Potansiyel O(n) = n^3kodu gizlemek istemeyeceğinizi düşünürdüm ...
poy

36
@ TC1: Ve sonra okumayı daha zor bulacağım. Hepsi kişisel tercihler meselesi ve aslında buradaki soruya yardımcı olmuyor.
2014

Yanıtlar:


281

İlk şey, böyle bir veri yapısı kullanmamanızdır. Üç boyutlu bir matrise ihtiyacınız varsa, birini tanımlarsınız:

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

Veya kullanarak indekslemek istiyorsanız [][][], operator[] bir proxy döndüren bir hangisine ihtiyacınız var .

Bunu yaptıktan sonra, sunduğunuz şekilde sürekli yinelemeniz gerektiğini fark ederseniz, onu destekleyecek bir yineleyici açığa çıkarırsınız:

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

O zaman sadece yazarsın:

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

(ya da sadece:

for ( auto& elem: m ) {
}

C ++ 11'iniz varsa.)

Ve bu tür yinelemeler sırasında üç dizine ihtiyacınız varsa, bunları ortaya çıkaran bir yineleyici oluşturmak mümkündür:

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
Sorunun asıl kaynağı ile ilgilenen tek cevap olduğu için bu cevap çok daha fazla oylanmalıdır.
2014

5
Doğru cevap olabilir ama bunun iyi bir cevap olduğuna katılmıyorum. Muhtemelen x10 kat yavaş derleme süresine ve muhtemelen x10 yavaş hata ayıklama (daha fazla olabilir) koduna sahip çok sayıda şifreli şablon kodu. Benim için kesinlikle orijinal kod benim için çok daha açık ...
Görkem

10
@beehorf ... ve ayrıca çok, çok daha yavaş. Çünkü C ve C ++ 'daki çok boyutlu diziler, dış boyutların iç içe dizilere işaretçileri depolaması anlamında aslında iç içe dizilerdir. Bu iç içe diziler daha sonra, önceden getirme ve önbelleğe alma işlemlerini etkili bir şekilde ortadan kaldırarak belleğin etrafına rastgele dağıtılır. Birinin vector<vector<vector<double> > >3 boyutlu bir alanı temsil etmek için kullanarak bir kod yazdığı örnekleri biliyorum . Yukarıdaki çözüme eşdeğer kodu yeniden yazmak
Michael Wild

5
@beehorf Herhangi bir şablon kodunu nerede görüyorsunuz? (Pratikte, Matrix3Dmuhtemelen bir şablon olmalı, ancak bu çok basit bir şablondur.) Ve sadece hata ayıklamanız Matrix3Dgerekir, bir 3B matrise her ihtiyaç duyduğunuzda değil, böylece hata ayıklamada muazzam miktarda zaman kazanırsınız. Açıklığa gelince: nasıl daha std::vector<std::vector<std::vector<int>>>net Matrix3D? Bu söz değil Matrix3Diç içe vektörler dağınık olabilir iken, bir matris olduğu gerçeğini zorlar ve yukarıdaki önemli ölçüde daha hızlı muhtemelen olduğunu.
James Kanze

10
@MichaelWild Ancak elbette, yaklaşımımın gerçek avantajı, herhangi bir istemci kodunu değiştirmek zorunda kalmadan ortamınızda neyin daha hızlı olduğuna bağlı olarak gösterimi değiştirebilmenizdir. İyi performansın anahtarı, profilleyicinin ihtiyaç duyduğunuz değişiklikleri tüm uygulamayı yeniden yazmak zorunda kalmadan yapabilmeniz için uygun kapsüllemedir.
James Kanze

44

forDöngüleri gizlemek için bir makro kullanmak , sadece birkaç karakter kaydetmek için çok kafa karıştırıcı olabilir. Bunun yerine aralık-için döngüleri kullanırdım:

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

Tabii ki yerini alabilir auto&ile const auto&eğer, aslında, verileri değiştirerek değil.


3
OP'nin C ++ 11'i kullanabileceğini varsayarsak.
Jarod42

1
@herohuyongtao Yineleyiciler durumunda. Burada daha deyimsel olabilir, ancak üç intdeğişkeni isteyebileceğiniz durumlar vardır .
James Kanze

1
Ve bu olmamalı do_something_on_A(*j)mı?
James Kanze

1
@Jefffrey Ah, evet. Türü açıklamak için başka bir neden. (I kullanımını tahmin autoiçin kve ihaklı olabilir hala yanlış düzeyinde sorunu çözer dışında; asıl sorun o iç içe vektörleri kullanarak olmasıdır..)
James Kanze

2
@Dhara k, bir indeks değil , bütün bir vektör vektörüdür (buna bir referans).
Yakk - Adam Nevraumont

21

Bunun gibi bir şey yardımcı olabilir:

 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 yapmak için biraz şablon sihrine ihtiyacımız var. Öncelikle bu değeri mi yoksa kapsayıcı mı olduğunu ayırt etmek için SFINAE yapısı oluşturmalıyız. Değerler için varsayılan uygulama ve diziler ve kap türlerinin her biri için uzmanlıklar. @Zeta, standart kapsayıcıları iç içe yerleştirilmiş iteratortüre göre belirleyebiliriz (ideal olarak türün aralık tabanıyla kullanılıp kullanılamayacağını kontrol etmeliyiz 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 {};

Uygulaması for_eachbasittir. Varsayılan işlev şunları arayacaktır 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);
 }

Ve uzmanlık kendini yinelemeli olarak arayacak:

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

Ve işte:

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

Ayrıca bu işaretçiler için de çalışmayacaktır (yığın içinde ayrılan diziler).


@herohuyongtao kısıtlamalarla Containerdiğerleri için ve diğerleri için iki uzmanlık uygulayabiliriz .
2014

1
@herohuyongtao Bir K-ary foreach örneği yaptım.
2014

1
@fasked: Cevabımdan kullanın is_container : has_iterator<T>::valueve her tür için bir uzmanlık yazmanıza gerek yoktur, çünkü her konteynerin bir iteratortypedef'i olmalıdır . Cevabımdaki herhangi bir şeyi tamamen kullanmaktan çekinmeyin, sizinki zaten daha iyi.
Zeta

Bunun için @Zeta +1. Ayrıca bahsettiğim gibi Containerkonsept yardımcı olacaktır.
2014

::iteratoryinelenebilir bir aralık yapmaz. int x[2][3][4]tam olarak yinelenebilir, çünkü uzmanlaşmanın struct foo { int x[3]; int* begin() { return x; } int* end() { return x+3; } }; ne T[]yapması gerektiğinden emin değilim ?
Yakk - Adam Nevraumont

17

Cevapların çoğu basitçe C ++ 'nın nasıl anlaşılmaz sözdizimsel uzantılara, yani IMHO'ya dönüştürülebileceğini gösteriyor.

Herhangi bir şablon veya makro tanımlayarak, diğer programcıları, karmaşıklaştırılmış kodun diğer bitlerini gizlemek için tasarlanmış karmaşık kod parçalarını anlamaya zorlarsınız.
Kodunuzu okuyan herkesi, yalnızca nesneleri net anlambilimle tanımlama işinizi yapmaktan kaçınmak için şablon uzmanlığına sahip olmaya zorlayacaksınız.

3 boyutlu diziler gibi ham verileri kullanmaya karar verdiyseniz, sadece onunla yaşayın veya verilerinize anlaşılır bir anlam veren bir sınıf tanımlayın.

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

int vektörünün bir vektörünün şifreli tanımıyla tutarlıdır ve açık bir semantik yoktur.


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

GÜNCELLEME: Bunu istediğini biliyorum, ama bunu kullanmasan iyi olur :)


5
Biliyorum, OP'nin istediği şey bu, ama cidden ... Bu, şaşırtıcı bir şaşkınlık örneği gibi görünüyor. TRIPLE_FORBir başlıkta tanımlandığını varsayarsak , burada `TRIPLE_FOR 'u gördüğümde ne düşünmeliyim.
James Kanze

2
Evet, sanırım haklısın :) Sanırım, bunu bir makro kullanarak yapılabileceğine bir örnek olarak burada bırakacağım, ama böyle yapmamanın daha iyi olduğuna dair bir not ekleyeceğim :) Yeni uyandım ve bu soruyu zihin için küçük bir ısınma olarak kullanmaya karar verdi.
FreeNickname

5

Bir fikir, üzerinde indeksleyeceğiniz tüm çoklu indeksli demetler kümesini "içeren" yinelenebilir sözde konteyner sınıfı yazmaktır. Burada uygulama yok çünkü çok uzun sürecek ama fikir şu ki yazabilmelisin ...

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

en iyi cevap burada imo.
davidhigh

4

Burada, girdinin bir kapsayıcı olup olmadığını tespit ederek yinelemeli olarak çalışan birçok yanıt görüyorum. Bunun yerine, geçerli katmanın işlevin aldığı türle aynı olup olmadığını neden algılamıyorsunuz? Çok daha basittir ve daha güçlü işlevlere izin verir:

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

Ancak bu (açıkça) bize belirsizlik hataları verir. Bu nedenle, mevcut girişin işleve uyup uymadığını tespit etmek için SFINAE kullanıyoruz

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

Bu artık kapsayıcıları doğru bir şekilde ele alıyor, ancak derleyici yine de işleve geçirilebilecek input_types için bu belirsizliği düşünüyor. Bu yüzden, ikinci işlevi birinci işlevi tercih etmesi için standart bir C ++ 03 numarası kullanıyoruz, ayrıca bir sıfırı geçip tercih ettiğimizi kabul ve int yaparak diğeri alır ...

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

Bu kadar. Altı, nispeten basit kod satırı ve diğer tüm yanıtların aksine, değerleri, satırları veya diğer alt birimleri yineleyebilirsiniz.

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

Burada ve burada derleme ve yürütme kanıtı

C ++ 11'de daha uygun bir sözdizimi istiyorsanız, bir makro ekleyebilirsiniz. (Aşağıdakiler test edilmemiştir)

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

Bu yanıtı aşağıdaki ifadeyle uyarıyorum: bu yalnızca gerçek bir dizi üzerinde çalışıyorsanız işe yarar - örneğiniz için çalışmaz std::vector.

Aynı işlemi çok boyutlu bir dizinin her öğesi üzerinde, her bir öğenin konumunu önemsemeden gerçekleştiriyorsanız, dizilerin bitişik bellek konumlarına yerleştirilmesi gerçeğinden yararlanıp her şeyi tek bir şey olarak ele alabilirsiniz. büyük tek boyutlu dizi. Örneğin, ikinci örneğinizde her öğeyi 2.0 ile çarpmak istersek:

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

Yukarıdaki yaklaşımı kullanmanın bazı "uygun" C ++ tekniklerinin kullanımına da izin verdiğini unutmayın:

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

Ben genelde bu yaklaşımı tavsiye yok senin diziler için tanımlanmış boyutlara sahip dayanır gibi (Jefffrey cevabı gibi bir şey tercih), ancak bazı durumlarda kullanışlı olabilir.



@ecatmur: İlginç - İşe yeni girdim, bu yüzden bunu kontrol edip cevabı uygun şekilde güncelleyip / sileceğim. Teşekkürler.
icabod

@ecatmur: C ++ 11 standardına (bölüm 8.3.4) baktım ve yazdıklarım işe yaramalı ve yasadışı görünmüyor (bana göre). Sağladığınız bağlantı, tanımlı dizi boyutunun dışındaki üyelere erişimle ilgilidir. Dizinin hemen geçtiği adresi aldığım doğru olsa da, bu verilere erişmiyor - bu, bir "son" sağlamak için, aynı şekilde işaretçileri yineleyiciler olarak kullanabildiğiniz gibi, "son" da bir geçmiş. son öğe.
icabod

Etkili erişiyorsunuz B[0][0][i]için i >= 3; (iç) dizinin dışına eriştiği için buna izin verilmez.
ecatmur

1
Bunu yapacak olursanız IMO'nun bitiş atamasının daha net bir yolu, end = start + (xSize * ySize * zSize)
noggin182

2

Kimsenin işi yapmak için aritmetik-sihire dayalı bir döngü önermesi beni biraz şaşırttı. Yana C. Wang hiçbir iç içe döngüler ile çözüm aramaktadır , bir tane önerecek edeceğiz:

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

Bu yaklaşım zarif ve esnek değildir, bu nedenle tüm süreci bir şablon işlevinde toplayabiliriz:

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

Bu şablon işlevi, iç içe döngüler şeklinde de ifade edilebilir:

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

Ayrıca, isteğe bağlı boyutun 3 boyutlu bir dizisi artı işlev adı sağlanarak kullanılabilir, bu da parametre çıkarımının her boyutun boyutunu saymanın zor işini yapmasına izin verir:

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

Daha jeneriklere doğru

Ancak bir kez daha, esneklikten yoksundur, çünkü yalnızca 3B diziler için çalışır, ancak SFINAE'yi kullanarak işi keyfi boyuttaki diziler için yapabiliriz, ilk önce 1. sıra dizilerini yineleyen bir şablon işlevine ihtiyacımız var :

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

Ve herhangi bir derecedeki dizileri yineleyen, özyinelemeyi yapan bir diğeri:

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

Bu, rastgele boyutta rastgele boyutlu bir dizinin tüm boyutlarındaki tüm öğeleri yinelememizi sağlar.


İle çalışan std::vector

Birden çok iç içe vektör için çözüm, keyfi boyutlu keyfi boyutlu dizilerden birini yeniden andırır, ancak SFINAE olmadan: İlk olarak s'yi yineleyen std::vectorve istenen işlevi çağıran bir şablon işlevine ihtiyacımız olacak :

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

Ve her tür vektör vektörünü yineleyen ve kendisini çağıran başka bir şablon işlevi:

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

İç içe geçme düzeyine bakılmaksızın, iterate_alldeğerler vektörü sürümü daha iyi bir eşleşme olmadıkça, böylece özyinelemeyi sona erdirmedikçe, vektörlerin vektörü sürümünü çağıracaktır.

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

İşlev gövdesinin oldukça basit ve anlaşılır olduğunu düşünüyorum ... Derleyicinin bu döngüleri açıp açamayacağını merak ediyorum (derleyicilerin çoğunun ilk örneği açabileceğinden neredeyse eminim).

Bkz burada canlı demo .

Umarım yardımcı olur.


1

Bu satırlar boyunca bir şey kullanın (sözde kodu, ancak fikir aynı kalır). Deseni bir kez döngüye çıkarırsınız ve her seferinde farklı bir işlev uygularsınız.

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

Döngüler için iç içe geçmişe bağlı kalın!

Burada önerilen tüm yöntemlerin okunabilirlik veya esneklik açısından dezavantajları vardır.

Dış döngüde işleme için bir iç döngünün sonuçlarını kullanmanız gerekirse ne olur? İç döngünüzde dış döngüden bir değere ihtiyacınız olursa ne olur? "Kapsülleme" yöntemlerinin çoğu burada başarısız olur.

İnanın bana, döngüler için iç içe geçmiş "temizleme" girişimlerini gördüm ve sonunda iç içe döngünün aslında en temiz ve en esnek çözüm olduğu ortaya çıktı.


0

Kullandığım tekniklerden biri şablonlardır. Örneğin:

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
}

Ardından do_something_on_A(A)ana kodunuzu aramanız yeterlidir . Şablon işlevi her boyut için bir kez, ilk kez ile T = std::vector<std::vector<int>>, ikinci kez ile oluşturulur T = std::vector<int>.

İsterseniz, bunu std::functionikinci argüman olarak (veya C ++ 03'teki işlev benzeri nesneleri) kullanarak daha genel hale getirebilirsiniz :

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

O zaman şöyle deyin:

do_something_on_vec(A, std::function(do_something_on_A));

Bu, işlevler aynı imzaya sahip olsa bile çalışır, çünkü ilk işlev, std::vectortürdeki herhangi bir şey için daha iyi bir eşleşmedir .


0

Aşağıdaki gibi bir döngüde indisler oluşturabilirsiniz (A, B, C boyutlardır):

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

Ben özellikle 3 boyut için tasarlanmış, size katılıyorum;)
janek

1
İnanılmaz derecede yavaş olduğundan bahsetmiyorum bile!
noggin182

@ noggin182: soru hız ile ilgili değil, iç içe döngülerden kaçınmakla ilgili; ayrıca orada gereksiz bölümler var, i / (B * C) yerine a
janek

Tamam, bu alternatif bir yol, muhtemelen daha verimli (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

0

Yalnızca en içteki döngüde ifadeleriniz varsa denemek isteyebileceğiniz bir şey - ve endişeniz daha çok kodun aşırı ayrıntılı yapısıyla ilgilidir - farklı bir boşluk şeması kullanmaktır. Bu, yalnızca for döngülerinizi yeterince kompakt bir şekilde belirtebilirseniz işe yarar, böylece hepsi bir satıra sığar.

İlk örneğiniz için, onu şu şekilde yeniden yazacağım:

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

Bu biraz zorluyor çünkü dış döngülerde, onlara ifadeler koymaya eşdeğer olan işlevleri çağırıyorsunuz. Gereksiz tüm boşlukları kaldırdım ve fena olabilir.

İkinci örnek çok daha iyi:

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

Bu, kullanmayı sevdiğinizden farklı bir boşluk kuralı olabilir, ancak yine de C / C ++ dışında herhangi bir bilgi (makro kuralları gibi) gerektirmeyen ve makrolar gibi herhangi bir hile gerektirmeyen kompakt bir sonuç elde eder.

Gerçekten bir makro istiyorsanız, aşağıdaki gibi bir şeyle bunu bir adım daha ileri götürebilirsiniz:

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

bu ikinci örneği şu şekilde değiştirir:

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

ve ilk örnek de daha iyi:

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

Umarım hangi ifadelerin hangi ifadeler için uygun olduğunu kolayca söyleyebilirsin. Ayrıca, virgüllere dikkat edin, artık onları herhangi bir fors'nin tek bir cümlesinde kullanamazsınız .


1
Bunların okunabilirliği korkunç. forBir hatta birden fazla döngü karıştırmak onu daha okunaklı yapmaz, daha az yapar .

0

İşte yinelenebilir her şeyi işleyen bir C ++ 11 uygulaması. Diğer çözümler kendilerini ::iteratortür dizileri veya dizileri olan kaplarla sınırlar: ancak a for_each, bir kapsayıcı değil, yinelemeyle ilgilidir.

Ayrıca SFINAE'yi is_iterableözellikteki tek bir noktaya ayırıyorum . Gönderme (öğeler ve yinelemeler arasında), daha açık bir çözüm bulduğum etiket gönderimi yoluyla yapılıyor.

Elemanlara uygulanan kaplar ve işlevlerin tümü mükemmel bir şekilde iletilir constve hem constaralıklara hem de functor'lara erişime izin vermez.

#include <utility>
#include <iterator>

Uyguladığım şablon işlevi. Diğer her şey bir ayrıntı ad alanına girebilir:

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

Etiket dağıtımı SFINAE'den çok daha temizdir. Bu ikisi sırasıyla yinelenebilir nesneler ve yinelenemez nesneler için kullanılır. İlkinin son yinelemesi mükemmel yönlendirmeyi kullanabilir, ancak tembelim:

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

Bu, yazmak için gerekli olan bir ortak metindir is_iterable. Ayrıntı ad alanında beginve endiçinde argümana bağlı arama yapıyorum . Bu, bir for( auto x : y )döngünün oldukça iyi yaptığı şeyi taklit eder :

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;

TypeSinkKod geçerli olup olmadığını test etmek yararlıdır. TypeSink< decltype(Kodu yaparsınız ) >ve eğer codegeçerliyse ifade olur void. Kod geçerli değilse SFINAE devreye girer ve uzmanlık engellenir:

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

Ben sadece test ediyorum begin. Bir adl_endtest de yapılabilir.

Sonuçların nihai uygulaması for_each_flatson derece basittir:

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

Canlı örnek

Bu, en altta: Sağlam olan en iyi cevaplar için avlanmakta özgürsünüz. Sadece birkaç daha iyi tekniğin kullanılmasını istedim!


-2

İlk olarak, vektörlerin bir vektörünü kullanmamalısınız. Her vektörün bitişik belleğe sahip olması garanti edilir, ancak vektörlerin bir vektörünün "global" belleği değildir (ve muhtemelen olmayacaktır). C tarzı diziler yerine standart kitaplık türü dizisini de kullanmalısınız.

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

Daha da iyisi, basit bir 3B matris sınıfı tanımlayabilirsiniz:

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

Daha ileri gidebilir ve tamamen sabit-doğru yapabilir, matris çarpımı ekleyebilir (doğru ve eleman açısından), vektörlerle çarpma, vb. Hatta farklı türlere de genelleyebilirsiniz (çoğunlukla çiftler kullanırsanız şablon yapardım) .

B [i] veya B [i] [j] yapabilmek için proxy nesneleri de ekleyebilirsiniz. Vektörleri (matematiksel anlamda) ve double & ile dolu matrisleri döndürebilirler, potansiyel olarak?

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.