为什么MATLAB对结构体数组分配中的字段顺序敏感?

10

首先,我将A定义为一个结构体,并定义了另外两个结构体:B元素的顺序与A相同,而C的元素顺序不同。

A.x = 11;
A.y = 11;

B.x = 21;
B.y = 22;

C.y = 31;   %// Note that I am specifying
C.x = 32;   %// y first and x second

A = B;      %// Works fine
A = C;      %// Works fine

A赋值给BC是可以的,这也是我从结构体中预期的行为——元素的顺序不应该影响结果。

现在我将A指定为一个结构体数组,尝试将它的一个元素分别赋值给BC

clear;

A(1).x = 11;
A(1).y = 12;

B.x = 21;
B.y = 22;

C.y = 31;   %// Note that I am specifying
C.x = 32;   %// y first and x second

A(1) = B;   %// Works fine
A(1) = C;   %// Error!

MATLAB突然报错:

不同结构之间的下标赋值

有人知道为什么会出现这种情况,以及如何优雅地修复它吗?

2个回答

11
这很可能是因为内置的struct subsasgn 函数只是比较源结构体和目标结构体的fieldnames的输出结果(这取决于字段的顺序),并且在比较之前不执行任何排序操作(可能是因为为每次赋值排序两个单元数组会影响性能)。 如果有差异(如您展示的情况),则会抛出错误并中止赋值操作。

最简单的方法是在源结构上使用 orderfields,并指定第二个输入参数来匹配目标结构的排序顺序。

A = struct('x', 11, 'y', 12);
B = struct('y', 21, 'x', 22);

%// Ensure that the fields of B are in the same order as they are in A
A(1) = orderfields(B, A);
在我个人看来,我认为subsasgn应该自己处理struct输入,因为这个操作相对较快(没有排序,struct的底层数据也没有被复制),但可以允许更灵活的struct赋值。
另一方面,如果你不是在进行直接赋值,而只是想要合并两个structs,那么字段的顺序不重要,并且字段的顺序是从第一个遇到的struct继承的。
%// Uses the ordering of the fields in A
C = cat(1, A, B);

%// Uses the ordering of the fields in B
D = cat(1, B, A);

更新

我刚注意到你在原始帖子中展示了以下结果,因为顺序没有关系而起作用。

A = B;

这种方法可行是因为这个赋值操作与 A 的数据类型无关。在这种情况下,MATLAB 会移除赋值前A所引用的数据,然后将 A重新指向B。我们甚至可以将A定义为单元数组并执行上述赋值操作而不会出现问题。

A = cell(2);
B = struct('y', 21, 'x', 22);

%// No errors here!
A = B; 

这个任务没有调用subsasgn(它只涉及下标分配),因此不应该出现你遇到的问题。


3
好的,问题得到解决了,但这种方法仍然感觉有些粗糙。对我来说,这似乎是MATLAB的一个缺陷。(仍然点赞是因为它解决了问题) - MGA
@MGA 我更新了代码以展示你只能在其中一个结构体上使用 orderfields。但是,我并不完全确定为什么MATLAB会表现出这种行为。很可能是因为在赋值期间内部比较了两个结构体的 fieldnames 输出(未排序)。另一种选择是在循环内进行赋值,但那可能会导致性能下降。 - Suever
2
@MGA:这样会更方便但速度会变慢。它需要排序,对于许多领域来说代价很高。 - Daniel
听起来像是自动类型检测中的缺陷。由于某种原因,Matlab无法推断它是相同的结构体。可能是由于不同的对齐方式或其他原因。总之,我认为自动重新排序字段并不是一件好事。通常情况下,我会说这是一个编程错误。如果用户没有预定义结构体,我会说他不能指望Matlab做很多额外的检查来确定结构体是否是相同类型的。在Matlab中,你已经为太多你不使用的东西付费了。 - patrik
1
@patrik 我同意,最终一致的排序应该由程序员来确保。我只是认为subsasgncat在字段排序方面的不同行为有点奇怪。如果排序很重要,至少要让它保持一致。 - Suever

4

过去我解决这个问题的一种方法是创建一个“空”的结构版本,类似于为对象创建构造函数。

%% define null struct
null_struct.x = 0;
null_struct.y = 0;

%% Now, initialize all structs with it
A=null_struct;
B=null_struct;
C=null_struct;

%% You can even initialize large arrays
Z(1:1000, 1:1000) = null_struct;

然后,您可以按任何顺序填充结构。 您甚至可以将“空”结构传递到函数中,并允许函数填充值,函数无需关心分配值的顺序。

A(1).x = 11;
A(1).y = 12;

B.x = 21;
B.y = 22;

C.y = 31;   % Note that I'm specifying
C.x = 32;   % y first and x second

A(1) = B;   % Works fine
A(1) = C;   % Also works fine!

初始化数据结构是非常好的编程实践,对于大型结构数组,事先进行初始化实际上可以节省很多时间。即使您必须初始化比所需更多的元素,并在最后截断数组,这通常也是值得的。

编辑:关于您的错误解释: MATLAB抛出错误的原因是,在内部(在C代码后端),MATLAB将字段名称存储在有序的字符字符串数组中,并将名称映射到相应的字段索引。当您执行类似于

A = C;

MATLAB首先检查两个字段名列表是否匹配,这要求两个列表完全相同,包括顺序也相同。如果匹配,则按顺序将右侧的字段值映射到左侧。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接