Para una matriz con múltiples dimensiones, generalmente necesitamos escribir un for
bucle para cada una de sus dimensiones. Por ejemplo:
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]);
}
}
}
Ves este tipo de for-for-for
bucles en nuestro código con frecuencia. ¿Cómo utilizo macros para definir los for-for-for
bucles para no tener que volver a escribir este tipo de código cada vez? ¿Hay una mejor manera de hacer esto?
O(n) = n^3
código potencial ...Respuestas:
Lo primero es que no usa esa estructura de datos. Si necesita una matriz tridimensional, defina una:
O si desea indexar usando
[][][]
, necesita unoperator[]
que devuelva un proxy.Una vez que haya hecho esto, si descubre que tiene que iterar constantemente como ha presentado, expone un iterador que lo admitirá:
Entonces solo escribe:
(o solo:
si tiene C ++ 11.)
Y si necesita los tres índices durante tales iteraciones, es posible crear un iterador que los exponga:
fuente
vector<vector<vector<double> > >
para representar un campo tridimensional. Reescribir el código, el equivalente a la solución anterior, resultó en una aceleración de 10.Matrix3D
probablemente debería ser una plantilla, pero es una plantilla muy sencilla). Y solo tiene que depurarMatrix3D
, no siempre que necesite una matriz 3D, por lo que ahorra una enorme cantidad de tiempo en la depuración. En cuanto a la claridad: ¿cómo esstd::vector<std::vector<std::vector<int>>>
más claro queMatrix3D
? Sin mencionar queMatrix3D
refuerza el hecho de que tiene una matriz, mientras que los vectores anidados podrían ser irregulares, y que lo anterior probablemente sea significativamente más rápido.Usar una macro para ocultar los
for
bucles puede ser muy confuso, solo para guardar algunos caracteres. En su lugar, usaría bucles de rango para :Por supuesto, puede reemplazar
auto&
conconst auto&
si, de hecho, no está modificando los datos.fuente
int
variables.do_something_on_A(*j)
?auto
fork
yi
puede estar justificado. Excepto que todavía resuelve el problema en el nivel incorrecto; el problema real es que está usando los vectores anidados.)k
es un vector completo de vectores (bueno, una referencia a él), no un índice.Algo como esto puede ayudar:
Para hacerlo N-ary, necesitamos algo de magia de plantilla. En primer lugar debemos crear la estructura SFINAE para distinguir si este valor o contenedor. La implementación predeterminada para valores y especializaciones para matrices y cada uno de los tipos de contenedor. Como señala @Zeta, podemos determinar los contenedores estándar por el
iterator
tipo anidado (idealmente deberíamos verificar si el tipo se puede usar con range-basefor
o no).La implementación de
for_each
es sencilla. La función predeterminada llamaráfunction
:Y la especialización se llamará a sí misma de forma recursiva:
Y voilá:
Además, esto no funcionará para punteros (matrices asignadas en el montón).
fuente
Container
y para otros.is_container : has_iterator<T>::value
de mi respuesta y no necesita escribir una especialización para cada tipo, ya que cada contenedor debe tener uniterator
typedef. Siéntase libre de usar completamente cualquier cosa de mi respuesta, la suya ya es mejor.Container
concepto ayudará.::iterator
no hace un rango iterable.int x[2][3][4]
es perfectamente iterable, yastruct foo { int x[3]; int* begin() { return x; } int* end() { return x+3; } };
que no estoy seguro de quéT[]
se supone que debe hacer la especialización.La mayoría de las respuestas simplemente demuestran cómo C ++ se puede convertir en extensiones sintácticas incomprensibles, en mi humilde opinión.
Al definir cualquier plantilla o macros, simplemente obliga a otros programadores a comprender fragmentos de código ofuscado diseñados para ocultar otros fragmentos de código ofuscado.
Obligará a cada persona que lea su código a tener experiencia en plantillas, solo para evitar hacer su trabajo de definir objetos con una semántica clara.
Si decidió utilizar datos sin procesar como matrices tridimensionales, simplemente viva con ellos o defina una clase que dé un significado comprensible a sus datos.
es simplemente consistente con la definición críptica de un vector de vector de vector de int sin semántica explícita.
fuente
ACTUALIZACIÓN: Sé que lo solicitó, pero es mejor que no lo use :)
fuente
TRIPLE_FOR
se definieran en algún encabezado, ¿qué debo pensar cuando vea `TRIPLE_FOR aquí?Una idea es escribir una clase de pseudocontenedor iterable que "contenga" el conjunto de todas las tuplas de índices múltiples sobre las que indexará. No hay implementación aquí porque tomará demasiado tiempo, pero la idea es que debería poder escribir ...
fuente
Veo muchas respuestas aquí que funcionan de forma recursiva, detectando si la entrada es un contenedor o no. En cambio, ¿por qué no detectar si la capa actual es del mismo tipo que la función? Es mucho más simple y permite funciones más potentes:
Sin embargo, esto (obviamente) nos da errores de ambigüedad. Entonces usamos SFINAE para detectar si la entrada actual encaja en la función o no
Esto ahora maneja los contenedores correctamente, pero el compilador aún lo considera ambiguo para input_types que se pueden pasar a la función. Entonces usamos un truco estándar de C ++ 03 para hacer que prefiera la primera función sobre la segunda, de pasar también un cero, y hacer que la que preferimos acepte e int, y la otra tome ...
Eso es. Seis líneas de código relativamente simples, y puede iterar sobre valores, filas o cualquier otra subunidad, a diferencia de todas las demás respuestas.
Prueba de compilación y ejecución aquí y aquí
Si desea una sintaxis más conveniente en C ++ 11, puede agregar una macro. (Lo siguiente no está probado)
fuente
Advierto esta respuesta con la siguiente declaración: esto solo funcionaría si estuviera operando en una matriz real ; no funcionaría para su ejemplo usando
std::vector
.Si está realizando la misma operación en cada elemento de una matriz multidimensional, sin preocuparse por la posición de cada elemento, puede aprovechar el hecho de que las matrices se colocan en ubicaciones de memoria contiguas y tratar todo como una gran matriz unidimensional. Por ejemplo, si quisiéramos multiplicar cada elemento por 2.0 en su segundo ejemplo:
Tenga en cuenta que el uso del enfoque anterior también permite el uso de algunas técnicas de C ++ "adecuadas":
Yo no aconsejo este enfoque general (prefiriendo algo así como la respuesta de Jefffrey), ya que se basa en tener tamaños definidos para sus matrices, pero en algunos casos puede ser útil.
fuente
B[0][0][i]
ai >= 3
; esto no está permitido ya que está accediendo fuera de la matriz (interna).Me sorprendió un poco que nadie propusiera algún bucle basado en aritmética-magia para hacer el trabajo.
Dado que C. Wang está buscando una solución sin bucles anidados, propondré una:Bueno, este enfoque no es elegante y flexible, por lo que podríamos empaquetar todo el proceso en una función de plantilla:
Esta función de plantilla también se puede expresar en forma de bucles anidados:
Y se puede utilizar proporcionando una matriz 3D de tamaño arbitrario más el nombre de la función, dejando que la deducción del parámetro haga el trabajo duro de contar el tamaño de cada dimensión:
Hacia más genérico
Pero una vez más, carece de flexibilidad porque solo funciona para matrices 3D, pero usando SFINAE podemos hacer el trabajo para matrices de una dimensión arbitraria, primero necesitamos una función de plantilla que itera matrices de rango 1:
Y otro que itera matrices de cualquier rango, haciendo la recursividad:
Esto nos permite iterar todos los elementos en todas las dimensiones de una matriz de tamaño arbitrario de dimensiones arbitrarias.
Trabajando con
std::vector
Para el vector anidado múltiple, la solución se asemeja a la de una matriz de tamaño arbitrario de dimensiones arbitrarias, pero sin SFINAE: Primero necesitaremos una función de plantilla que itera
std::vector
sy llama a la función deseada:Y otra función de plantilla que itera cualquier tipo de vector de vectores y se llama a sí mismo:
Independientemente del nivel de anidamiento,
iterate_all
llamará a la versión de vector de vectores a menos que la versión de vector de valores sea una mejor coincidencia, poniendo así fin a la recursividad.Creo que el cuerpo de la función es bastante simple y directo ... Me pregunto si el compilador podría desenrollar estos bucles (estoy casi seguro de que la mayoría de los compiladores podrían desenrollar el primer ejemplo).
Vea la demostración en vivo aquí .
Espero eso ayude.
fuente
Use algo en esta línea (su pseudocódigo, pero la idea sigue siendo la misma). Extrae el patrón para que se repita una vez y aplica una función diferente cada vez.
fuente
¡Quédate con los bucles for anidados!
Todos los métodos sugeridos aquí tienen desventajas en términos de legibilidad o flexibilidad.
¿Qué sucede si necesita utilizar los resultados de un ciclo interno para el procesamiento en el ciclo externo? ¿Qué sucede si necesita un valor del ciclo externo dentro de su ciclo interno? La mayoría de los métodos de "encapsulación" fallan aquí.
Créame, he visto varios intentos de "limpiar" los bucles for anidados y, al final, resulta que el bucle anidado es en realidad la solución más limpia y flexible.
fuente
Una técnica que he usado son las plantillas. P.ej:
Luego, simplemente llame
do_something_on_A(A)
a su código principal. La función de plantilla se crea una vez para cada dimensión, la primera vez conT = std::vector<std::vector<int>>
, la segunda vez con conT = std::vector<int>
.Puede hacer esto más genérico usando
std::function
(u objetos similares a funciones en C ++ 03) como un segundo argumento si lo desea:Entonces llámalo como:
Esto funciona a pesar de que las funciones tienen la misma firma porque la primera función coincide mejor con cualquier elemento
std::vector
del tipo.fuente
Podría generar índices en un ciclo como este (A, B, C son dimensiones):
fuente
Una cosa que puede querer probar si solo tiene declaraciones en el bucle más interno, y su preocupación es más sobre la naturaleza demasiado detallada del código, es usar un esquema de espacios en blanco diferente. Esto solo funcionará si puede indicar sus bucles for de manera lo suficientemente compacta como para que quepan en una línea.
Para su primer ejemplo, lo reescribiría como:
Esto es un poco empujarlo porque está llamando a funciones en los bucles externos, lo que equivale a poner declaraciones en ellos. He eliminado todos los espacios en blanco innecesarios y es posible que sea aceptable.
El segundo ejemplo es mucho mejor:
Esta puede ser una convención de espacios en blanco diferente a la que le gusta usar, pero logra un resultado compacto que, sin embargo, no requiere ningún conocimiento más allá de C / C ++ (como las convenciones de macros) y no requiere ningún truco como las macros.
Si realmente desea una macro, puede dar un paso más con algo como:
lo que cambiaría el segundo ejemplo a:
y al primer ejemplo también le va mejor:
Es de esperar que pueda decir con bastante facilidad qué declaraciones van con cada declaración. Además, tenga cuidado con las comas, ahora no puede usarlas en una sola cláusula de ninguna de las
for
s.fuente
for
bucle en una línea no lo hace más legible, lo hace menos .Aquí hay una implementación de C ++ 11 que maneja todo lo iterable. Otras soluciones se limitan a contenedores con
::iterator
typedefs o matrices: pero afor_each
se trata de iteración, no de contenedor.También aíslo el SFINAE en un solo punto del
is_iterable
rasgo. El envío (entre elementos e iterables) se realiza mediante el envío de etiquetas, que encuentro es una solución más clara.Los contenedores y las funciones aplicadas a los elementos están perfectamente reenviados, permitiendo tanto el acceso
const
como noconst
a las gamas y functores.La función de plantilla que estoy implementando. Todo lo demás podría entrar en un espacio de nombres de detalles:
El envío de etiquetas es mucho más limpio que SFINAE. Estos dos se utilizan para objetos iterables y objetos no iterables respectivamente. La última iteración de la primera podría usar un reenvío perfecto, pero soy vago:
Esta es una repetición necesaria para escribir
is_iterable
. Hago una búsqueda dependiente de argumentos enbegin
yend
en un espacio de nombres detallado. Esto emula lo que unfor( auto x : y )
bucle hace razonablemente bien:El
TypeSink
es útil para probar si el código es válido. Hace elTypeSink< decltype(
código) >
y sicode
es válido, la expresión esvoid
. Si el código no es válido, SFINAE entra en acción y la especialización se bloquea:Solo pruebo
begin
. También seadl_end
podría hacer una prueba.La implementación final de
for_each_flat
termina siendo extremadamente simple:Ejemplo vivo
Esto está muy abajo en la parte inferior: siéntase libre de buscar las respuestas principales, que son sólidas. ¡Solo quería que se usaran algunas técnicas mejores!
fuente
En primer lugar, no debería utilizar un vector de vectores de vectores. Se garantiza que cada vector tiene memoria contigua, pero la memoria "global" de un vector de vectores no lo es (y probablemente no lo será). También debe usar la matriz de tipo de biblioteca estándar en lugar de las matrices de estilo C.
Sin embargo, mejor aún, podría definir una clase de matriz 3D simple:
Podrías ir más allá y hacerlo completamente const-correcto, agregar multiplicación de matrices (propia y de elementos), multiplicación por vectores, etc. Incluso podrías generalizarlo a diferentes tipos (lo haría una plantilla si usa principalmente dobles) .
También puede agregar objetos proxy para poder hacer B [i] o B [i] [j]. ¿Podrían devolver vectores (en el sentido matemático) y matrices llenas de doble &, potencialmente?
fuente