为了避免混乱,我们先把前面定义的那 4 个一维数组还原成初始值:

    还记得吗?Julia 中的一维数组都是列向量。而且,我们可以使用英文分号纵向地拼接数组,并使用空格横向地拼接数组。所以,我们也可以把纵向的拼接叫做在第一个维度上的拼接,并把横向的拼接叫做在第二个维度上的拼接。下面的例子看起来会更加的形象:

    1. julia> [a1; a2]
    2. 6-element Array{Int64,1}:
    3. 1
    4. 3
    5. 5
    6. 2
    7. 4
    8. 6
    9. julia> [a1 a2]
    10. 3×2 Array{Int64,2}:
    11. 1 2
    12. 3 4
    13. 5 6
    14. julia>

    这两种拼接实际上都可以通过调用cat函数来实现。我们需要做的就是,把要拼接的多个数组都传给这个函数,同时确定其关键字参数dims的值。这个关键字参数代表的就是,将要在哪一个维度上进行拼接。而且,它还是一个必选的参数。我们下面就用这个函数来实现前面的那两个拼接操作:

    1. julia> cat(a1, a2, dims=1)
    2. 6-element Array{Int64,1}:
    3. 1
    4. 3
    5. 5
    6. 2
    7. 4
    8. 6
    9. julia> cat(a1, a2, dims=2)
    10. 3×2 Array{Int64,2}:
    11. 1 2
    12. 3 4
    13. 5 6
    14. julia>

    这很容易不是吗?不过,如果只是这样的话,我想这个cat函数就没有太大的存在意义了。实际上,我们还可以为它的dims参数赋予更大的正整数。例如:

    1. julia> cat(a1, a2, dims=3)
    2. 3×1×2 Array{Int64,3}:
    3. [:, :, 1] =
    4. 3
    5. 5
    6. [:, :, 2] =
    7. 2
    8. 4
    9. 6
    10. julia> cat(a1, a2, dims=4)
    11. 3×1×1×2 Array{Int64,4}:
    12. [:, :, 1, 1] =
    13. 1
    14. 3
    15. 5
    16. [:, :, 1, 2] =
    17. 2
    18. 6
    19. julia>

    如上所示。当dims=3时,cat函数会把a1a2分别作为两个二维数组的唯一组成部分,然后再用这两个二维数组合成一个三维数组。从而,这个三维数组的尺寸就是3×1×2。更具体地说,之所以它在第一个维度上的长度是3,是因为a1a2的长度都是3。它在第二个维度上的长度是1,是因为a1a2都是一维的数组,并且它们分别是那两个二维数组中唯一的低维数组。至于第三个维度上的长度为2的根本原因是,我们用来做拼接的数组有两个。

    dims=4时,cat函数依然会把a1a2分别作为两个二维数组的唯一组成部分。并且,它还会把这两个二维数组分别作为两个三维数组的唯一组成部分。最后,它会再用这两个三维数组合成一个四维数组。所以,这个四维数组的尺寸才会是3×1×1×2。至于更多的细节,我想你应该已经可以参照前面的描述自行解释了。

    我们再来一起拼接一个更加复杂的数组。首先,我们要合成两个二维数组,如下:

    这两个二维数组的尺寸都是3×2,并且它们的元素值也都很有特点。现在,我们要把它们拼接成一个四维数组:

    1. julia> cat(a13, a24, dims=4)
    2. 3×2×1×2 Array{Int64,4}:
    3. [:, :, 1, 1] =
    4. 1 7
    5. 3 9
    6. 5 11
    7. [:, :, 1, 2] =
    8. 2 8
    9. 4 10
    10. 6 12
    11. julia>

    这个四维数组的尺寸是‌3×2×1×2。与前面那两个3×2的数组比对一下,你是不是已经看出一些规律了呢?没错,它们在前两个维度上的尺寸完全相同。并且,最后一个维度上的长度完全取决于我们用来做拼接的数组的个数。至于第三个维度上的1,是因为我们拿来做拼接的是二维数组,它们都没有第三个维度。

    1. julia> vcat(a1, a2)
    2. 1
    3. 3
    4. 5
    5. 2
    6. 4
    7. 6
    8. julia>

    又比如,hcat函数仅用于横向的拼接,它的用法与vcat函数一样:

    1. julia> hcat(a1, a2)
    2. 3×2 Array{Int64,2}:
    3. 1 2
    4. 3 4
    5. 5 6
    6. julia>

    另外,我们还要详细地说一下这个函数。它可以同时进行纵向和横向的拼接。在使用的时候,我们先要确定拼接后的数组的尺寸,然后再传入用于拼接的值。先看一个最简单的例子:

    如果我们把拼接后数组的尺寸确定为一个正整数,那么就相当于在确定新数组的列数。至于新数组会有多少行,那就要看用于拼接的值有多少个了。正如上例所示,我们传给hvcat函数的第一个参数值3代表着新数组会有 3 列。如此一来,这个函数就会依次地把a1中的各个元素值分别分配给新数组中的某一列。

    注意,我们在这里传给hvcat函数的第二个参数值是a1...,而不是a1。这是为什么呢?

    还记得吗?符号...的作用是,把紧挨在它左边的那个值中的所有元素值都平铺开来,让它们都成为独立的参数值。所以,上述调用就相当于hvcat(3, 1, 3, 5)。如果我们把...从中去掉,那么 Julia 就会立即报错:

    1. julia> hvcat(3, a1)
    2. ERROR: ArgumentError: number of arrays 1 is not a multiple of the requested number of block columns 3
    3. # 省略了一些回显的内容。
    4. julia>

    这条错误信息的大意是:既然确定了新数组会有 3 列,那么后面提供的参数值的个数就应该是 3 的倍数。否则,这个函数就无法均匀地把参数值分配到各个列上。由于我们在后面只提供了一个参数值a1,所以就引发了这个错误。

    在第一个参数值是3的情况下,如果后续参数值的数量正好是 3,那么hvcat函数就会生成一个1×3的二维数组。如果这个数量是 6,那么它就会生成一个2×3的二维数组。以此类推。

    原则上,除了第一个参数,我们可以把任意的值作为参数值传给hvcat函数。但如果这些参数值都是一维数组,那么该函数就会识别出它们,并依次地把它们(包含的所有元素值)分别分配给新数组中的某一列。例如:

    1. julia> hvcat(3, a1, a2, a3)
    2. 3×3 Array{Int64,2}:
    3. 1 2 7
    4. 3 4 9
    5. 5 6 11
    6. julia>

    此时,新数组的行数就取决于后续参数值的长度。注意,这些后续的参数值的长度必须是相同的。

    1. julia> hvcat((3), a1, a2, a3)
    2. 3×3 Array{Int64,2}:
    3. 1 2 7
    4. 3 4 9
    5. 5 6 11
    6. julia>

    这次我们传给hvcat函数的第一个参数值是一个元组,并且其中只包含了一个元素值3。这个调用表达式的求值结果与前一个例子的结果完全相同。

    你可能已经猜到,这个元组里的3同样代表了新数组的列数。不过,它还有另外一个含义,即:新数组进行第一轮分配时所需的后续参数值的数量。如果还有第二轮分配的话,那么就可以是下面这样:

    请注意,在此例中,上面的调用表达式中的第一个参数值是(3,3),而下面的第一个参数值是3。它们起到的作用是一样的。

    可以看到,在这两个调用表达式中,hvcat函数都会先依次地把第 1、2、3 个一维数组中的所有元素值分别分配给新数组的第 1、2、3 列。这对应于求值结果中的前三行。然后,它会再把第 4、5、6 个一维数组中的所有元素值分别分配给新数组的那三列。这对应于求值结果中的后三行。此时,新数组的行数就等于这些一维数组的长度的 2 倍。显然,这些一维数组的长度也必须是相同的。

    现在我们知道了,参数值(3,3)中的第二个3的含义是:新数组进行第二轮分配时所需的后续参数值的数量。实际上,作为传递给hvcat函数的第一个参数值,元组中的每一个正整数都必须是相同的。如果出现了像(3,2)(3,3,2)这样的参数值,那么hvcat函数就会立即报错。

    那为什么还要向hvcat函数传入元组呢?我们直接传入一个正整数不就好了吗?

    与只传入一个正整数相比,传入一个元组有以下两点不同:

    1. 传入元组可以确切地告诉hvcat函数需要为新数组分配几轮元素值。而如果只传入一个正整数,我们可能还需要计算一下才能得知真正的分配轮数。因为后续参数值的数量可以是这个正整数的任意倍数。比如,假设后续的参数值是a5...,而我们并不知道a5里有多少个元素值,所以就无法预料到分配的轮数。

    因此,这里就存在一个选择的问题。我们需要根据实际情况选择“灵活”或者“严谨”。当你在编写一个供他人使用的程序的时候,这种选择尤为重要。不过,这种选择在很多时候并不意味着非01

    言归正传。虽然hvcat函数在二维数组的拼接方面很强大,但是它与vcathcat一样,都无法拼接出维数更多的数组。为了满足这样的需求,我们只能使用cat函数。当然,若我们要拼接很复杂的数组,则可以把这些函数组合起来使用。我更加推荐这种使用方式。因为这样做可以使程序的可读性更好,也更不容易出错,另外在程序的性能方面往往也不会有什么损失。