Como faço para iterar por meio de cada elemento em uma matriz n-dimensional no MATLAB?

87

Eu tenho um problema. Eu preciso iterar por meio de cada elemento em uma matriz n-dimensional no MATLAB. O problema é que não sei como fazer isso para um número arbitrário de dimensões. Eu sei que posso dizer

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

e assim por diante, mas há uma maneira de fazer isso para um número arbitrário de dimensões?

rlbond
fonte
13
Nota de terminologia Matlab: Matlab tem um pequeno número de tipos de dados principais. Os mais importantes são: estrutura, matriz e matriz de células. Ao se referir a partes de uma matriz, é comum usar o termo "elemento" e reservar o termo "célula" para se referir a partes de uma matriz de células. Matrizes e matrizes de células têm inúmeras diferenças sintáticas e semânticas, embora ambas sejam estruturas de dados N-dimensionais.
Sr. Fooz,
3
Posso perguntar para que você precisa da iteração? Talvez haja uma maneira "vetorizada" de fazer isso ...
Hosam Aly

Respostas:

92

Você pode usar a indexação linear para acessar cada elemento.

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

Isso é útil se você não precisa saber em que i, j, k, você está. No entanto, se você não precisa saber em qual índice está, provavelmente será melhor usar arrayfun ()

Andrew
fonte
1
Além disso, se você quisesse recuperar os índices, por algum motivo, você ainda pode usar esses dois comandos simples: I = cell(1, ndims(array)); [I{:}] = ind2sub(size(array),idx);.
knedlsepp
34

A ideia de um índice linear para matrizes em matlab é importante. Um array no MATLAB é, na verdade, apenas um vetor de elementos, estendido na memória. O MATLAB permite que você use um índice de linha e coluna ou um único índice linear. Por exemplo,

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

A(2,3)
ans =
     7

A(8)
ans =
     7

Podemos ver a ordem em que os elementos são armazenados na memória, desenrolando o array em um vetor.

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

Como você pode ver, o 8º elemento é o número 7. Na verdade, a função find retorna seus resultados como um índice linear.

find(A>6)
ans =
     1
     6
     8

O resultado é que podemos acessar cada elemento por vez de uma matriz nd geral usando um único loop. Por exemplo, se quiséssemos elevar ao quadrado os elementos de A (sim, sei que existem maneiras melhores de fazer isso), poderíamos fazer isso:

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

Existem muitas circunstâncias em que o índice linear é mais útil. A conversão entre o índice linear e dois (ou mais) subscritos dimensionais é realizada com as funções sub2ind e ind2sub.

O índice linear se aplica em geral a qualquer array em matlab. Portanto, você pode usá-lo em estruturas, matrizes de células, etc. O único problema com o índice linear é quando eles ficam muito grandes. O MATLAB usa um número inteiro de 32 bits para armazenar esses índices. Portanto, se sua matriz tiver mais do que um total de 2 ^ 32 elementos, o índice linear falhará. É realmente apenas um problema se você usar matrizes esparsas com frequência, quando ocasionalmente isso pode causar um problema. (Embora eu não use uma versão do MATLAB de 64 bits, acredito que o problema foi resolvido para aqueles indivíduos sortudos que usam.)


fonte
A indexação no MATLAB de 64 bits permite, de fato, corretamente os subscritos de 64 bits. Por exemplo: x = ones(1,2^33,'uint8'); x(2^33)funciona conforme o esperado.
Edric
@Edric - Claro, esse é um comportamento que certamente teria mudado nos anos (e em muitos lançamentos) desde que fiz essa declaração. Obrigado por verificar.
:) Só percebi a idade da resposta depois de comentar - a pergunta simplesmente apareceu no meu feed RSS, e eu nem percebi que também a respondi!
Edric
15

Conforme apontado em algumas outras respostas, você pode iterar sobre todos os elementos em uma matriz A(de qualquer dimensão) usando um índice linear de 1a numel(A)em um único loop for. Existem também algumas funções que você pode usar: arrayfune cellfun.

Vamos primeiro supor que você tem uma função que deseja aplicar a cada elemento A(chamado my_func). Primeiro, você cria um identificador de função para esta função:

fcn = @my_func;

Se Afor uma matriz (do tipo double, single, etc.) de dimensão arbitrária, você pode usar arrayfunpara aplicar my_funca cada elemento:

outArgs = arrayfun(fcn, A);

Se Afor uma matriz de células de dimensão arbitrária, você pode usar cellfunpara aplicar my_funca cada célula:

outArgs = cellfun(fcn, A);

A função my_funcdeve ser aceita Acomo entrada. Se houver alguma saída de my_func, ela será colocada em outArgs, que terá o mesmo tamanho / dimensão de A.

Uma advertência sobre saídas ... se my_funcretorna saídas de diferentes tamanhos e tipos quando opera em diferentes elementos de A, então outArgsterá que ser transformado em uma matriz de células. Isso é feito chamando um arrayfunou cellfuncom um par parâmetro / valor adicional:

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

