Comment parcourir chaque élément d'une matrice à n dimensions dans MATLAB?

87

J'ai un problème. J'ai besoin de parcourir chaque élément d'une matrice à n dimensions dans MATLAB. Le problème est que je ne sais pas comment faire cela pour un nombre arbitraire de dimensions. Je sais que je peux dire

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

et ainsi de suite, mais y a-t-il un moyen de le faire pour un nombre arbitraire de dimensions?

rlbond
la source
13
Note terminologique Matlab: Matlab a un petit nombre de types de données de base. Les plus importants sont: struct, matrix et cell array. Lorsqu'on fait référence à des parties d'une matrice, il est courant d'utiliser le terme «élément» et de réserver le terme «cellule» pour faire référence à des parties d'un tableau de cellules. Les tableaux de cellules et les matrices présentent de nombreuses différences syntaxiques et sémantiques, même si les deux sont des structures de données à N dimensions.
Mr Fooz
3
Puis-je vous demander pourquoi vous avez besoin de l'itération? Il y a peut-être un moyen "vectorisé" de le faire à la place ...
Hosam Aly

Réponses:

92

Vous pouvez utiliser l'indexation linéaire pour accéder à chaque élément.

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

Ceci est utile si vous n'avez pas besoin de savoir en quoi vous êtes i, j, k. Cependant, si vous n'avez pas besoin de savoir à quel index vous vous trouvez, il vaut probablement mieux utiliser arrayfun ()

Andrew
la source
1
En outre, si vous voulez récupérer les indices pour une raison quelconque, vous pouvez toujours utiliser ces deux commandes simples: I = cell(1, ndims(array)); [I{:}] = ind2sub(size(array),idx);.
knedlsepp
34

L'idée d'un index linéaire pour les tableaux dans matlab est importante. Un tableau dans MATLAB n'est en réalité qu'un vecteur d'éléments, étiré en mémoire. MATLAB vous permet d'utiliser soit un index de ligne et de colonne, soit un seul index linéaire. Par exemple,

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

A(2,3)
ans =
     7

A(8)
ans =
     7

Nous pouvons voir l'ordre dans lequel les éléments sont stockés en mémoire en déroulant le tableau dans un vecteur.

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

Comme vous pouvez le voir, le 8ème élément est le numéro 7. En fait, la fonction find renvoie ses résultats sous forme d'index linéaire.

find(A>6)
ans =
     1
     6
     8

Le résultat est que nous pouvons accéder à chaque élément tour à tour d'un tableau nd général en utilisant une seule boucle. Par exemple, si nous voulions mettre au carré les éléments de A (oui, je sais qu'il existe de meilleures façons de le faire), on pourrait le faire:

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

Il existe de nombreuses circonstances dans lesquelles l'indice linéaire est plus utile. La conversion entre l'indice linéaire et les indices à deux dimensions (ou plus) est effectuée avec les fonctions sub2ind et ind2sub.

L'index linéaire s'applique en général à n'importe quel tableau dans matlab. Vous pouvez donc l'utiliser sur des structures, des tableaux de cellules, etc. Le seul problème avec l'index linéaire est quand ils deviennent trop grands. MATLAB utilise un entier 32 bits pour stocker ces index. Donc, si votre tableau contient plus d'un total de 2 ^ 32 éléments, l'index linéaire échouera. Ce n'est vraiment un problème que si vous utilisez souvent des matrices éparses, alors que cela posera parfois un problème. (Bien que je n'utilise pas de version 64 bits de MATLAB, je pense que ce problème a été résolu pour les personnes chanceuses qui le font.)


la source
L'indexation dans MATLAB 64 bits autorise en effet correctement les indices 64 bits. Par exemple: x = ones(1,2^33,'uint8'); x(2^33)fonctionne comme prévu.
Edric
@Edric - Bien sûr, c'est un comportement qui aurait sûrement changé au cours des années (et de nombreuses versions) depuis que j'ai fait cette déclaration. Merci d'avoir vérifié.
:) Je ne me suis rendu compte de l'âge de la réponse qu'après avoir commenté - la question est apparue dans mon flux RSS, et je n'ai même pas remarqué que j'avais répondu aussi!
Edric
15

Comme indiqué dans quelques autres réponses, vous pouvez parcourir tous les éléments d'une matrice A(de n'importe quelle dimension) en utilisant un index linéaire de 1à numel(A)dans une seule boucle for. Vous pouvez également utiliser quelques fonctions: arrayfunet cellfun.

Supposons d'abord que vous ayez une fonction que vous souhaitez appliquer à chaque élément de A(appelé my_func). Vous créez d'abord un descripteur de fonction pour cette fonction:

fcn = @my_func;

S'il As'agit d'une matrice (de type double, simple, etc.) de dimension arbitraire, vous pouvez utiliser arrayfunpour appliquer my_funcà chaque élément:

outArgs = arrayfun(fcn, A);

S'il As'agit d'un tableau de cellules de dimension arbitraire, vous pouvez utiliser cellfunpour appliquer my_funcà chaque cellule:

outArgs = cellfun(fcn, A);

La fonction my_funcdoit accepter Acomme entrée. S'il y a des sorties de my_func, celles-ci sont placées dans outArgs, qui auront la même taille / dimension que A.

Une mise en garde sur les sorties ... si my_funcrenvoie des sorties de tailles et de types différents lorsqu'il fonctionne sur différents éléments de A, alors outArgsdevra être transformé en un tableau de cellules. Cela se fait en appelant soit arrayfunou cellfunavec une paire paramètre / valeur supplémentaire:

outArgs = arrayfun(fcn, A, 'UniformOutput', false);
outArgs = cellfun(fcn, A, 'UniformOutput', false);
novice
la source
13

