8.1.1 高级索引

链式索引

Octave 允许使用重复(链式)索引表达式,在单个命令中提取数组的子集,而无需使用中间变量。这可以使编写包含复杂索引操作或使用多种索引方法的代码更加容易。以下示例展示了两个等效的索引提取操作:

A = reshape (1:16, 4, 4);
B = A(2:4, 2:3);
C = B(3:5);
D = C( [ true, false, true ] )
     ⇒  D = [ 8, 11 ]

D = A(2:4, 2:3)(3:5)([ true, false, true ])
     ⇒  D = [ 8, 11 ]

链式索引必然比产生相同结果的单个索引表达式慢,但与使用中间变量赋值执行多个离散索引操作相比,通常计算效率更高。

请注意,链式索引仅适用于右值表达式,不能用于赋值操作的左侧。

分量到线性索引的转换

当需要从数组中提取条目的子集,且这些条目的索引无法表示为各分量的笛卡尔积时,可以使用线性索引配合 sub2ind 函数。例如:

A = reshape (1:8, 2, 2, 2)  # 创建三维数组
A =

ans(:,:,1) =

   1   3
   2   4

ans(:,:,2) =

   5   7
   6   8

A(sub2ind (size (A), [1, 2, 1], [1, 1, 2], [1, 2, 1]))
   ⇒  ans = [A(1, 1, 1), A(2, 1, 2), A(1, 2, 1)]
 
ind = sub2ind (dims, i, j)
ind = sub2ind (dims, s1, s2, …, sN)

将下标转换为线性索引。

输入 dims 是一个维度向量,其中每个元素是数组在相应维度上的大小(参见 size)。其余输入是要转换的标量或下标向量。

输出向量 ind 包含转换后的线性索引。

背景:数组元素既可以由从 1 开始、遍历数组中所有元素的线性索引指定,也可以由行、列、页等的下标指定。ind2subsub2ind 函数实现这两种形式之间的相互转换。

线性索引依次遍历维度 1(行)、维度 2(列)、维度 3(页)等,直到对所有元素进行编号。考虑以下 3×3 矩阵:

[(1,1), (1,2), (1,3)]     [1, 4, 7]
[(2,1), (2,2), (2,3)] ==> [2, 5, 8]
[(3,1), (3,2), (3,3)]     [3, 6, 9]

左侧矩阵包含每个矩阵元素的下标元组。右侧矩阵显示同一矩阵的线性索引。

以下示例展示了如何通过一次 sub2ind 调用,将 3×3 矩阵的二维索引 (2,1)(2,3) 转换为线性索引。

s1 = [2, 2];
s2 = [1, 3];
ind = sub2ind ([3, 3], s1, s2)
    ⇒  ind =  2   8

另请参见: ind2sub, size.

 
[s1, s2, …, sN] = ind2sub (dims, ind)

将线性索引转换为下标。

输入 dims 是一个维度向量,其中每个元素是数组在相应维度上的大小(参见 size)。第二个输入 ind 包含要转换的线性索引。

输出 s1, …, sN 包含转换后的下标。

背景:数组元素既可以由从 1 开始、遍历数组中所有元素的线性索引指定,也可以由行、列、页等的下标指定。ind2subsub2ind 函数实现这两种形式之间的相互转换。

线性索引依次遍历维度 1(行)、维度 2(列)、维度 3(页)等,直到对所有元素进行编号。考虑以下 3×3 矩阵:

[1, 4, 7]     [(1,1), (1,2), (1,3)]
[2, 5, 8] ==> [(2,1), (2,2), (2,3)]
[3, 6, 9]     [(3,1), (3,2), (3,3)]

左侧矩阵包含每个矩阵元素的线性索引。右侧矩阵显示同一矩阵的下标元组。

以下示例展示了如何将线性索引 28 转换为 3×3 矩阵的相应下标。

ind = [2, 8];
[r, c] = ind2sub ([3, 3], ind)
    ⇒  r =  2   2
    ⇒  c =  1   3

如果输出下标的数量超过维度的数量,则多余的维度被设置为 1。另一方面,如果提供的下标少于维度的数量,则超出的维度会合并到最终请求的维度中。为清晰起见,请考虑以下示例:

ind  = [2, 8];
dims = [3, 3];
## 等同于 dims = [3, 3, 1]
[r, c, s] = ind2sub (dims, ind)
    ⇒  r =  2   2
    ⇒  c =  1   3
    ⇒  s =  1   1
## 等同于 dims = [9]
r = ind2sub (dims, ind)
    ⇒  r =  2   8

另请参见: sub2ind, size.

 
tf = isindex (ind)
tf = isindex (ind, n)

如果 ind 是有效索引,则返回 true。

有效索引既可以是正整数(尽管可能为实数数据类型),也可以是逻辑数组。

