web-dev-qa-db-fra.com

Utiliser find avec une structure

J'ai une structure qui contient des milliers d'échantillons de données. Chaque point de données contient plusieurs objets. Par exemple:

Structure(1).a = 7
Structure(1).b = 3
Structure(2).a = 2
Structure(2).b = 6
Structure(3).a = 1
Structure(3).b = 6
...
... (thousands more)
...
Structure(2345).a = 4
Structure(2345).b = 9

... etc.

Si je voulais trouver le numéro d'index de tous les objets '.b' contenant le nombre 6, je m'attendais à ce que la fonction suivante fasse l'affaire:

find(Structure.b == 6)

... et je m'attendrais à ce que la réponse contienne '2' et '3' (pour l'entrée montrée ci-dessus).

Cependant, cela ne fonctionne pas. Quelle est la syntaxe correcte et/ou pourrais-je organiser mes données de manière plus logique en premier lieu?

29
CaptainProg

La syntaxe Structure.b pour un tableau de structures vous donne liste séparée par des virgules , vous devrez donc toutes les concaténer (par exemple, en utilisant des crochets []) pour obtenir un vecteur:

find([Structure.b] == 6)

Pour l'entrée indiquée ci-dessus, le résultat est comme prévu:

ans =
     2     3

Comme Jonas l'a noté, cela ne fonctionnerait que s'il n'y a pas de champs contenant des matrices vides, car les matrices vides ne seront pas reflétées dans le résultat de la concaténation.

Gestion des structures avec des champs vides

Si vous pensez que ces champs peuvent contenir des matrices vides, convertissez-les en NaNs (si possible ...) ou envisagez d'utiliser l'une des solutions les plus sûres suggérées par Rody.

De plus, j'ai pensé à une autre solution intéressante pour cela en utilisant des chaînes. Nous pouvons tout concaténer dans une chaîne délimitée pour conserver les informations sur les champs vides, puis les recomposer (cela, à mon humble avis, est plus facile à faire dans MATLAB que de gérer les valeurs numériques stockées dans les cellules).

Inspiré par le commentaire de Jonas, nous pouvons convertir des champs vides en NaNs comme ceci:

str = sprintf('%f,', Structure.b)
B = textscan(str, '%f', 'delimiter', ',', 'EmptyValue', NaN)

et cela vous permet d'appliquer find sur le contenu de B:

find(B{:} == 6)

ans =
     2
     3
24
Eitan T

Une autre réponse à cette question! Cette fois, nous comparerons les performances des 4 méthodes suivantes:

  1. Ma méthode originale
  2. La méthode originale d'EitanT (qui ne gère pas les vides)
  3. Méthode améliorée d'EitanT utilisant des chaînes
  4. Une nouvelle méthode: une simple boucle for
  5. Une autre nouvelle méthode: une version vectorisée et sans danger pour emtpy

Code de test:

% Set up test
N = 1e5;

S(N).b = [];
for ii = 1:N
    S(ii).b = randi(6); end

% Rody Oldenhuis 1
tic
sol1 = find( cellfun(@(x)isequal(x,6),{S.b}) );
toc

% EitanT 1
tic
sol2 = find([S.b] == 6);
toc

% EitanT 2
tic
str = sprintf('%f,', S.b);
values = textscan(str, '%f', 'delimiter', ',', 'EmptyValue', NaN);
sol3 = find(values{:} == 6);
toc


% Rody Oldenhuis 2
tic
ids = false(N,1);
for ii = 1:N
    ids(ii) = isequal(S(ii).b, 6);
end
sol4 = find(ids);
toc

% Rody Oldenhuis 3
tic
idx = false(size(S));
SS = {S.b};
inds = ~cellfun('isempty', SS);
idx(inds) = [SS{inds}]==6;
sol5 = find(idx);
toc

% make sure they are all equal
all(sol1(:)==sol2(:))
all(sol1(:)==sol3(:))
all(sol1(:)==sol4(:))
all(sol1(:)==sol5(:))

Résultats sur ma machine au travail (AMD A6-3650 APU (4 cœurs), 4 Go de RAM, Windows 7 64 bits):

Elapsed time is 28.990076 seconds. % Rody Oldenhuis 1 (cellfun)
Elapsed time is 0.119165 seconds.  % EitanT 1 (no empties)
Elapsed time is 22.430720 seconds. % EitanT 2 (string manipulation)
Elapsed time is 0.706631 seconds.  % Rody Oldenhuis 2 (loop)
Elapsed time is 0.207165 seconds.  % Rody Oldenhuis 3 (vectorized)

ans =
     1
ans =
     1
ans =
     1
ans =
     1

Sur ma Homebox (AMD Phenom (tm) II X6 1100T (6 cœurs), 16 Go de RAM, Ubuntu64 12.10):

Elapsed time is 0.572098 seconds.  % cellfun
Elapsed time is 0.119557 seconds.  % no emtpties
Elapsed time is 0.220903 seconds.  % string manipulation
Elapsed time is 0.107345 seconds.  % loop
Elapsed time is 0.180842 seconds.  % cellfun-with-string

Je dois aimer ce JIT :)

et wow ... quelqu'un sait pourquoi les deux systèmes se comportent si différemment?

En outre, un fait peu connu - cellfun avec l'un des arguments de chaîne possibles est incroyablement rapide (ce qui montre combien de fonctions anonymes de surcharge nécessitent ...).

Pourtant, si vous pouvez être absolument sûr qu'il n'y a pas de vidange, optez pour la réponse originale d'EitanT; c'est à ça que sert Matlab. Si vous n'êtes pas sûr, optez simplement pour la boucle.

9
Rody Oldenhuis

En s'appuyant sur la réponse d'EitanT avec le commentaire de Jonas, un moyen plus sûr pourrait être

>> S(1).a = 7;
   S(1).b = 3;
   S(2).a = 2;
   S(2).b = 6;
   S(3).a = 1;
   S(3).b = [];
   S(4).a = 1;
   S(4).b = 6;

>> find( cellfun(@(x)isequal(x,6),{S.b}) )
ans =
     2     4

Ce n'est probablement pas très rapide cependant (par rapport à la version d'EitanT), alors n'utilisez cela qu'en cas de besoin.

9
Rody Oldenhuis