2.3.2 整数数组作为下标

下面看看下标元组中的元素由切片和整数数组构成的情况。假设整数数组有Nt个,而切片有Ns个。Nt +Ns为数组的维数D。

首先,这Nt个整数数组必须满足广播条件,假设它们进行广播之后的维数为M,形状为(d0,d1,…,dM-1)。

如果Ns为0,即没有切片元素时,则下标所得到的结果数组result的形状和整数数组广播之后的形状相同。它的每个元素值按照下面的公式获得:

其中,ind0到indnt -1为进行广播之后的整数数组。让我们看一个例子,从而加深对此公式的理解:

若只需要沿着指定轴通过整数数组获取元素,可以使用numpy.take()函数,其运算速度比整数数组的下标运算略快,并且支持下标越界处理。

    i0 = np.array([[1, 2, 1], [0, 1, 0]])
    i1 = np.array([[[0]], [[1]]])
    i2 = np.array([[[2, 3, 2]]])
    b = a[i0, i1, i2]
    b
    array([[[22, 43, 22],
            [ 2, 23,  2]],
    
           [[27, 48, 27],
            [ 7, 28,  7]]])

首先,i0、i1、i2三个整数数组的shape属性分别为(2,3)、(2,1,1)、(1,1,3)。根据广播规则,先在长度不足3的shape属性前面补1,使得它们的维数相同,广播之后的shape属性为各个轴的最大值:

    (1, 2, 3)
    (2, 1, 1)
    (1, 1, 3)
    ---------
     2  2  3

即三个整数数组广播之后的shape属性为(2,2,3),这也就是下标运算所得到的结果数组的维数:

    b.shape
    (2, 2, 3)

我们可以使用broadcast_arrays()查看广播之后的数组:

对于b中的任意一个元素b[i,j,k],它是数组a中经过ind0、ind1和ind2进行下标转换之后的值:

    i, j, k = 0, 1, 2
    print b[i, j, k], a[ind0[i, j, k], ind1[i, j, k], ind2[i, j, k]]
    
    i, j, k = 1, 1, 1
    print b[i, j, k], a[ind0[i, j, k], ind1[i, j, k], ind2[i, j, k]]
    2 2
    28 28

下面考虑Ns不为0的情况。当存在切片下标时,情况就变得更加复杂了。可以细分为两种情况:下标元组中的整数数组之间没有切片,即整数数组只有一个或连续的多个整数数组。这时结果数组的shape属性为:将原始数组的shape属性中整数数组所占据的部分替换为它们广播之后的shape属性。例如假设原始数组a的shape属性为(3,4,5),i0和i1广播之后的形状为(2,2,3),则a[1:3,i0,i1]的形状为(2,2,2,3):

    c = a[1:3, i0, i1]
    c.shape
    (2, 2, 2, 3)

其中,c的shape属性中的第一个2是切片“1:3”的长度,后面的(2,2,3)则是i0和i1广播之后的数组的形状:

    ind0, ind1 = np.broadcast_arrays(i0, i1)
    ind0.shape
    (2, 2, 3)
    i, j, k = 1, 1, 2
    print c[:, i, j, k]
    print a[1:3, ind0[i, j, k], ind1[i, j, k]]  # 和c[:,i,j,k]的值相同
    [21 41]
    [21 41]

当下标元组中的整数数组不连续时,结果数组的shape属性为整数数组广播之后的形状后面添加上切片元素所对应的形状。例如a[i0,:,i1]的shape属性为(2,2,3,4),其中(2,2,3)是i0和i1广播之后的形状,而4是数组a的第1轴的长度:

    d = a[i0, :, i1]
    d.shape
    (2, 2, 3, 4)
    i, j, k = 1, 1, 2
       d[i,j,k,:]     a[ind0[i,j,k],:,ind1[i,j,k]]
    ----------------  ----------------------------
    [ 1,  6, 11, 16]  [ 1,  6, 11, 16]