Um outro truque é usar ind2sube sub2ind. Em conjunto com numele size, isso pode permitir que você faça coisas como o seguinte, que cria uma matriz N-dimensional e, em seguida, define todos os elementos na "diagonal" como 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
fonte
1 para mostrar um bom exemplo de como o MATLAB quebra a digitação de pato.
Phillip Cloud
1

Você poderia fazer uma função recursiva fazer o trabalho

  • Deixei L = size(M)
  • Deixei idx = zeros(L,1)
  • Tome length(L)como profundidade máxima
  • Ciclo for idx(depth) = 1:L(depth)
  • Se sua profundidade for length(L), faça a operação do elemento, senão chame a função novamente comdepth+1

Não tão rápido quanto os métodos vetorizados se você quiser verificar todos os pontos, mas se não precisar avaliar a maioria deles, pode economizar bastante tempo.

Dennis Jaheruddin
fonte
1

essas soluções são mais rápidas (cerca de 11%) do que usar numel;)

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

ou

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

UPD. tnx @rayryeng para erro detectado na última resposta


aviso Legal

As informações de tempo referenciadas por esta postagem estão incorretas e imprecisas devido a um erro de digitação fundamental que foi cometido (veja o fluxo de comentários abaixo, bem como o histórico de edições - veja especificamente a primeira versão desta resposta). Caveat Emptor .

mathcow
fonte
1
1 : array(:)é equivalente a 1 : array(1). Isso não itera por meio de todos os elementos, por isso seus tempos de execução são rápidos. Além disso, randgera números de ponto flutuante e , ao fazer 1 : array(:)isso, produziria uma matriz vazia, pois sua instrução está tentando encontrar um vetor crescente com seu valor inicial como 1 com um valor final como um número de ponto flutuante com um intervalo [0,1)exclusivo de 1 em aumento passos de 1. Não existe tal vetor possível, o que resulta em um vetor vazio. Seu forloop não funciona e, portanto, sua afirmação é falsa. -1 voto. Desculpe.
rayryeng,
@rayryeng você não está certo. array (:) não é equivalente a 1: array (1). Gosta reshape(...).
mathcow,
@rayryeng matlab r2013a + linux - funciona! ;) Acabei de executar esse código também
mathcow,
Digite em 1 : array(:)no prompt de comando após criar array . Você obteve uma matriz vazia? se sim, então seu código não funciona. Estou deixando meu voto porque você está dando informações falsas.
rayryeng,
@rayryeng, estou entendendo! sim, você está certo, desculpe pela disputa tola
mathcow
-1

Se você olhar mais profundamente para os outros usos de, sizepoderá ver que pode realmente obter um vetor do tamanho de cada dimensão. Este link mostra a documentação:

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

Depois de obter o vetor de tamanho, itere sobre esse vetor. Algo assim (perdoe minha sintaxe, já que não uso o Matlab desde a faculdade):

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

Transforme isso em sintaxe real Matlab-legal, e eu acho que faria o que você quiser.

Além disso, você deve ser capaz de fazer a indexação linear conforme descrito aqui .

Erich Mirabal
fonte
Não consigo ver como essa ordem de loops irá iterar em todos os elementos de uma matriz. Por exemplo, se você tiver uma matriz 3 por 4 (com 12 elementos), seu loop interno irá iterar apenas 7 vezes.
gnovice,
ele deve iterar em cada dimensão da matriz. O loop externo itera sobre a dimensão, o loop interno sobre o tamanho dessa dimensão. Pelo menos, essa é a ideia. Como todo mundo está afirmando, se tudo que ele quer é cada célula, a indexação do liner é a melhor. Se ele quiser iterar em cada dimensão, terá que fazer algo semelhante a isso.
Erich Mirabal,
também, obrigado pela edição. meu link era meio complicado e simplesmente não funcionava direito usando a forma usual de link. Além disso, para expandir minha declaração: ele ainda teria que fazer muitos outros rastreamentos do índice (usando como um contador ou algo parecido). Acho que a abordagem de você ou de Andrew seria mais fácil para o que acho que ele está tentando fazer.
Erich Mirabal,
-1

Você deseja simular loops for aninhados.

A iteração por meio de uma matriz n-dimensional pode ser vista como um aumento do número de n dígitos.

Em cada dimensão temos tantos dígitos quanto o comprimento da dimensão.

Exemplo:

Suponha que tenhamos array (matriz)

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

em "para notação" temos:

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 isso, você teria que usar a "notação numérica de n dígitos"

Temos um número de 3 dígitos, com 3 dígitos para o primeiro, 4 para o segundo e cinco para o terceiro dígito

Temos que aumentar o número, para obter a sequência

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

Portanto, você pode escrever o código para aumentar esse número de n dígitos. Você pode fazer isso de forma que possa começar com qualquer valor do número e aumentar / diminuir os dígitos em quaisquer números. Dessa forma, você pode simular loops for aninhados que começam em algum lugar da tabela e não terminam no final.

Porém, esta não é uma tarefa fácil. Não posso ajudar com a notação matlab, infelizmente.

bmegli
fonte