Maneiras limpas de escrever vários loops 'for'

98

Para uma matriz com múltiplas dimensões, geralmente precisamos escrever um forloop para cada uma de suas dimensões. Por exemplo:

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

Você vê esse tipo de for-for-forloops em nosso código com frequência. Como uso macros para definir os for-for-forloops, de modo que não precise reescrever esse tipo de código todas as vezes? Existe uma maneira melhor de fazer isso?

C. Wang
fonte
62
A resposta óbvia é que não. Você não cria uma nova linguagem usando macros (ou qualquer outra técnica); a pessoa que vier atrás de você não conseguirá ler o código.
James Kanze
17
Quando você tem um vetor de um vetor de um vetor, isso é um sinal de projeto ruim.
março
5
@Nim: Você pode fazer isso com 1 matriz plana (não tenho certeza se é melhor).
Jarod42
16
Eu acho que você não gostaria de ocultar O(n) = n^3código potencial ...
poy
36
@ TC1: E então vou achar mais difícil de ler. É tudo uma questão de preferências pessoais e na verdade não ajuda com a questão em questão aqui.
ereOn

Respostas:

281

A primeira coisa é que você não usa essa estrutura de dados. Se você precisa de uma matriz tridimensional, você define uma:

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

Ou se você quiser indexar usando [][][], você precisa de um operator[] que retorna um proxy.

Depois de fazer isso, se você achar que precisa repetir constantemente como apresentou, você expõe um iterador que irá suportá-lo:

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

Então você apenas escreve:

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

(ou apenas:

for ( auto& elem: m ) {
}

se você tiver C ++ 11.)

E se você precisar dos três índices durante essas iterações, é possível criar um iterador que os expõe:

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);
        }
        //  ...
    };
};
James Kanze
fonte
21
Esta resposta deve ser bem mais votada, pois é a única que lida com a verdadeira origem do problema.
ereOn
5
pode ser a resposta correta, mas não concordo que seja uma boa resposta. muitos códigos de template enigmáticos com tempo de compilação provavelmente x10 vezes lento e provavelmente código de depuração lento x10 (pode ser mais). Para mim definitivamente o código original é muito mais claro para mim ...
Gorkem
10
@beehorf ... e também muito, muito mais lento. Porque os arrays multidimensionais em C e C ++ são, na verdade, arrays aninhados, no sentido de que as dimensões externas armazenam ponteiros para os arrays aninhados. Esses arrays aninhados são então arbitrariamente espalhados pela memória, derrotando efetivamente qualquer pré-busca e armazenamento em cache. Eu sei de exemplos em que alguém escreveu um código usando vector<vector<vector<double> > >para representar um campo tridimensional. Reescrever o código com o equivalente à solução acima resultou em uma aceleração de 10.
Michael Wild
5
@beehorf Onde você vê algum código de modelo? (Na prática, Matrix3Dprovavelmente deveria ser um modelo, mas é um modelo muito simples.) E você só precisa depurar Matrix3D, não sempre que precisa de uma matriz 3D, portanto, você economiza uma quantidade enorme de tempo na depuração. Quanto à clareza: como é std::vector<std::vector<std::vector<int>>>mais claro do que Matrix3D? Sem mencionar que Matrix3Dreforça o fato de que você tem uma matriz, enquanto os vetores aninhados podem ser irregulares, e que o acima é provavelmente significativamente mais rápido.
James Kanze
10
@MichaelWild Mas é claro, a vantagem real da minha abordagem é que você pode alterar a representação, dependendo do que for mais rápido em seu ambiente, sem ter que modificar nenhum código do cliente. A chave para um bom desempenho é o encapsulamento adequado, para que você possa fazer as alterações que o criador de perfil diz que você precisa, sem ter que reescrever todo o aplicativo.
James Kanze
44

Usar uma macro para ocultar os forloops pode ser muito confuso, apenas para salvar alguns caracteres. Eu usaria loops range-for em vez disso:

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

É claro que você pode substituir auto&por const auto&se, de fato, não estiver modificando os dados.

Sapato
fonte
3
Presumindo que o OP possa usar C ++ 11.
Jarod42
1
@herohuyongtao No caso de iteradores. O que pode ser mais idiomático aqui, mas há casos em que você deseja as três intvariáveis.
James Kanze
1
E não deveria ser assim do_something_on_A(*j)?
James Kanze
1
@Jefffrey Ah, sim. Outra razão para definir o tipo. (Acho que o uso de autofor ke ipode ser justificado. Exceto que ainda resolve o problema no nível errado; o verdadeiro problema é que ele está usando os vetores aninhados.)
James Kanze
2
@Dhara ké um vetor inteiro de vetores (bem, uma referência a ele), não um índice.
Yakk - Adam Nevraumont
21