Une autre astuce consiste à utiliser ind2subet sub2ind. En conjonction avec numelet size, cela peut vous permettre de faire des choses comme ce qui suit, qui crée un tableau à N dimensions, puis définit tous les éléments sur la "diagonale" sur 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
la source
+1 pour montrer un bon exemple de la façon dont MATLAB rompt la frappe de canard.
Phillip Cloud
1

Vous pouvez faire en sorte qu'une fonction récursive fasse le travail

  • Laisser L = size(M)
  • Laisser idx = zeros(L,1)
  • Prendre length(L)comme profondeur maximale
  • Boucle for idx(depth) = 1:L(depth)
  • Si votre profondeur est length(L), effectuez l'opération d'élément, sinon appelez à nouveau la fonction avecdepth+1

Pas aussi rapide que les méthodes vectorisées si vous voulez vérifier tous les points, mais si vous n'avez pas besoin d'évaluer la plupart d'entre eux, cela peut être un gain de temps considérable.

Dennis Jaheruddin
la source
1

ces solutions sont plus rapides (environ 11%) que l'utilisation numel;)

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

ou

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

UPD. tnx @rayryeng pour erreur détectée dans la dernière réponse


Avertissement

Les informations de synchronisation auxquelles ce post a fait référence sont incorrectes et inexactes en raison d'une faute de frappe fondamentale qui a été faite (voir le flux de commentaires ci-dessous ainsi que l' historique des modifications - regardez en particulier la première version de cette réponse). Caveat Emptor .

mathcow
la source
1
1 : array(:)équivaut à 1 : array(1). Cela n'itère pas tous les éléments, c'est pourquoi vos temps d'exécution sont rapides. De plus, randgénère des nombres à virgule flottante , ce 1 : array(:)qui produirait un tableau vide car votre instruction essaie de trouver un vecteur croissant avec sa valeur initiale comme 1 avec une valeur de fin comme un nombre à virgule flottante avec une plage d' [0,1)exclusivité de 1 en augmentation pas de 1. Un tel vecteur n'est pas possible, ce qui donne un vecteur vide. Votre forboucle ne s'exécute pas et votre affirmation est donc fausse. -1 vote. Pardon.
rayryeng
@rayryeng vous n'avez pas raison. array (:) n'est pas équivalent à 1: array (1). Il aime reshape(...).
mathcow
@rayryeng matlab r2013a + linux - ça marche! ;) Je viens d'
exécuter
Tapez 1 : array(:)dans votre invite de commande après la création array . Obtenez-vous une matrice vide? si oui, votre code ne fonctionne pas. Je quitte mon vote parce que vous donnez de fausses informations.
rayryeng
@rayryeng je comprends! oui, vous avez raison, désolé pour une dispute stupide
mathcow
-1

Si vous examinez plus en détail les autres utilisations de, sizevous pouvez voir que vous pouvez réellement obtenir un vecteur de la taille de chaque dimension. Ce lien vous montre la documentation:

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

Après avoir obtenu le vecteur de taille, parcourez ce vecteur. Quelque chose comme ça (pardonnez ma syntaxe puisque je n'ai pas utilisé Matlab depuis l'université):

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

Faites-en une véritable syntaxe légale Matlab, et je pense que cela ferait ce que vous voulez.

De plus, vous devriez pouvoir effectuer l'indexation linéaire comme décrit ici .

Erich Mirabal
la source
Je ne vois pas vraiment comment cet ordre des boucles va itérer sur tous les éléments d'une matrice. Par exemple, si vous avez une matrice 3 par 4 (avec 12 éléments), votre boucle interne n'itérera que 7 fois.
gnovice
il doit itérer sur chaque dimension de la matrice. La boucle extérieure itère sur la dimension, la boucle intérieure sur la taille de cette dimension. Du moins, c'est l'idée. Comme tout le monde le dit, si tout ce qu'il veut, c'est chaque cellule, l'indexation des doublures est la meilleure. S'il veut parcourir chaque dimension, il devra faire quelque chose de similaire.
Erich Mirabal
aussi, merci pour l'édition. mon lien était un peu compliqué et ne fonctionnerait tout simplement pas correctement en utilisant la méthode de liaison habituelle. Aussi, pour développer ma déclaration: il devrait encore faire beaucoup d'autres suivis de l'index (en utilisant comme un compteur ou quelque chose comme ça). Je pense que votre approche ou celle d'Andrew serait plus facile pour ce que je pense qu'il essaie de faire.
Erich Mirabal
-1

Vous souhaitez simuler des boucles for imbriquées.

L'itération à travers un tableau à n dimensions peut être considérée comme une augmentation du nombre à n chiffres.

A chaque dimmension, nous avons autant de chiffres que la longueur de la dimmension.

Exemple:

Supposons que nous ayons un tableau (matrice)

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

en "pour la notation" nous avons:

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]=...

pour simuler cela, vous devrez utiliser la "notation des nombres à n chiffres"

Nous avons un numéro à 3 chiffres, avec 3 chiffres pour le premier, 4 pour le deuxième et cinq pour le troisième chiffre

Nous devons augmenter le nombre pour obtenir la séquence

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

Ainsi, vous pouvez écrire le code pour augmenter ce nombre à n chiffres. Vous pouvez le faire de telle manière que vous pouvez commencer avec n'importe quelle valeur du nombre et augmenter / diminuer les chiffres de n'importe quel nombre. De cette façon, vous pouvez simuler des boucles imbriquées pour qui commencent quelque part dans le tableau et ne se terminent pas à la fin.

Ce n’est cependant pas une tâche facile. Je ne peux malheureusement pas aider avec la notation matlab.

bmegli
la source