简短版:
作为accumarray
的第四个参数传递的函数有时会被不一致的参数调用,其中规范将第一个参数编码为accumarray
。
因此,用作accumarray
参数的函数必须测试实际上是异常条件的内容。
问题是:1-expression匿名函数如何测试这种异常情况?更一般地说:如何编写对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)));
注意vals1
是通过交换元素2和8从vals0
获得的。“匿名”函数anon
计算其输入的非无限元素中的最大值。
鉴于这些定义,下面的两个调用
accumarray(idxs, vals0, [], anon)
accumarray(idxs, vals1, [], anon)
只有第二个参数(vals0
vs vals1
)不同的应该产生相同的结果,因为vals0
和vals1
之间的差异仅影响调用anon
之一的参数中的值,并且此函数的结果对其参数中元素的排序不敏感。
事实证明,这两个表达式中的第一个正常评估并产生正确的结果 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.
要解决这个问题,我只能想出 2 就是编写一个单独的函数(当然是“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
的函数参数将被实际调用,那么使这个函数参数健壮的唯一方法是在其中包含许多额外的代码来执行必要的检查。这几乎肯定会要求函数有多个语句,这些语句排除了匿名函数。 (例如,上面的函数kluge
比anon
更健壮,但我不知道如何适应匿名函数。)无法使用accumarray
的匿名函数降低其效用。
所以我的问题是:
如何指定匿名函数,这些函数可以是
accumarray
的健全参数?
1 我在本文所示的所有MATLAB输出中删除了MATLAB典型的过填充空白行。
2 我欢迎您提出任何其他疑难解答建议的评论;解决这个问题比应该解决的要困难得多。
<子> 3
特别是,请参阅“函数处理输入后面的第1到5项,如下所示:”。
子>
答案 0 :(得分: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
的问题有什么关系?这是一个线索,因为这会强制anon
为给定的idx
而不是子集/子阵列调用完整的值集,正如Luis Mendo所建议的那样。
考虑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
对于vals0
和vals1
,Inf
属于idxs
等于2的集合。由于idxs
未排序,因此不会处理idxs=2
一次性idxs
的所有值,首先。实际算法(实现)是不透明的,但似乎首先假设fun
已排序,处理第一个参数的每个单值块。这可以通过在idxs
中放置一个断点来验证,第四个输入参数是函数引用。当它在第二时遇到fun
中的1时,它似乎重新开始,但随后调用accumarray
包含给定索引的所有值。据推测,unique
会将idxs
的某些实现称为完全细分accumarray
(顺便提一下,订单不保留)。正如kjo建议的那样,这是vals1
实际处理输入的点,如文档中所述,steps 1-5 here之后(“找出有多少独特索引... “)。因此,anon(Inf)
在调用vals0
时会崩溃,但anon(4)
则不会,而是在第一次尝试时调用Inf
。
然而,即使它在第一次完全遵循这些步骤,如果仅包含anon([Inf Inf Inf]
s的完整的子数组(考虑fun
)返回空矩阵,它也不一定是健壮的太)。尽管{1}} 必须返回标量,但这是一项要求,尽管是低调的。文档中不清楚的是它必须返回一个标量,用于任何输入,而不仅仅是基于算法的高级描述所期望的。
解决方法:
anon = @(x) max([x(~isinf(x));-Inf]);
答案 1 :(得分:6)
文档没有说明anon
仅被称为 ,vals
的整个集合 1 对应于{{1}的每个值作为它的输入。如您的示例所示,它会使用其子集进行调用。
所以使idx
健壮的方法似乎是:当输入是anon
的任何子集时(或者可能只是每个集合的任何子集具有相同的{ - ),确保它给出标量输出。 {1}}值)。在您的情况下,vals
不会返回标量。
1 当然,它实际上是一个数组,但我认为用集来描述它更容易(和子集)。子>