¿Cómo itero a través de cada elemento en una matriz n-dimensional en MATLAB?

87

Tengo un problema. Necesito iterar a través de cada elemento en una matriz n-dimensional en MATLAB. El problema es que no sé cómo hacer esto para un número arbitrario de dimensiones. Se que puedo decir

for i = 1:size(m,1)
    for j = 1:size(m,2)
        for k = 1:size(m,3)

y así sucesivamente, pero ¿hay alguna manera de hacerlo para un número arbitrario de dimensiones?

rlbond
fuente
13
Nota terminológica de Matlab: Matlab tiene una pequeña cantidad de tipos de datos básicos. Los más importantes son: estructura, matriz y matriz de celdas. Cuando se hace referencia a partes de una matriz, es común usar el término "elemento" y reservar el término "celda" para referirse a partes de una matriz de celdas. Las matrices y matrices de celdas tienen numerosas diferencias sintácticas y semánticas, aunque ambas son estructuras de datos N-dimensionales.
Mr Fooz
3
¿Puedo preguntar para qué necesita la iteración? Quizás haya una forma "vectorizada" de hacerlo en su lugar ...
Hosam Aly

Respuestas:

92

Puede utilizar la indexación lineal para acceder a cada elemento.

for idx = 1:numel(array)
    element = array(idx)
    ....
end

Esto es útil si no necesita saber en qué se encuentra i, j, k. Sin embargo, si no necesita saber en qué índice se encuentra, probablemente sea mejor que use arrayfun ()

Andrés
fuente
1
También, si desea recuperar los índices por alguna razón, todavía se podía utilizar estos dos comandos simples: I = cell(1, ndims(array)); [I{:}] = ind2sub(size(array),idx);.
knedlsepp
34

La idea de un índice lineal para matrices en matlab es importante. Una matriz en MATLAB es en realidad solo un vector de elementos, encadenados en la memoria. MATLAB le permite utilizar un índice de fila y columna, o un índice lineal único. Por ejemplo,

A = magic(3)
A =
     8     1     6
     3     5     7
     4     9     2

A(2,3)
ans =
     7

A(8)
ans =
     7

Podemos ver el orden en que se almacenan los elementos en la memoria desenrollando la matriz en un vector.

A(:)
ans =
     8
     3
     4
     1
     5
     9
     6
     7
     2

Como puede ver, el octavo elemento es el número 7. De hecho, la función encontrar devuelve sus resultados como un índice lineal.

find(A>6)
ans =
     1
     6
     8

El resultado es que podemos acceder a cada elemento a su vez de una matriz nd general utilizando un solo ciclo. Por ejemplo, si quisiéramos cuadrar los elementos de A (sí, sé que hay mejores formas de hacerlo), uno podría hacer esto:

B = zeros(size(A));
for i = 1:numel(A)
  B(i) = A(i).^2;
end

B
B =
    64     1    36
     9    25    49
    16    81     4

Hay muchas circunstancias en las que el índice lineal es más útil. La conversión entre el índice lineal y subíndices de dos (o más) dimensiones se logra con las funciones sub2ind e ind2sub.

El índice lineal se aplica en general a cualquier matriz en matlab. Entonces puede usarlo en estructuras, matrices de celdas, etc. El único problema con el índice lineal es cuando se vuelven demasiado grandes. MATLAB utiliza un entero de 32 bits para almacenar estos índices. Entonces, si su matriz tiene más de un total de 2 ^ 32 elementos, el índice lineal fallará. En realidad, solo es un problema si usa matrices dispersas con frecuencia, cuando ocasionalmente esto causará un problema. (Aunque no uso una versión de MATLAB de 64 bits, creo que ese problema se ha resuelto para los afortunados que lo hacen).


fuente
De hecho, la indexación en MATLAB de 64 bits permite correctamente subíndices de 64 bits. Por ejemplo: x = ones(1,2^33,'uint8'); x(2^33)funciona como se esperaba.
Edric
@Edric - Por supuesto, este es un comportamiento que seguramente habría cambiado en los años (y muchos lanzamientos) desde que hice esa declaración. Sin embargo, gracias por comprobarlo.
:) No me di cuenta de la antigüedad de la respuesta hasta que hice un comentario: la pregunta apareció en mi feed RSS y ni siquiera me di cuenta de que la había respondido también.
Edric
15