Algo assim pode ajudar:

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

Para torná-lo N-ário, precisamos de alguma magia de modelo. Primeiramente devemos criar a estrutura SFINAE para distinguir se este valor ou container. A implementação padrão para valores e especializações para arrays e cada um dos tipos de contêiner. Como nota @Zeta, podemos determinar os containers padrão pelo iteratortipo aninhado (o ideal seria verificar se o tipo pode ser usado com range-base forou não).

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

A implementação do for_eaché simples. A função padrão chamará 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);
 }

E a especialização se chamará recursivamente:

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

E voila:

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

Além disso, isso não funcionará para ponteiros (matrizes alocadas na pilha).

fasked
fonte
@herohuyongtao com restrições, podemos implementar duas especializações para Containere para os outros.
fasked em
1
@herohuyongtao Fiz um exemplo de foreach K-ary.
fasked em
1
@fasked: Use is_container : has_iterator<T>::valueda minha resposta e você não precisa escrever uma especialização para cada tipo, uma vez que cada container deve ter um iteratortypedef. Sinta-se à vontade para usar totalmente qualquer coisa da minha resposta, a sua já está melhor.
Zeta
@Zeta +1 para isso. Também como mencionei, o Containerconceito vai ajudar.
fasked em
::iteratornão faz um intervalo iterável. int x[2][3][4]é perfeitamente iterável, pois struct foo { int x[3]; int* begin() { return x; } int* end() { return x+3; } }; não tenho certeza do que a T[]especialização deve fazer?
Yakk - Adam Nevraumont
17

A maioria das respostas simplesmente demonstra como C ++ pode ser distorcido em extensões sintáticas incompreensíveis, IMHO.

Ao definir quaisquer modelos ou macros, você apenas força outros programadores a entender pedaços de código ofuscado projetados para esconder outros pedaços de código ofuscado.
Você vai forçar todo cara que lê seu código a ter experiência em template, apenas para evitar fazer seu trabalho de definir objetos com semântica clara.

Se você decidiu usar dados brutos como arrays tridimensionais, apenas conviva com eles ou então defina uma classe que dê algum significado compreensível aos seus dados.

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

é apenas consistente com a definição criptografada de um vetor de vetor de vetor de int sem semântica explícita.

Kuroi Neko
fonte
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;
}

ATUALIZAÇÃO: Eu sei, que você pediu, mas é melhor não usar isso :)

