向量化间隔限制 table 中的查找值

vectorize lookup values in table of interval limits

这里有一个问题,关于我们是否可以在matlab中使用向量化类型的操作来避免写for循环。

我有一个向量

Q = [0.1,0.3,0.6,1.0]

我在 [0,1)

上生成一个均匀分布的随机向量
X = [0.11,0.72,0.32,0.94]

我想知道 X 的每个条目是否在 [0,0.1)[0.1,0.3)[0.3,0.6)[0.6,1.0) 之间,我想 return 一个向量,其中包含 Q 中每个条目小于 X 的最大元素的索引。

我可以写一个 for 循环

Y = zeros(length(X),1)
for i = 1:1:length(X)
    Y(i) = find(X(i)<Q, 1);
end

此示例的预期结果:

Y = [2,4,3,4]

但是我想知道有没有办法避免写for循环? (我看到很多很好的回答我的问题。非常感谢!现在如果我们更进一步,如果我的 Q 是一个矩阵,我想检查是否)

Y = zeros(length(X),1)
for i = 1:1:length(X)
    Y(i) = find(X(i)<Q(i), 1);
end

您可以创建一个匿名函数来执行比较,然后使用 arrayfun:

将其应用于 X 的每个成员
compareFunc = @(x)find(x < Q, 1);
result = arrayfun(compareFunc, X, 'UniformOutput', 1);

创建匿名函数时,Q数组将存储在匿名函数(compareFunc)中。

或者,作为一行(统一输出是 arrayfun 的默认行为):

result = arrayfun(@(x)find(x < Q, 1), X);

使用bsxfun 将有助于实现这一点。你需要阅读它。我还在开头加了一个Q = 0来处理小X的情况

X = [0.11,0.72,0.32,0.94 0.01];
Q = [0.1,0.3,0.6,1.0];
Q_extra = [0 Q];

Diff = bsxfun(@minus,X(:)',Q_extra (:)); %vectorized subtraction
logical_matrix = diff(Diff < 0); %find the transition from neg to positive
[X_categories,~] = find(logical_matrix == true); % get indices

% 输出为 2 4 3 4 1

编辑:每种方法需要多长时间?

我很好奇每个解决方案之间的区别:

下面的测试代码:

Q = [0,0.1,0.3,0.6,1.0];

X = rand(1,1e3);

tic
Y = zeros(length(X),1);
for i = 1:1:length(X)
    Y(i) = find(X(i)<Q, 1);
end
toc
tic
result = arrayfun(@(x)find(x < Q, 1), X);
toc

tic
Q = [0 Q];
Diff = bsxfun(@minus,X(:)',Q(:)); %vectorized subtraction
logical_matrix = diff(Diff < 0); %find the transition from neg to positive
[X_categories,~] = find(logical_matrix == true); % get indices
toc

运行 你自己看,我发现当 X 的大小为 1e6 时,bsxfun 快得多,而对于较小的数组,差异是变化的并且可以忽略不计。

示例:当尺寸 X 为 1e3

Elapsed time is 0.001582 seconds. % for loop
Elapsed time is 0.007324 seconds. % anonymous function
Elapsed time is 0.000785 seconds. % bsxfun

Octave 为您提供了一个巧妙的自动矢量化技巧,如果您拥有的矢量沿着不同的维度。如果你让Q成为一个列向量,你可以这样做:

X = [0.11, 0.72, 0.32, 0.94];
Q = [0.1; 0.3; 0.6; 1.0; 2.0; 3.0];
X <= Q

结果是一个 6x4 矩阵,表示 Q 的每个元素小于 X 的哪些元素。我将 Q 设置为与 X 不同的长度只是为了说明这一点:

0   0   0   0
1   0   0   0
1   0   1   0
1   1   1   1
1   1   1   1
1   1   1   1

回到你原来的例子,你可以做

length(Q) - sum(X <= Q) + 1

获得

2   4   3   4

请注意,我在 Q 的定义中使用了分号而不是逗号。如果你想在定义它之后使它成为一个列向量,那么做这样的事情:

length(Q) - sum(X <= Q') + 1

之所以可行,是因为 Octave 隐式地将 bsxfun 应用于行和列向量上的操作。根据@excaza 的评论,MATLAB 在 R2016b 之前不会这样做,所以在 MATLAB 中你可以这样做:

length(Q) - sum(bsxfun(@le, X, Q)) + 1

您可以在 IDEOne 中试用此示例 here

Octave 有一个函数 lookup 可以做到这一点。它需要查找 table 排序值和一个数组,以及 returns 一个包含查找中值索引的数组 table.

octave> Q = [0.1 0.3 0.6 1.0];
octave> x = [0.11 0.72 0.32 0.94];
octave> lookup (Q, X)
ans =

   1   3   2   3

唯一的问题是您的查找 table 有一个隐含的零,可以很容易地修复:

octave> lookup ([0 Q], X) # alternatively, just add 1 at the results
ans =

   2   4   3   4

使用 max 的第二个输出,作为一种 "vectorized find":

[~, Y] = max(bsxfun(@lt, X(:).', Q(:)), [], 1);

这是如何工作的

  1. 对于X的每个元素,测试它是否小于Q的每个元素。这是用 bsxfun(@lt, X(:).', Q(:)) 完成的。请注意,结果中的每一列对应于 X 的一个元素,每一行对应于 Q.
  2. 的一个元素
  3. 然后,对于 X 的每个元素,获取 Q 的第一个比较为 true 的元素的索引。这是用 [~, Y] = max(..., [], 1) 完成的。请注意 max returns 的第二个输出是 first 最大化器的索引(沿着指定的维度),因此在这种情况下它给出了第一个的索引每列 true

对于您的示例值,

Q = [0.1, 0.3, 0.6, 1.0];
X = [0.11, 0.72, 0.32, 0.94];
[~, Y] = max(bsxfun(@lt, X(:).', Q(:)), [], 1);

给予

Y =
     2     4     3     4

受@Mad Physicist 发布的解决方案的启发,这是我的解决方案。

Q = [0.1,0.3,0.6,1.0]   
X = [0.11,0.72,0.32,0.94] 
Temp = repmat(X',1,4)<repmat(Q,4,1)
[~, ind]= max( Temp~=0, [], 2 );

思路是将X和Q做成"same shape",然后使用元素比较,然后我们得到一个逻辑矩阵,其行告诉X中的给定元素是否小于每个元素在 Q 中,然后 return 这个逻辑矩阵每一行的第一个非零索引。我还没有测试过这种方法与其他方法相比有多快