Como se señaló en algunas otras respuestas, puede iterar sobre todos los elementos en una matriz A(de cualquier dimensión) usando un índice lineal de 1a numel(A)en un solo bucle for. También hay un par de funciones que puede utilizar: arrayfuny cellfun.

Primero supongamos que tiene una función que desea aplicar a cada elemento de A(llamado my_func). Primero crea un identificador de función para esta función:

fcn = @my_func;

Si Aes una matriz (de tipo doble, simple, etc.) de dimensión arbitraria, puede usar arrayfunpara aplicar my_funca cada elemento:

outArgs = arrayfun(fcn, A);

Si Aes una matriz de celdas de dimensión arbitraria, puede usar cellfunpara aplicar my_funca cada celda:

outArgs = cellfun(fcn, A);

La función my_functiene que aceptar Acomo entrada. Si hay alguna salida de my_func, se colocan en outArgs, que será del mismo tamaño / dimensión que A.

Una advertencia sobre las salidas ... si my_funcdevuelve salidas de diferentes tamaños y tipos cuando opera en diferentes elementos de A, entonces outArgstendrá que convertirse en una matriz de celdas. Esto se hace llamando a arrayfuno cellfuncon un par de parámetro / valor adicional:

outArgs = arrayfun(fcn, A, 'UniformOutput', false);
outArgs = cellfun(fcn, A, 'UniformOutput', false);
gnovice
fuente
13

Otro truco consiste en utilizar ind2suby sub2ind. Junto con numely size, esto puede permitirle hacer cosas como las siguientes, que crean una matriz N-dimensional, y luego establece todos los elementos en la "diagonal" en 1.

d = zeros( 3, 4, 5, 6 ); % Let's pretend this is a user input
nel = numel( d );
sz = size( d );
szargs = cell( 1, ndims( d ) ); % We'll use this with ind2sub in the loop
for ii=1:nel
    [ szargs{:} ] = ind2sub( sz, ii ); % Convert linear index back to subscripts
    if all( [szargs{2:end}] == szargs{1} ) % On the diagonal?
        d( ii ) = 1;
    end
end
Edric
fuente
+1 por mostrar un buen ejemplo de cómo MATLAB rompe la escritura de pato.
Phillip Cloud
1

Podrías hacer que una función recursiva haga el trabajo

  • Dejar L = size(M)
  • Dejar idx = zeros(L,1)
  • Toma length(L)como profundidad máxima
  • Lazo for idx(depth) = 1:L(depth)
  • Si su profundidad es length(L), realice la operación del elemento, de lo contrario, llame a la función nuevamente condepth+1

No es tan rápido como los métodos vectorizados si desea verificar todos los puntos, pero si no necesita evaluar la mayoría de ellos, puede ahorrar bastante tiempo.

Dennis Jaheruddin
fuente
1

estas soluciones son más rápidas (alrededor del 11%) que usar numel;)

for idx = reshape(array,1,[]),
     element = element + idx;
end

o

for idx = array(:)',
    element = element + idx;
end

UPD. tnx @rayryeng por error detectado en la última respuesta


Descargo de responsabilidad

La información de tiempo a la que se ha hecho referencia en esta publicación es incorrecta e inexacta debido a un error tipográfico fundamental que se cometió (consulte el flujo de comentarios a continuación, así como el historial de edición ; mire específicamente la primera versión de esta respuesta). Caveat Emptor .

