web-dev-qa-db-ja.com

構造体でfindを使用する

数千のデータサンプルを保持する構造体があります。各データポイントには複数のオブジェクトが含まれます。例えば:

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

... 等々。

番号6を含むすべての「.b」オブジェクトのインデックス番号を検索したい場合、次の関数がトリックを実行すると予想していました。

find(Structure.b == 6)

...そして、答えには「2」と「3」が含まれることが予想されます(上記の入力の場合)。

ただし、これは機能しません。正しい構文は何ですか?そもそも、より論理的な方法でデータを配置できますか?

29
CaptainProg

構文Structure.bは、構造体の配列に対して コンマ区切りリスト を提供するため、すべてを連結する必要があります(たとえば、_[])ベクトルを取得するには:

find([Structure.b] == 6)

上記の入力の場合、結果は期待どおりです。

ans =
     2     3

Jonasが指摘したように、空のマトリックスは連結結果に反映されないため、空のマトリックスを含むフィールドがない場合にのみ機能します。

空のフィールドを持つ構造体の処理

これらのフィールドに空の行列が含まれていると思われる場合は、それらをNaNsに変換するか(可能であれば...)、Rodyが提案するより安全なソリューションのいずれかを使用することを検討してください。

さらに、文字列を使用して、これに対する別の興味深い回避策を考えました。空のフィールドに関する情報を保持するためにすべてを区切られた文字列に連結し、それをトークン化して戻すことができます(これは私の控えめな意見では、セルに格納された数値を処理するよりもMATLABで行う方が簡単です)。

Jonasのコメントに触発されて、空のフィールドをNaNsに変換できます:

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

これにより、findの内容にBを適用できます。

find(B{:} == 6)

ans =
     2
     3
24
Eitan T

この質問への別の答え!今回は、次の4つの方法のパフォーマンスを比較します。

  1. 私のオリジナルの方法
  2. EitanTの元のメソッド(emptyを処理しない)
  3. EitanTの文字列を使用した改善された方法
  4. 新しい方法:単純なforループ
  5. 別の新しい方法:ベクトル化された、空の安全なバージョン

テストコード:

% 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(:))

職場のマシンでの結果(AMD A6-3650 APU(4コア)、4GB RAM、Windows 7 64ビット):

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

ホームボックス(AMD Phenom(tm)II X6 1100T(6コア)、16GB 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

そのJITが大好きだ:)

そしてすごい...誰もが2つのシステムが非常に異なる動作をする理由を知っていますか?

また、ほとんど知られていない事実- cellfunは、可能な文字列引数の1つを使用すると非常に高速です (匿名関数が必要とするオーバーヘッドの量を示します...)。

それでも、空がないことを完全に確信できる場合は、EitanTの元の答えに進んでください。それがMatlabの目的です。確信が持てない場合は、ループに進んでください。

9
Rody Oldenhuis

EitanTの答えをJonasのコメントに基づいて構築すると、より安全な方法は

>> 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

ただし、おそらく(EitanTのバージョンと比較して)それほど高速ではないため、必要な場合にのみ使用してください。

9
Rody Oldenhuis