29

我有一个包含数千个数据样本的结构。每个数据点包含多个对象。例如:

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”(对于上面显示的输入)。

但是,这不起作用。什么是正确的语法和/或我可以首先以更合乎逻辑的方式排列我的数据?

4

3 回答 3

25

结构数组的语法Structure.b为您提供了一个逗号分隔的 list,因此您必须将它们全部连接起来(例如,使用括号[])以获得向量:

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
于 2013-01-23T13:31:45.280 回答
9

基于 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 的版本相比),所以只在需要时使用它。

于 2013-01-23T13:49:44.280 回答
9

这个问题的另一个答案!这次,我们将比较以下 4 种方法的性能:

  1. 我原来的方法
  2. EitanT 的原始方法(不处理 emtpies)
  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

在我的 Homebox(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 :)

哇……有人知道为什么这两个系统的行为如此不同吗?

此外,鲜为人知的事实 -cellfun使用可能的字符串参数之一非常快(这表明匿名函数需要多少开销......)。

不过,如果您可以绝对确定没有空,请选择 EitanT 的原始答案;这就是 Matlab 的用途。如果你不能确定,就去循环。

于 2013-01-23T16:32:04.637 回答