Mathcow
fuente
1
1 : array(:)es equivalente a 1 : array(1). Esto no itera a través de todos los elementos, por lo que sus tiempos de ejecución son rápidos. Además, randgenera números de punto flotante , y al hacerlo 1 : array(:)produciría una matriz vacía ya que su declaración está tratando de encontrar un vector creciente con su valor inicial como 1 con un valor final como un número de punto flotante con un rango de [0,1)exclusivo de 1 en aumento pasos de 1. No existe tal vector posible, lo que resulta en un vector vacío. Su forciclo no se ejecuta, por lo que su afirmación es falsa. -1 voto. lo siento.
rayryeng
@rayryeng no tienes razón. array (:) no es equivalente a 1: array (1). Le gusta reshape(...).
mathcow
@rayryeng matlab r2013a + linux - ¡funciona! ;) Acabo de ejecutar ese código también
mathcow
Escriba 1 : array(:)en el símbolo del sistema después de crear array . ¿Obtienes una matriz vacía? si es así, entonces su código no funciona. Dejo mi voto porque está dando información falsa.
rayryeng
@rayryeng ¡lo entiendo! sí, tienes razón, perdón por la disputa tonta
mathcow
-1

Si observa más a fondo los otros usos de size, puede ver que en realidad puede obtener un vector del tamaño de cada dimensión. Este enlace le muestra la documentación:

www.mathworks.com/access/helpdesk/help/techdoc/ref/size.html

Después de obtener el vector de tamaño, itere sobre ese vector. Algo como esto (perdón por mi sintaxis ya que no he usado Matlab desde la universidad):

d = size(m);
dims = ndims(m);
for dimNumber = 1:dims
   for i = 1:d[dimNumber]
      ...

Convierta esto en una sintaxis legal de Matlab real, y creo que haría lo que quiera.

Además, debería poder realizar la indexación lineal como se describe aquí .

Erich Mirabal
fuente
No puedo ver cómo ese orden de bucles iterará sobre todos los elementos de una matriz. Por ejemplo, si tiene una matriz de 3 por 4 (con 12 elementos), su bucle interno solo se repetirá 7 veces.
gnovice
debería iterar sobre cada dimensión de la matriz. El bucle externo itera sobre la dimensión, el bucle interno sobre el tamaño de esa dimensión. Al menos, esa es la idea. Como todos los demás están diciendo, si todo lo que quiere es cada celda, la indexación de líneas es lo mejor. Si quiere iterar sobre cada dimensión, tendrá que hacer algo similar a esto.
Erich Mirabal
también, gracias por editar. mi enlace era un poco complicado y simplemente no funcionaba correctamente usando la forma de enlace habitual. Además, para expandir mi declaración: todavía tendría que hacer mucho otro seguimiento del índice (usando como un contador o algo así). Creo que el enfoque de usted o de Andrew sería más fácil para lo que creo que está tratando de hacer.
Erich Mirabal
-1

Quiere simular bucles for anidados n.

La iteración a través de una matriz n-dimmensional puede verse como un aumento del número de n dígitos.

En cada dimmension tenemos tantos dígitos como la longitud de la dimmension.

Ejemplo:

Supongamos que tenemos una matriz (matriz)

int[][][] T=new int[3][4][5];

en "para notación" tenemos:

for(int x=0;x<3;x++)
   for(int y=0;y<4;y++)
       for(int z=0;z<5;z++)
          T[x][y][z]=...

para simular esto, tendría que utilizar la "notación numérica de n dígitos"

Tenemos un número de 3 dígitos, con 3 dígitos para el primero, 4 para el segundo y cinco para el tercer dígito

Tenemos que aumentar el número, así obtendríamos la secuencia

0 0 0
0 0 1
0 0 2    
0 0 3
0 0 4
0 1 0
0 1 1
0 1 2
0 1 3
0 1 4
0 2 0
0 2 1
0 2 2
0 2 3
0 2 4
0 3 0
0 3 1
0 3 2
0 3 3
0 3 4
and so on

Entonces puede escribir el código para aumentar ese número de n dígitos. Puede hacerlo de tal manera que pueda comenzar con cualquier valor del número y aumentar / disminuir los dígitos en cualquier número. De esa manera, puede simular bucles for anidados que comienzan en algún lugar de la tabla y no terminan al final.

Sin embargo, esta no es una tarea fácil. Desafortunadamente, no puedo ayudar con la notación matlab.

bmegli
fuente