10

精简版:

作为第四个参数传递的函数accumarray有时会使用与编码的第一个参数的规范不一致accumarray的参数调用。

因此,用作参数的函数accumarray必须测试实际上是什么异常情况。

问题是:1 表达式匿名函数如何测试这种异常情况?更一般地说:如何编写对accumarray的未记录行为具有鲁棒性的匿名函数?


完整版本:

下面的代码是我今天大部分工作时间的问题的一个彻底提炼的版本。

首先是一些定义:

idxs = [1:3 1:3 1:3]';

vals0 = [1   4 6   3 5 7   6 Inf 2]';
vals1 = [1 Inf 6   3 5 7   6   4 2]';

anon = @(x) max(x(~isinf(x)));

注意vals1vals0通过交换元素 2 和 8 获得的。“匿名”函数anon计算其输入的非无限元素中的最大值。

鉴于这些定义,下面的两个调用

accumarray(idxs, vals0, [], anon)
accumarray(idxs, vals1, [], anon)

仅在它们的第二个参数(vals0vs )上有所不同,应该产生相同的结果,因为和vals1之间的差异仅影响对其中一个调用的参数中值的顺序,并且此函数的结果对 的顺序不敏感论据中的元素。vals0vals1anon

事实证明,这两个表达式中的第一个可以正常计算并产生正确的结果1

>> accumarray(idxs, vals0, [], anon)
ans =
     6
     5
     7

然而,第二个失败了:

>> accumarray(idxs, vals1, [], anon)
Error using accumarray
The function '@(x)max(x(~isinf(x)))' returned a non-scalar value.

为了解决这个问题,我能想出的只是写一个单独的函数在它自己的文件中,当然是“MATLAB方式”)

function out = kluge(x)
    global ncalls;
    ncalls = ncalls + 1;
    y = ~isinf(x);
    if any(y)
        out = max(x(y));
    else
        {ncalls x}
        out = NaN;
    end
end

...并运行以下内容:

>> global ncalls;
>> ncalls = int8(0); accumarray(idxs, vals0, [], @kluge)
ans =
     6
     5
     7
>> ncalls = int8(0); accumarray(idxs, vals1, [], @kluge)
ans = 
    [2]    [Inf]

ans =
     6
     5
     7

accumarray上面最后一次调用的输出可以看出,第二次调用kluge回调的参数是数组[Int]。这毫无疑问地告诉我,accumarray它的行为与记录的3不同(因为idxs没有指定长度为 1 的数组要传递给accumarray' 的函数参数)。

事实上,从这个和其他测试中,我确定,与我的预期相反,传递给的函数accumarray被调用超过max(idxs)(= 3) 次;在上面涉及的表达式中kluge,它被称为 5 次。

这里的问题是,如果不能依赖accumarray's 函数参数的实际调用方式,那么使该函数参数健壮的唯一方法是在其中包含许多额外的代码来执行必要的检查。这几乎肯定会要求函数有多个语句,这排除了匿名函数。(例如,上面的函数kluge比 更健壮anon,但我不知道如何适应匿名函数。)不能使用匿名函数accumarray大大降低了它的效用。

所以我的问题是:

如何指定可以作为健壮参数的匿名函数?accumarray


1在这篇文章中显示的所有 MATLAB 输出中,我已从 MATLAB 的典型过度填充中删除了空白行。
2我欢迎您提出任何其他故障排除建议;解决这个问题比它应该的要困难得多。
3 特别是,请参阅“函数按如下方式处理输入:”行之后的第 1 到第 5 项。

4

2 回答 2

7

简短的回答

accumarray在这种情况下,的第四个输入参数anon必须为任何输入返回一个标量。

长答案(以及关于索引排序的讨论)

考虑对索引进行排序时的输出:

>> [idxsSorted,sortInds] = sort(idxs)
>> accumarray(idxsSorted, vals0(sortInds), [], anon)
ans =
     6
     5
     7
>> accumarray(idxsSorted, vals1(sortInds), [], anon)
ans =
     6
     5
     7

现在,所有文档都必须说明以下内容:

如果 subs 中的下标未排序,则 fun 不应依赖于其输入数据中值的顺序。

这与麻烦有什么关系anon?这是一条线索,因为正如 Luis Mendo 所建议的那样,这强制anon要求为给定而不是子集/子数组调用完整的值集。idx


考虑如何accumarray处理未排序的索引和值列表:

>> [idxs vals0 vals1]
ans =
     1     1     1
     2     4   Inf
     3     6     6
     1     3     3
     2     5     5
     3     7     7
     1     6     6
     2   Inf     4
     3     2     2

对于vals0vals1, 都Inf属于idxs等于 2 的集合。由于idxs未排序,因此它不会idxs=2一次性处理 的所有值,首先。实际的算法(实现)是不透明的,但它似乎首先假设它idxs是排序的,处理第一个参数的每个单值块。fun这可以通过在第四个输入参数的函数引用中放置一个断点来验证。当它第二次遇到 1idxs时,它似乎重新开始,但随后调用包含给定索引的所有值。大概调用了全分段的一些实现funaccumarrayuniqueidxs(顺便说一句,保留顺序)。正如 kjo 所建议的那样,这是accumarray 实际处理文档中描述的输入的点,按照此处的步骤 1-5(“找出有多少唯一索引......”)。结果,它vals1anon(Inf)被调用时崩溃了 for ,但不是 for vals0,而是anon(4)在第一次尝试时调用。

然而,即使它一开始就完全遵循了这些步骤,如果一个完整的值子数组只包含Infs(考虑到anon([Inf Inf Inf])也返回一个空矩阵,它也不一定是健壮的。这是一个要求,虽然是一个低调的要求,但它fun 必须返回一个标量。文档中不清楚的是,对于任何输入,它必须返回一个标量,而不仅仅是基于算法的高级描述所期望的。


解决方法:

anon = @(x) max([x(~isinf(x));-Inf]);
于 2014-02-10T23:13:40.653 回答
6

文档并没有说仅以对应于每个值的整个集合1作为其输入来anon调用。从您的示例中可以看出,它确实会被调用它的子集。valsidx

因此,使anon健壮的方法似乎是:确保当其输入是任何子集vals(或者可能只是具有相同idx值的每个集合的任何子集)时,它给出一个标量输出。在您的情况下,anon(inf)不返回标量。

1当然,它实际上是一个数组,但我认为用集合(和子集)来描述它更容易。

于 2014-02-10T23:18:47.090 回答