Para uma matriz com múltiplas dimensões, geralmente precisamos escrever um for
loop 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-for
loops em nosso código com frequência. Como uso macros para definir os for-for-for
loops, de modo que não precise reescrever esse tipo de código todas as vezes? Existe uma maneira melhor de fazer isso?
O(n) = n^3
código potencial ...Respostas:
A primeira coisa é que você não usa essa estrutura de dados. Se você precisa de uma matriz tridimensional, você define uma:
Ou se você quiser indexar usando
[][][]
, você precisa de umoperator[]
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:
Então você apenas escreve:
(ou apenas:
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:
fonte
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.Matrix3D
provavelmente deveria ser um modelo, mas é um modelo muito simples.) E você só precisa depurarMatrix3D
, 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 queMatrix3D
? Sem mencionar queMatrix3D
reforç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.Usar uma macro para ocultar os
for
loops pode ser muito confuso, apenas para salvar alguns caracteres. Eu usaria loops range-for em vez disso:É claro que você pode substituir
auto&
porconst auto&
se, de fato, não estiver modificando os dados.fonte
int
variáveis.do_something_on_A(*j)
?auto
fork
ei
pode ser justificado. Exceto que ainda resolve o problema no nível errado; o verdadeiro problema é que ele está usando os vetores aninhados.)k
é um vetor inteiro de vetores (bem, uma referência a ele), não um índice.Algo assim pode ajudar:
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
iterator
tipo aninhado (o ideal seria verificar se o tipo pode ser usado com range-basefor
ou não).A implementação do
for_each
é simples. A função padrão chamaráfunction
:E a especialização se chamará recursivamente:
E voila:
Além disso, isso não funcionará para ponteiros (matrizes alocadas na pilha).
fonte
Container
e para os outros.is_container : has_iterator<T>::value
da minha resposta e você não precisa escrever uma especialização para cada tipo, uma vez que cada container deve ter umiterator
typedef. Sinta-se à vontade para usar totalmente qualquer coisa da minha resposta, a sua já está melhor.Container
conceito vai ajudar.::iterator
não faz um intervalo iterável.int x[2][3][4]
é perfeitamente iterável, poisstruct foo { int x[3]; int* begin() { return x; } int* end() { return x+3; } };
não tenho certeza do que aT[]
especialização deve fazer?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.
é apenas consistente com a definição criptografada de um vetor de vetor de vetor de int sem semântica explícita.
fonte
ATUALIZAÇÃO: Eu sei, que você pediu, mas é melhor não usar isso :)
fonte
TRIPLE_FOR
foram definidos em algum cabeçalho, o que devo pensar quando vir `TRIPLE_FOR aqui.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 ...
fonte
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:
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
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 ...
É 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.
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)
fonte
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:
Observe que usar a abordagem acima também permite o uso de algumas técnicas C ++ "adequadas":
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.
fonte
B[0][0][i]
parai >= 3
; isso não é permitido porque está acessando fora da matriz (interna).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:Bem, esta abordagem não é elegante e flexível, então poderíamos embalar todo o processo em uma função de modelo:
Esta função de modelo também pode ser expressa na forma de loops aninhados:
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:
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:
E outro que itera arrays de qualquer classificação, fazendo a recursão:
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::vector
chame a função desejada:E outra função de modelo que itera qualquer tipo de vetor de vetores e se autodenomina:
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.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.
fonte
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.
fonte
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.
fonte
Uma técnica que usei são os modelos. Por exemplo:
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 comT = std::vector<std::vector<int>>
, na segunda vez comT = 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:Então chame-o assim:
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::vector
o tipo.fonte
Você pode gerar índices em um loop como este (A, B, C são dimensões):
fonte
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:
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:
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:
o que mudaria o segundo exemplo para:
e o primeiro exemplo também se sai melhor:
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
for
s.fonte
for
loop em uma linha não a torna mais legível, mas menos .Aqui está uma implementação C ++ 11 que lida com tudo iterável. Outras soluções se restringem a containers com
::iterator
typedefs 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
const
ou não oconst
acesso aos intervalos e functores.A função de modelo que estou implementando. Todo o resto pode ir para um namespace details:
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:
Este é um padrão necessário para escrever
is_iterable
. Eu faço pesquisa dependente de argumento embegin
eend
em um namespace de detalhes. Isso emula o que umfor( auto x : y )
loop faz razoavelmente bem: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:Eu apenas testei
begin
. Umadl_end
teste também pode ser feito.A implementação final do
for_each_flat
acaba sendo extremamente simples: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!
fonte
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.
Melhor ainda, você pode definir uma classe de matriz 3D simples:
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?
fonte