FreeNickname
fonte
5
Eu sei que é o que o OP pediu, mas sério ... Isso parece um exemplo maravilhoso de ofuscação. Supondo que TRIPLE_FORforam definidos em algum cabeçalho, o que devo pensar quando vir `TRIPLE_FOR aqui.
James Kanze
2
Sim, acho que você está certo :) Acho que vou deixar aqui apenas como um exemplo de que isso pode ser feito usando uma macro, mas adicione uma nota que é melhor não fazer assim :) Acabei de acordar e decidi usar essa pergunta como um pequeno aquecimento para a mente.
FreeNickname
5

Uma ideia é escrever uma classe de pseudo-contêiner iterável que "contenha" o conjunto de todas as tuplas de vários índices que você indexará. Nenhuma implementação aqui porque vai demorar muito, mas a ideia é que você deve ser capaz de escrever ...

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.
}
Steve314
fonte
melhor resposta aqui imo.
davidhigh
4

Vejo muitas respostas aqui que funcionam recursivamente, detectando se a entrada é um contêiner ou não. Em vez disso, por que não detectar se a camada atual é do mesmo tipo que a função assume? É muito mais simples e permite funções mais poderosas:

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

No entanto, isso (obviamente) nos dá erros de ambigüidade. Então, usamos SFINAE para detectar se a entrada atual se encaixa na função ou não

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

Isso agora trata os contêineres corretamente, mas o compilador ainda considera isso ambíguo para input_types que podem ser passados ​​para a função. Então usamos um truque padrão do C ++ 03 para fazê-lo preferir a primeira função em vez da segunda, de também passar um zero, e fazer a que preferimos aceitar e int, e a outra pegar ...

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

É isso aí. Seis linhas de código relativamente simples e você pode iterar em valores, linhas ou qualquer outra subunidade, ao contrário de todas as outras respostas.

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

Prova de compilação e execução aqui e aqui

Se você quiser uma sintaxe mais conveniente em C ++ 11, poderá adicionar uma macro. (O que segue não foi testado)

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 << ", ";
     }
}
Pato mugindo
fonte
3

Limito esta resposta com a seguinte afirmação: isso só funcionaria se você estivesse operando em um array real - não funcionaria para o seu exemplo usando std::vector.

Se você estiver realizando a mesma operação em todos os elementos de uma matriz multidimensional, sem se preocupar com a posição de cada item, você pode aproveitar o fato de que as matrizes são colocadas em locais de memória contíguos e tratar tudo como um grande matriz unidimensional. Por exemplo, se quisermos multiplicar cada elemento por 2.0 em seu segundo exemplo:

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

Observe que usar a abordagem acima também permite o uso de algumas técnicas C ++ "adequadas":

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

Eu geralmente não aconselham essa abordagem (preferindo algo como a resposta de Jefffrey), uma vez que depende de ter tamanhos definidos para suas matrizes, mas em alguns casos pode ser útil.

icabod
fonte
Isso é ilegal: stackoverflow.com/questions/6015080/…
ecatmur
@ecatmur: Interessante - acabei de começar a trabalhar, então vou verificar e atualizar / excluir a resposta de acordo. Obrigado.
icabod
@ecatmur: Eu olhei para o padrão C ++ 11 (seção 8.3.4), e o que escrevi deve funcionar, e não parece ilegal (para mim). O link fornecido está relacionado ao acesso a membros fora do tamanho de array definido. Embora seja verdade que eu obtenho o endereço de apenas após o array, ele não está acessando os dados - isso é para fornecer um "fim", da mesma forma que você pode usar ponteiros como iteradores, com "fim" sendo um passado o último elemento.
icabod
Você está efetivamente acessando B[0][0][i]para i >= 3; isso não é permitido porque está acessando fora da matriz (interna).
ecatmur
1
Uma maneira mais clara da IMO de atribuir fim SE você fosse fazer isso é end = start + (xSize * ySize * zSize)
noggin182
2

Fiquei meio chocado que ninguém propôs algum loop baseado em magia aritmética para fazer o trabalho. Como C. Wang está procurando uma solução sem loops aninhados , vou propor uma:

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

Bem, esta abordagem não é elegante e flexível, então poderíamos embalar todo o processo em uma função de modelo:

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

Esta função de modelo também pode ser expressa na forma de loops aninhados:

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

E pode ser usado fornecendo uma matriz 3D de tamanho arbitrário mais o nome da função, permitindo que a dedução do parâmetro faça o trabalho árduo de contar o tamanho de cada dimensão:

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

Para mais genérico

Mas, mais uma vez, falta flexibilidade porque só funciona para arrays 3D, mas usando SFINAE podemos fazer o trabalho para arrays de uma dimensão arbitrária, primeiro precisamos de uma função de modelo que itera arrays de classificação 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);
    }
}

E outro que itera arrays de qualquer classificação, fazendo a recursão:

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

Isso nos permite iterar todos os elementos em todas as dimensões de uma matriz de dimensões arbitrárias de tamanho arbitrário.


Trabalhando com std::vector

Para o vetor aninhado múltiplo, a solução se assemelha a um array de dimensões arbitrárias de tamanho arbitrário, mas sem SFINAE: Primeiro, precisaremos de uma função de modelo que itera se std::vectorchame a função desejada:

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

E outra função de modelo que itera qualquer tipo de vetor de vetores e se autodenomina:

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

Independentemente do nível de aninhamento, iterate_all chamará a versão do vetor de vetores, a menos que a versão do vetor de valores seja uma correspondência melhor, encerrando assim a recursividade.

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

Eu acho que o corpo da função é bem simples e direto ... Eu me pergunto se o compilador poderia desenrolar esses loops (tenho quase certeza de que a maioria dos compiladores poderia desenrolar o primeiro exemplo).

Veja a demonstração ao vivo aqui .

Espero que ajude.

PaperBirdMaster
fonte
1

Use algo nesse sentido (seu pseudocódigo, mas a ideia permanece a mesma). Você extrai o padrão para fazer um loop uma vez e aplica uma função diferente a cada vez.

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)
RobAu
fonte
1

Fique com os loops for aninhados!

Todos os métodos sugeridos aqui têm desvantagens em termos de legibilidade ou flexibilidade.

O que acontece se você precisar usar os resultados de um loop interno para o processamento no loop externo? O que acontece se você precisar de um valor do loop externo dentro do seu loop interno? A maioria dos métodos de "encapsulamento" falham aqui.

Acredite em mim, já vi várias tentativas de "limpar" loops for aninhados e, no final, descobri que o loop aninhado é, na verdade, a solução mais limpa e flexível.

James Anderson
fonte
0

Uma técnica que usei são os modelos. Por exemplo:

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
}

Então, você simplesmente chama do_something_on_A(A)seu código principal. A função de modelo é criada uma vez para cada dimensão, na primeira vez com T = std::vector<std::vector<int>>, na segunda vez com T = std::vector<int>.

Você poderia tornar isso mais genérico usando std::function(ou objetos semelhantes a funções em C ++ 03) como um segundo argumento se quiser:

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

Então chame-o assim:

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

Isso funciona mesmo que as funções tenham a mesma assinatura, porque a primeira função é uma correspondência melhor para qualquer coisa com std::vectoro tipo.

JoshG79
fonte
0

Você pode gerar índices em um loop como este (A, B, C são dimensões):

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;
}
Janek
fonte
Eu concordo com você, ele foi projetado especificamente para 3 dimensões;)
Janek
1
Sem falar que é incrivelmente lento!
noggin182 de
@ noggin182: a questão não era sobre velocidade, mas sim sobre como evitar for-loops aninhados; além disso, existem divisões desnecessárias lá, i / (B * C) pode ser substituído por a
janek
Ok, esta é uma forma alternativa, provavelmente mais eficiente (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

Uma coisa que você pode querer tentar se tiver apenas instruções no loop mais interno - e sua preocupação é mais sobre a natureza excessivamente detalhada do código - é usar um esquema de espaço em branco diferente. Isso só funcionará se você puder definir seus loops for compactamente o suficiente para que todos caibam em uma linha.

Para seu primeiro exemplo, eu o reescreveria como:

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

Isso é meio que forçado porque você está chamando funções nos loops externos, o que é equivalente a colocar instruções neles. Eu removi todos os espaços em branco desnecessários e pode ser aceitável.

O segundo exemplo é muito melhor:

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

Esta pode ser uma convenção de espaço em branco diferente da que você gostaria de usar, mas atinge um resultado compacto que, no entanto, não requer nenhum conhecimento além de C / C ++ (como convenções de macro) e não requer nenhum artifício como macros.

Se você realmente quiser uma macro, poderá dar um passo adiante com algo como:

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

o que mudaria o segundo exemplo para:

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

e o primeiro exemplo também se sai melhor:

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

Esperançosamente, você pode dizer com bastante facilidade quais declarações combinam com quais declarações. Além disso, cuidado com as vírgulas, agora você não pode usá-las em uma única cláusula de nenhum dos fors.

Michael
fonte
1
A legibilidade deles é horrível. Colocar mais de um forloop em uma linha não a torna mais legível, mas menos .
0

Aqui está uma implementação C ++ 11 que lida com tudo iterável. Outras soluções se restringem a containers com ::iteratortypedefs ou arrays: mas umfor_each é sobre iteração, não ser um container.

Eu também isolei o SFINAE em um único ponto no is_iterable característica. O despacho (entre elementos e iteráveis) é feito por meio de despacho de tag, que considero uma solução mais clara.

Os containers e as funções aplicadas aos elementos são todos perfeitamente encaminhados, permitindo constou não o constacesso aos intervalos e functores.

#include <utility>
#include <iterator>

A função de modelo que estou implementando. Todo o resto pode ir para um namespace details:

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

O envio de etiquetas é muito mais limpo que o SFINAE. Esses dois são usados ​​para objetos iteráveis ​​e objetos não iteráveis, respectivamente. A última iteração da primeira poderia usar encaminhamento perfeito, mas estou preguiçoso:

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

Este é um padrão necessário para escrever is_iterable. Eu faço pesquisa dependente de argumento embegin e endem um namespace de detalhes. Isso emula o que um for( auto x : y )loop faz razoavelmente bem:

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;

O TypeSinké útil para testar se o código é válido. Você TypeSink< decltype(codifica ) >e se ocode for válido, a expressão é void. Se o código não for válido, o SFINAE entra em ação e a especialização é bloqueada:

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

Eu apenas testei begin. Um adl_endteste também pode ser feito.

A implementação final do for_each_flatacaba sendo extremamente simples:

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

Exemplo vivo

Isso está bem embaixo: sinta-se à vontade para pesquisar as principais respostas, que são sólidas. Eu só queria que algumas técnicas melhores fossem usadas!

Yakk - Adam Nevraumont
fonte
-2

Em primeiro lugar, você não deve usar um vetor de vetores de vetores. Cada vetor tem a garantia de ter memória contígua, mas a memória "global" de um vetor de vetores não tem (e provavelmente não terá). Você deve usar a matriz de tipo de biblioteca padrão em vez de matrizes de estilo C também.

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

Melhor ainda, você pode definir uma classe de matriz 3D simples:

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

Você poderia ir mais longe e torná-lo totalmente correto, adicionar multiplicação de matriz (adequada e elemento), multiplicação por vetores, etc. Você poderia até mesmo generalizá-lo para diferentes tipos (eu faria um modelo se você usar principalmente duplos) .

Você também pode adicionar objetos proxy para fazer B [i] ou B [i] [j]. Eles poderiam retornar vetores (no sentido matemático) e matrizes cheias de duplo &, potencialmente?

Miles Rout
fonte