如果提供了参数 n,则指定要索引的维度的最大范围。在可能的情况下,内部结果会被缓存,以便后续使用 ind 进行索引时不再重复执行检查。

实现说明:在进行有效索引检查之前,字符串首先被转换为双精度值。除非字符串包含 NULL 字符 "\0",否则它始终是有效索引。

分量数量不等于维度数

具有 ‘nd’ 个维度的数组,可以用包含 1 到 ‘nd’ 个分量的索引表达式进行索引。在普通且最常见的情况下,分量数量 ‘M’ 与维度数量 ‘nd’ 相匹配。此时应用普通索引规则,每个分量对应数组的相应维度。

然而,如果索引分量的数量超过维度的数量(M > nd),则超出的分量必须都是单维度(1)。此外,如果 M < nd,其行为等效于对输入对象进行重塑,将末尾 nd - M 个维度合并到最后一个索引维度 M 中。因此,结果将具有索引表达式的维度,而非原始对象的维度。只要索引的维度数量大于 1(M > 1),就会发生这种情况,因此线性索引的特殊规则不再适用。通过下面的示例可以最容易理解这一点:

A = reshape (1:8, 2, 2, 2)  # 创建三维数组
A =

ans(:,:,1) =

   1   3
   2   4

ans(:,:,2) =

   5   7
   6   8

## 二维索引导致第三维合并到第二维中。
## 用于索引的等效数组 Atmp 现在是 2x4。
Atmp = reshape (A, 2, 4)
Atmp =

   1   3   5   7
   2   4   6   8


A(2,1)   # 重塑为 2x4 矩阵,第一列第二个条目:ans = 2
A(2,4)   # 重塑为 2x4 矩阵,第四列第二个条目:ans = 8
A(:,:)   # 重塑为 2x4 矩阵,选择所有行和列,ans = Atmp

请注意,这里优雅地使用了双冒号来代替调用 reshape 函数。

数组复制

线性索引的另一个高级用途是创建填充有单个值的数组。这可以通过对标量值使用全一索引来实现。结果是索引表达式的维度且每个元素都等于原始标量的对象。例如,以下语句

a = 13;
a(ones (1, 4))

生成一个四个元素都等于 13 的行向量。

类似地,通过用两个全一向量对标量进行索引,可以创建矩阵。以下语句

a = 13;
a(ones (1, 2), ones (1, 3))

创建一个 2x3 矩阵,所有元素都等于 13。这也可以写成

13(ones (2, 3))

使用索引比代码构造 scalar * ones (M, N, …) 更高效,因为它避免了不必要的乘法运算。此外,对于要复制的对象,可能没有定义乘法运算,而索引数组则始终是定义的。以下代码展示了如何从本身不是标量的基本单元创建 2x3 元胞数组。

{"Hello"}(ones (2, 3))

应当注意,ones (1, n)(一个全一行向量)产生一个范围对象(增量为零)。范围在内部存储为起始值、增量、结束值和值的总数;因此,当元素数量大于 4 时,它比全一的向量或矩阵的存储效率更高。特别是,当 ‘r’ 是行向量时,表达式

  r(ones (1, n), :)
  r(ones (n, 1), :)

将产生相同的结果,但第一个会明显更快,至少当 ‘r’ 和 ‘n’ 足够大时是如此。在第一种情况下,索引以压缩形式保存为范围,这允许 Octave 选择更高效的算法来处理该表达式。

对于不熟悉这些技术的用户,一般建议使用 repmat 函数将较小的数组复制到较大的数组中,该函数正是运用了这些技巧。

利用索引提高性能

索引的第二个用途是加速代码。索引是一种快速操作,明智地使用它可以减少对单个数组元素进行循环的需求,而循环是一种慢速操作。

考虑以下示例,它创建一个 10 元素的行向量 a,包含值 a(i) = sqrt (i)。

for i = 1:10
  a(i) = sqrt (i);
endfor

使用这样的循环来创建向量是非常低效的。在这种情况下,使用表达式会高效得多

a = sqrt (1:10);

这完全避免了循环。

在无法避免循环,或必须组合多个值以形成更大的矩阵的情况下,通常先设置矩阵的大小(预分配存储空间),然后使用索引命令插入元素会更快。例如,给定一个矩阵 a

[nr, nc] = size (a);
x = zeros (nr, n * nc);
for i = 1:n
  x(:,(i-1)*nc+1:i*nc) = a;
endfor

比以下代码快得多

x = a;
for i = 1:n-1
  x = [x, a];
endfor

因为 Octave 不必反复调整中间结果的大小。

有关更多性能改进建议,请参见 Vectorization and Faster Code Execution


版权所有 © 2024-2026 Octave中文网

ICP备案/许可证号:黑ICP备2024030411号-4