單維和多維陣列

Julia 與大多數技術運算語言一樣,提供了一流的陣列實作。大多數技術運算語言都非常重視陣列實作,而犧牲了其他容器。Julia 沒有特別處理陣列。陣列函式庫幾乎完全以 Julia 本身實作,其效能來自編譯器,就像用 Julia 編寫的任何其他程式碼一樣。因此,也可以透過繼承 AbstractArray 來定義自訂陣列類型。有關實作自訂陣列類型的更多詳細資訊,請參閱 AbstractArray 介面的手冊章節

陣列是儲存在多維度格線中的物件集合。允許零維度陣列,請參閱 此常見問題解答條目。在最一般的情況下,陣列可能包含 Any 類型的物件。對於大多數運算目的,陣列應包含更特定類型的物件,例如 Float64Int32

一般來說,與許多其他技術運算語言不同,Julia 沒有預期程式會以向量化方式撰寫以獲得效能。Julia 的編譯器使用類型推論,並為純量陣列索引產生最佳化程式碼,讓程式可以以方便且易讀的方式撰寫,同時不犧牲效能,有時還能使用較少的記憶體。

在 Julia 中,傳遞給函式的所有引數都是 透過共用傳遞(即透過指標)。有些技術運算語言會透過值傳遞陣列,雖然這可以防止呼叫方意外修改呼叫者中的值,但這會讓避免不必要的陣列複製變得困難。根據慣例,函式名稱結尾為 ! 表示它會改變或摧毀一個或多個引數的值(例如,比較 sortsort!)。呼叫方必須建立明確的副本,以確保它們不會修改它們不想變更的輸入。許多非變異函式會透過在輸入的明確副本上呼叫名稱相同的函式(在結尾加上 !)並傳回該副本來實作。

基本函式

函式說明
eltype(A)A 中所包含元素的類型
length(A)A 中的元素數量
ndims(A)A 的維度數量
size(A)包含 A 維度的元組
size(A,n)A 沿維度 n 的大小
axes(A)包含 A 的有效索引的元組
axes(A,n)表達沿維度 n 的有效索引的範圍
eachindex(A)用於拜訪 A 中每個位置的高效率迭代器
stride(A,k)沿維度 k 的步幅(相鄰元素之間的線性索引距離)
strides(A)每個維度中步幅的元組

建立和初始化

提供了許多用於建立和初始化陣列的函式。在下列此類函式的清單中,帶有 dims... 引數的呼叫可以採用單一維度大小元組,或是一系列作為可變數量引數傳遞的維度大小。這些函式中的大多數也接受第一個輸入 T,這是陣列的元素類型。如果省略類型 T,它將預設為 Float64

函式說明
Array{T}(undef, dims...)未初始化的稠密 Array
zeros(T, dims...)全為零的 Array
ones(T, dims...)全為一的 Array
trues(dims...)所有值都為 trueBitArray
falses(dims...)所有值都為 falseBitArray
reshape(A, dims...)包含與 A 相同資料的陣列,但維度不同
copy(A)複製 A
deepcopy(A)複製 A,遞迴複製其元素
similar(A, T, dims...)A 相同類型的未初始化陣列(稠密、稀疏等),但具有指定的元素類型和維度。如果省略,第二個和第三個參數都是可選的,預設為 A 的元素類型和維度。
reinterpret(T, A)A 具有相同二進位資料的陣列,但元素類型為 T
rand(T, dims...)具有隨機、iid [1] 和均勻分佈值的 Array。對於浮點類型 T,值位於半開區間 $[0, 1)$ 中。
randn(T, dims...)具有隨機、iid 和標準常態分佈值的 Array
Matrix{T}(I, m, n)mn 的單位矩陣。需要 using LinearAlgebra 才能使用 I
range(start, stop, n)startstopn 個線性間隔元素的範圍
fill!(A, x)使用值 x 填滿陣列 A
fill(x, dims...)使用值 x 填滿的 Array。特別是,fill(x) 建立一個包含 x 的零維度 Array

要查看我們傳遞維度到這些函式的各種方式,請考慮以下範例

julia> zeros(Int8, 2, 3)
2×3 Matrix{Int8}:
 0  0  0
 0  0  0

julia> zeros(Int8, (2, 3))
2×3 Matrix{Int8}:
 0  0  0
 0  0  0

julia> zeros((2, 3))
2×3 Matrix{Float64}:
 0.0  0.0  0.0
 0.0  0.0  0.0

在此,(2, 3)Tuple,而第一個引數(元素類型)是選用的,預設為 Float64

陣列文字

陣列也可以使用方括號直接建構;語法 [A, B, C, ...] 會建立一個一維陣列(即向量),包含以逗號分隔的引數作為其元素。結果陣列的元素類型(eltype)會自動根據大括號內引數的類型決定。如果所有引數都是同類型,則那就是它的 eltype。如果它們都有共同的 提升類型,則它們會使用 convert 轉換成該類型,且該類型會是陣列的 eltype。否則,會建構一個可以容納任何東西的異質陣列(Vector{Any});這包括沒有提供引數的文字 []陣列文字可以使用語法 T[A, B, C, ...] 指定類型,其中 T 是類型。

julia> [1, 2, 3] # An array of `Int`s
3-element Vector{Int64}:
 1
 2
 3

julia> promote(1, 2.3, 4//5) # This combination of Int, Float64 and Rational promotes to Float64
(1.0, 2.3, 0.8)

julia> [1, 2.3, 4//5] # Thus that's the element type of this Array
3-element Vector{Float64}:
 1.0
 2.3
 0.8

julia> Float32[1, 2.3, 4//5] # Specify element type manually
3-element Vector{Float32}:
 1.0
 2.3
 0.8

julia> []
Any[]

串接

如果方括號內的引數是用單分號 (;) 或換行符號分隔,而不是逗號,則它們的內容會垂直串接在一起,而不是將引數本身用作元素。

julia> [1:2, 4:5] # Has a comma, so no concatenation occurs. The ranges are themselves the elements
2-element Vector{UnitRange{Int64}}:
 1:2
 4:5

julia> [1:2; 4:5]
4-element Vector{Int64}:
 1
 2
 4
 5

julia> [1:2
        4:5
        6]
5-element Vector{Int64}:
 1
 2
 4
 5
 6

類似地,如果引數是用 tab 鍵、空格或雙分號分隔,則它們的內容會水平串接在一起。

julia> [1:2  4:5  7:8]
2×3 Matrix{Int64}:
 1  4  7
 2  5  8

julia> [[1,2]  [4,5]  [7,8]]
2×3 Matrix{Int64}:
 1  4  7
 2  5  8

julia> [1 2 3] # Numbers can also be horizontally concatenated
1×3 Matrix{Int64}:
 1  2  3

julia> [1;; 2;; 3;; 4]
1×4 Matrix{Int64}:
 1  2  3  4

單個分號(或換行符)和空格(或製表符)可以組合,同時在水平和垂直方向上串接。

julia> [1 2
        3 4]
2×2 Matrix{Int64}:
 1  2
 3  4

julia> [zeros(Int, 2, 2) [1; 2]
        [3 4]            5]
3×3 Matrix{Int64}:
 0  0  1
 0  0  2
 3  4  5

julia> [[1 1]; 2 3; [4 4]]
3×2 Matrix{Int64}:
 1  1
 2  3
 4  4

空格(和製表符)的優先權高於分號,先執行任何水平串接,然後串接結果。另一方面,使用雙分號進行水平串接,會在水平串接結果之前執行任何垂直串接。

julia> [zeros(Int, 2, 2) ; [3 4] ;; [1; 2] ; 5]
3×3 Matrix{Int64}:
 0  0  1
 0  0  2
 3  4  5

julia> [1:2; 4;; 1; 3:4]
3×2 Matrix{Int64}:
 1  1
 2  3
 4  4

正如 ;;; 在第一和第二維度中串接,使用更多分號會延伸相同的通用方案。分隔符中的分號數量指定特定維度,因此 ;;; 在第三維度中串接,;;;; 在第四維度中串接,依此類推。較少的分號優先,因此通常先串接較低維度。

julia> [1; 2;; 3; 4;; 5; 6;;;
        7; 8;; 9; 10;; 11; 12]
2×3×2 Array{Int64, 3}:
[:, :, 1] =
 1  3  5
 2  4  6

[:, :, 2] =
 7   9  11
 8  10  12

與之前一樣,用於水平串接的空格(和製表符)的優先權高於任何數量的分號。因此,也可以通過先指定其列來編寫更高維度的陣列,其元素以類似於其佈局的方式按文本排列

julia> [1 3 5
        2 4 6;;;
        7 9 11
        8 10 12]
2×3×2 Array{Int64, 3}:
[:, :, 1] =
 1  3  5
 2  4  6

[:, :, 2] =
 7   9  11
 8  10  12

julia> [1 2;;; 3 4;;;; 5 6;;; 7 8]
1×2×2×2 Array{Int64, 4}:
[:, :, 1, 1] =
 1  2

[:, :, 2, 1] =
 3  4

[:, :, 1, 2] =
 5  6

[:, :, 2, 2] =
 7  8

julia> [[1 2;;; 3 4];;;; [5 6];;; [7 8]]
1×2×2×2 Array{Int64, 4}:
[:, :, 1, 1] =
 1  2

[:, :, 2, 1] =
 3  4

[:, :, 1, 2] =
 5  6

[:, :, 2, 2] =
 7  8

儘管它們都表示在第二維度中串接,但空格(或製表符)和 ;; 不能出現在同一個陣列表達式中,除非雙分號僅用作「換行」字元。這允許單個水平串接跨越多行(而不會將換行符解釋為垂直串接)。

julia> [1 2 ;;
       3 4]
1×4 Matrix{Int64}:
 1  2  3  4

終止分號也可用于添加尾隨長度 1 維度。

julia> [1;;]
1×1 Matrix{Int64}:
 1

julia> [2; 3;;;]
2×1×1 Array{Int64, 3}:
[:, :, 1] =
 2
 3

更一般地說,串接可通過 cat 函數完成。這些語法是函數調用的速記,這些函數本身是便利函數

語法函式說明
cat沿維度 k 串接輸入陣列
[A; B; C; ...]vcatcat(A...; dims=1) 的速記
[A B C ...]hcatcat(A...; dims=2) 的速記
[A B; C D; ...]hvcat同時垂直和水平串聯
[A; C;; B; D;;; ...]hvncat同時 n 維串聯,其中分號的數量表示要串聯的維度

型別陣列文字

可以使用語法 T[A, B, C, ...] 建立具有特定元素型別的陣列。這將建立一個元素型別為 T 的 1 維陣列,初始化為包含元素 ABC 等。例如,Any[x, y, z] 建立一個異質陣列,其中可以包含任何值。

串聯語法也可以用型別為前綴,以指定結果的元素型別。

julia> [[1 2] [3 4]]
1×4 Matrix{Int64}:
 1  2  3  4

julia> Int8[[1 2] [3 4]]
1×4 Matrix{Int8}:
 1  2  3  4

理解

理解提供建立陣列的一般且強大的方法。理解語法類似於數學中的集合建構符號

A = [ F(x, y, ...) for x=rx, y=ry, ... ]

此形式的意義是 F(x,y,...) 使用變數 xy 等進行評估,這些變數會採用其給定值清單中的每個值。值可以指定為任何可迭代物件,但通常會是像 1:n2:(n-1) 的範圍,或像 [1.2, 3.4, 5.7] 的明確值陣列。結果是一個 N 維密集陣列,其維度是變數範圍 rxry 等的維度串聯,且每個 F(x,y,...) 評估都會傳回一個純量。

以下範例計算當前元素及其沿 1 維網格的左右鄰居的加權平均值。

julia> x = rand(8)
8-element Array{Float64,1}:
 0.843025
 0.869052
 0.365105
 0.699456
 0.977653
 0.994953
 0.41084
 0.809411

julia> [ 0.25*x[i-1] + 0.5*x[i] + 0.25*x[i+1] for i=2:length(x)-1 ]
6-element Array{Float64,1}:
 0.736559
 0.57468
 0.685417
 0.912429
 0.8446
 0.656511

結果陣列類型取決於計算元素的類型,就像陣列文字一樣。為了明確控制類型,可以在理解中加上一個類型。例如,我們可以透過撰寫來要求以單精度表示結果

Float32[ 0.25*x[i-1] + 0.5*x[i] + 0.25*x[i+1] for i=2:length(x)-1 ]

產生器表達式

理解也可以在沒有包圍方括號的情況下撰寫,產生稱為產生器的物件。這個物件可以反覆運算以依需求產生值,而不是配置陣列並事先儲存(請參閱反覆運算)。例如,下列表達式在不配置記憶體的情況下加總一個數列

julia> sum(1/n^2 for n=1:1000)
1.6439345666815615

在引數清單中撰寫具有多個維度的產生器表達式時,需要使用括號將產生器與後續引數分開

julia> map(tuple, 1/(i+j) for i=1:2, j=1:2, [1:4;])
ERROR: syntax: invalid iteration specification

for之後的所有逗號分隔表達式都解釋為範圍。加入括號讓我們可以將第三個引數加入map

julia> map(tuple, (1/(i+j) for i=1:2, j=1:2), [1 3; 2 4])
2×2 Matrix{Tuple{Float64, Int64}}:
 (0.5, 1)       (0.333333, 3)
 (0.333333, 2)  (0.25, 4)

產生器透過內部函數實作。就像在語言中其他地方使用的內部函數一樣,包圍範圍的變數可以在內部函數中「擷取」。例如,sum(p[i] - q[i] for i=1:n)從包圍範圍擷取三個變數pqn。擷取的變數可能會造成效能挑戰;請參閱效能提示

產生器和理解中的範圍可以透過撰寫多個for關鍵字來取決於先前的範圍

julia> [(i, j) for i=1:3 for j=1:i]
6-element Vector{Tuple{Int64, Int64}}:
 (1, 1)
 (2, 1)
 (2, 2)
 (3, 1)
 (3, 2)
 (3, 3)

在這種情況下,結果永遠是 1-d。

可以透過使用if關鍵字來篩選產生的值

julia> [(i, j) for i=1:3 for j=1:i if i+j == 4]
2-element Vector{Tuple{Int64, Int64}}:
 (2, 2)
 (3, 1)

索引

索引到 n 維度陣列A的一般語法為

X = A[I_1, I_2, ..., I_n]

其中每個 I_k 可能為一個標量整數、一個整數陣列或任何其他 受支援的索引。這包括 Colon (:) 以選取整個維度中的所有索引,a:ca:b:c 形式的範圍以選取連續或跨步的子區段,以及布林陣列以選取其 true 索引處的元素。

如果所有索引都是標量,則結果 X 是陣列 A 中的單一元素。否則,X 是陣列,其維度數目與所有索引的維度數目總和相同。

例如,如果所有索引 I_k 都是向量,則 X 的形狀將會是 (length(I_1), length(I_2), ..., length(I_n)),其中 X 的位置 i_1, i_2, ..., i_n 包含值 A[I_1[i_1], I_2[i_2], ..., I_n[i_n]]

範例

julia> A = reshape(collect(1:16), (2, 2, 2, 2))
2×2×2×2 Array{Int64, 4}:
[:, :, 1, 1] =
 1  3
 2  4

[:, :, 2, 1] =
 5  7
 6  8

[:, :, 1, 2] =
  9  11
 10  12

[:, :, 2, 2] =
 13  15
 14  16

julia> A[1, 2, 1, 1] # all scalar indices
3

julia> A[[1, 2], [1], [1, 2], [1]] # all vector indices
2×1×2×1 Array{Int64, 4}:
[:, :, 1, 1] =
 1
 2

[:, :, 2, 1] =
 5
 6

julia> A[[1, 2], [1], [1, 2], 1] # a mix of index types
2×1×2 Array{Int64, 3}:
[:, :, 1] =
 1
 2

[:, :, 2] =
 5
 6

請注意,在最後兩個案例中,結果陣列的大小不同。

如果 I_1 變更為一個二維矩陣,則 X 會變成一個形狀為 (size(I_1, 1), size(I_1, 2), length(I_2), ..., length(I_n))n+1 維陣列。矩陣會新增一個維度。

範例

julia> A = reshape(collect(1:16), (2, 2, 2, 2));

julia> A[[1 2; 1 2]]
2×2 Matrix{Int64}:
 1  2
 1  2

julia> A[[1 2; 1 2], 1, 2, 1]
2×2 Matrix{Int64}:
 5  6
 5  6

位置 i_1, i_2, i_3, ..., i_{n+1} 包含值 A[I_1[i_1, i_2], I_2[i_3], ..., I_n[i_{n+1}]]。所有以標量索引的維度都會被捨棄。例如,如果 J 是索引陣列,則 A[2, J, 3] 的結果是一個大小為 size(J) 的陣列。其第 j 個元素會由 A[2, J[j], 3] 填入。

作為此語法的特殊部分,end 關鍵字可用於表示索引括號內每個維度的最後索引,由所索引的最內層陣列的大小決定。沒有 end 關鍵字的索引語法等同於呼叫 getindex

X = getindex(A, I_1, I_2, ..., I_n)

範例

julia> x = reshape(1:16, 4, 4)
4×4 reshape(::UnitRange{Int64}, 4, 4) with eltype Int64:
 1  5   9  13
 2  6  10  14
 3  7  11  15
 4  8  12  16

julia> x[2:3, 2:end-1]
2×2 Matrix{Int64}:
 6  10
 7  11

julia> x[1, [2 3; 4 1]]
2×2 Matrix{Int64}:
  5  9
 13  1

索引賦值

在 n 維陣列 A 中賦予值的通用語法為

A[I_1, I_2, ..., I_n] = X

其中每個 I_k 可能為一個標量整數、一個整數陣列或任何其他 受支援的索引。這包括 Colon (:) 以選取整個維度中的所有索引,a:ca:b:c 形式的範圍以選取連續或跨步的子區段,以及布林陣列以選取其 true 索引處的元素。

如果所有索引 I_k 都是整數,則 A 中位置 I_1, I_2, ..., I_n 的值會被 X 的值覆寫,必要時會轉換為 Aeltype

如果任何索引 I_k 本身是一個陣列,則右手邊的 X 也必須是一個陣列,其形狀與索引 A[I_1, I_2, ..., I_n] 的結果相同,或是一個元素數量相同的向量。A 中位置 I_1[i_1], I_2[i_2], ..., I_n[i_n] 的值會被 X[I_1, I_2, ..., I_n] 的值覆寫,必要時會轉換。元素級別的賦值運算子 .= 可用於在所選位置上 廣播 X

A[I_1, I_2, ..., I_n] .= X

就像在 索引 中,end 關鍵字可用於表示索引括號內每個維度的最後索引,由所賦值陣列的大小決定。沒有 end 關鍵字的索引賦值語法等同於呼叫 setindex!

setindex!(A, X, I_1, I_2, ..., I_n)

範例

julia> x = collect(reshape(1:9, 3, 3))
3×3 Matrix{Int64}:
 1  4  7
 2  5  8
 3  6  9

julia> x[3, 3] = -9;

julia> x[1:2, 1:2] = [-1 -4; -2 -5];

julia> x
3×3 Matrix{Int64}:
 -1  -4   7
 -2  -5   8
  3   6  -9

支援的索引類型

在表達式 A[I_1, I_2, ..., I_n] 中,每個 I_k 都可以是標量索引、標量索引陣列,或表示標量索引陣列且可由 to_indices 轉換為標量索引陣列的物件

  1. 一個純量索引。預設包含
    • 非布林整數
    • CartesianIndex{N},行為類似於跨越多個維度的整數 N 元組(詳情請見下方)
  2. 純量索引陣列。包含
    • 整數向量和多維陣列
    • 空陣列,例如 [],不選取任何元素,例如 A[[]](不要與 A[] 混淆)
    • 範圍,例如 a:ca:b:c,從 a 選取到 c(包含)的連續或跨步子區段
    • 任何自訂純量索引陣列,為 AbstractArray 的子類型
    • CartesianIndex{N} 陣列(詳情請見下方)
  3. 表示純量索引陣列的物件,且可透過 to_indices 轉換為純量索引陣列。預設包含
    • Colon() (:),表示整個維度或整個陣列中的所有索引
    • 布林陣列,選取其 true 索引的元素(詳情請見下方)

一些範例

julia> A = reshape(collect(1:2:18), (3, 3))
3×3 Matrix{Int64}:
 1   7  13
 3   9  15
 5  11  17

julia> A[4]
7

julia> A[[2, 5, 8]]
3-element Vector{Int64}:
  3
  9
 15

julia> A[[1 4; 3 8]]
2×2 Matrix{Int64}:
 1   7
 5  15

julia> A[[]]
Int64[]

julia> A[1:2:5]
3-element Vector{Int64}:
 1
 5
 9

julia> A[2, :]
3-element Vector{Int64}:
  3
  9
 15

julia> A[:, 3]
3-element Vector{Int64}:
 13
 15
 17

julia> A[:, 3:3]
3×1 Matrix{Int64}:
 13
 15
 17

笛卡兒索引

特殊的 CartesianIndex{N} 物件表示一個純量索引,行為類似於跨越多個維度的整數 N 元組。例如

julia> A = reshape(1:32, 4, 4, 2);

julia> A[3, 2, 1]
7

julia> A[CartesianIndex(3, 2, 1)] == A[3, 2, 1] == 7
true

單獨來看,這似乎相對微不足道;CartesianIndex 僅將多個整數收集到一個物件中,代表單一的多維度索引。然而,當與產生 CartesianIndexes 的其他索引形式和反覆運算子結合使用時,這可以產生非常優雅且高效的程式碼。請參閱下方的 反覆運算,並查看 這篇關於多維度演算法和反覆運算的部落格文章,以取得更進階的範例。

CartesianIndex{N} 的陣列也受支援。它們代表一個純量索引的集合,每個索引跨越 N 個維度,啟用有時稱為逐點索引的索引形式。例如,它能從上方的 A 存取第一個「頁面」中的對角線元素

julia> page = A[:, :, 1]
4×4 Matrix{Int64}:
 1  5   9  13
 2  6  10  14
 3  7  11  15
 4  8  12  16

julia> page[[CartesianIndex(1, 1),
             CartesianIndex(2, 2),
             CartesianIndex(3, 3),
             CartesianIndex(4, 4)]]
4-element Vector{Int64}:
  1
  6
 11
 16

這可以用 點廣播 和將其與一般整數索引結合(而不是將第一個 pageA 中作為一個獨立步驟萃取出來)來更簡單地表達。它甚至可以與 : 結合,同時從兩個頁面中萃取兩個對角線

julia> A[CartesianIndex.(axes(A, 1), axes(A, 2)), 1]
4-element Vector{Int64}:
  1
  6
 11
 16

julia> A[CartesianIndex.(axes(A, 1), axes(A, 2)), :]
4×2 Matrix{Int64}:
  1  17
  6  22
 11  27
 16  32
警告

CartesianIndexCartesianIndex 陣列與 end 關鍵字不相容,後者用於表示維度的最後一個索引。不要在可能包含 CartesianIndex 或其陣列的索引表達式中使用 end

邏輯索引

通常稱為邏輯索引或使用邏輯遮罩索引,透過布林陣列索引會選取值為 true 的索引中的元素。透過布林向量 B 索引實際上與透過 findall(B) 傳回的整數向量索引相同。類似地,透過 N 維度布林陣列索引實際上與透過值為 trueCartesianIndex{N} 向量索引相同。邏輯索引必須是其索引維度長度相同的向量,或必須是提供的唯一索引,且與其索引陣列的大小和維度相符。通常直接使用布林陣列作為索引會比先呼叫 findall 更有效率。

julia> x = reshape(1:16, 4, 4)
4×4 reshape(::UnitRange{Int64}, 4, 4) with eltype Int64:
 1  5   9  13
 2  6  10  14
 3  7  11  15
 4  8  12  16

julia> x[[false, true, true, false], :]
2×4 Matrix{Int64}:
 2  6  10  14
 3  7  11  15

julia> mask = map(ispow2, x)
4×4 Matrix{Bool}:
 1  0  0  0
 1  0  0  0
 0  0  0  0
 1  1  0  1

julia> x[mask]
5-element Vector{Int64}:
  1
  2
  4
  8
 16

索引數目

笛卡兒索引

索引 N 維度陣列的通常方式是使用剛好 N 個索引;每個索引會選取其特定維度中的位置。例如,在三維度陣列 A = rand(4, 3, 2) 中,A[2, 3, 1] 會選取陣列第一「頁面」中第三欄第二列的數字。這通常稱為笛卡兒索引

線性索引

當精確提供一個索引 i 時,該索引不再表示陣列特定維度中的位置。相反,它使用線性跨越整個陣列的列優先迭代順序來選擇第 i 個元素。這稱為線性索引。它本質上將陣列視為已重新塑造成具有一維向量的 vec

julia> A = [2 6; 4 7; 3 1]
3×2 Matrix{Int64}:
 2  6
 4  7
 3  1

julia> A[5]
7

julia> vec(A)[5]
7

陣列 A 中的線性索引可以使用 CartesianIndices(A)[i] 轉換為笛卡兒索引以進行笛卡兒索引(請參閱 CartesianIndices),並且可以將一組 N 笛卡兒索引使用 LinearIndices(A)[i_1, i_2, ..., i_N] 轉換為線性索引(請參閱 LinearIndices)。

julia> CartesianIndices(A)[5]
CartesianIndex(2, 2)

julia> LinearIndices(A)[2, 2]
5

重要的是要注意,這些轉換的效能有很大的不對稱性。將線性索引轉換為一組笛卡兒索引需要進行除法和取餘數,而反向進行則只需要乘法和加法。在現代處理器中,整數除法可能比乘法慢 10-50 倍。雖然某些陣列(例如 Array 本身)是使用線性記憶體區塊實作,並在其實作中直接使用線性索引,但其他陣列(例如 Diagonal)需要完整的笛卡兒索引集來進行查詢(請參閱 IndexStyle 以內省哪個是哪個)。

警告

在反覆處理陣列的所有索引時,最好反覆處理 eachindex(A),而不是 1:length(A)。這不僅在 AIndexCartesian 的情況下會更快,而且還會支援具有自訂索引的陣列,例如 OffsetArrays。如果只需要值,那麼最好直接反覆處理陣列,即 for a in A

遺漏和額外的索引

除了線性索引之外,在某些情況下,N 維陣列可以使用少於或多於 N 個索引進行索引。

如果未編入索引的尾隨維度長度均為一,則可以省略索引。換句話說,只有當這些省略的索引對於一個界內索引表達式只有一個可能的值時,才能省略尾隨索引。例如,大小為 (3, 4, 2, 1) 的四維陣列可以使用僅三個索引進行索引,因為被跳過的維度(第四維度)長度為一。請注意,線性索引優先於此規則。

julia> A = reshape(1:24, 3, 4, 2, 1)
3×4×2×1 reshape(::UnitRange{Int64}, 3, 4, 2, 1) with eltype Int64:
[:, :, 1, 1] =
 1  4  7  10
 2  5  8  11
 3  6  9  12

[:, :, 2, 1] =
 13  16  19  22
 14  17  20  23
 15  18  21  24

julia> A[1, 3, 2] # Omits the fourth dimension (length 1)
19

julia> A[1, 3] # Attempts to omit dimensions 3 & 4 (lengths 2 and 1)
ERROR: BoundsError: attempt to access 3×4×2×1 reshape(::UnitRange{Int64}, 3, 4, 2, 1) with eltype Int64 at index [1, 3]

julia> A[19] # Linear indexing
19

使用 A[] 省略所有索引時,此語義提供了一個簡單的慣用語,用於擷取陣列中的唯一元素,並同時確保只有一個元素。

類似地,如果陣列維度之外的所有索引均為 1(或更普遍地為 axes(A, d) 的第一個且唯一的元素,其中 d 是該特定維度號碼),則可以提供多於 N 個索引。這允許將向量編入索引,例如一欄矩陣

julia> A = [8,6,7]
3-element Vector{Int64}:
 8
 6
 7

julia> A[2,1]
6

反覆運算

反覆運算整個陣列的建議方式為

for a in A
    # Do something with the element a
end

for i in eachindex(A)
    # Do something with i and/or A[i]
end

當您需要每個元素的值,但不需要索引時,使用第一個構造。在第二個構造中,如果 A 是具有快速線性索引的陣列類型,則 i 將為 Int;否則,它將為 CartesianIndex

julia> A = rand(4, 3);

julia> B = view(A, 1:3, 2:3);

julia> for i in eachindex(B)
           @show i
       end
i = CartesianIndex(1, 1)
i = CartesianIndex(2, 1)
i = CartesianIndex(3, 1)
i = CartesianIndex(1, 2)
i = CartesianIndex(2, 2)
i = CartesianIndex(3, 2)
注意

for i = 1:length(A) 相比,使用 eachindex 進行反覆運算提供了一種對任何陣列類型進行反覆運算的高效方式。此外,這也支援具有自訂索引的通用陣列,例如 OffsetArrays

陣列特質

如果您撰寫自訂 AbstractArray 類型,您可以使用以下方式指定它具有快速線性索引

Base.IndexStyle(::Type{<:MyArray}) = IndexLinear()

此設定將導致 eachindex 反覆運算 MyArray 以使用整數。如果您未指定此特質,則會使用預設值 IndexCartesian()

陣列和向量化運算子與函式

以下運算子支援陣列

  1. 一元算術 – -+
  2. 二元算術 – -+*/\^
  3. 比較 – ==!= (isapprox)、

為了啟用數學和其他運算的便利向量化,Julia 提供點語法 f.(args...),例如 sin.(x)min.(x,y),用於對陣列或陣列與標量的混合(廣播運算)進行逐元素運算;這些運算具有額外的優點,當與其他點呼叫結合時,會「融合」成一個循環,例如 sin.(cos.(x))

此外,每個二元運算子都支援 點版本,該版本可以應用於陣列(以及陣列與標量的組合)中,例如 融合廣播運算,例如 z .== sin.(x .* y)

請注意,例如 == 的比較運算會對整個陣列進行運算,給出單一的布林答案。使用點運算子(例如 .==)進行逐元素比較。(對於 < 等比較運算,只有 .< 逐元素版本適用於陣列。)

另外,請注意 max.(a,b)maximum(a) 的差異,前者會對 ab 的元素套用 broadcast,並對 max 進行逐元素運算,而後者則會找出 a 中最大的值。min.(a,b)minimum(a) 也有相同的關係。

廣播

有時,對不同大小的陣列執行逐元素二元運算很有用,例如將一個向量加到矩陣的每一欄。執行此操作的一種低效率方法是將向量複製到矩陣的大小

julia> a = rand(2, 1); A = rand(2, 3);

julia> repeat(a, 1, 3) + A
2×3 Array{Float64,2}:
 1.20813  1.82068  1.25387
 1.56851  1.86401  1.67846

當維度變大時,這會造成浪費,因此 Julia 提供了 broadcast,它會擴充陣列引數中的單一維度,以符合另一個陣列中對應的維度,而不會使用額外的記憶體,並逐元素套用指定的函數

julia> broadcast(+, a, A)
2×3 Array{Float64,2}:
 1.20813  1.82068  1.25387
 1.56851  1.86401  1.67846

julia> b = rand(1,2)
1×2 Array{Float64,2}:
 0.867535  0.00457906

julia> broadcast(+, a, b)
2×2 Array{Float64,2}:
 1.71056  0.847604
 1.73659  0.873631

例如 .+.*點運算子 等同於 broadcast 呼叫(但它們會融合,如 上方所述)。另外還有一個 broadcast! 函數,用於指定明確的目的地(也可以透過 .= 指派以融合的方式存取)。事實上,f.(args...) 等同於 broadcast(f, args...),提供了一個方便的語法來廣播任何函數(點語法)。巢狀「點呼叫」f.(...)(包括對 .+ 等的呼叫)會 自動融合 成單一的 broadcast 呼叫。

此外,broadcast 不僅限於陣列(請參閱函數文件);它也處理純量、元組和其他集合。預設情況下,只有某些參數類型被視為純量,包括(但不限於)NumberStringSymbolTypeFunction 和一些常見的單例,例如 missingnothing。所有其他參數都會在元素級別上進行迭代或索引。

julia> convert.(Float32, [1, 2])
2-element Vector{Float32}:
 1.0
 2.0

julia> ceil.(UInt8, [1.2 3.4; 5.6 6.7])
2×2 Matrix{UInt8}:
 0x02  0x04
 0x06  0x07

julia> string.(1:3, ". ", ["First", "Second", "Third"])
3-element Vector{String}:
 "1. First"
 "2. Second"
 "3. Third"

有時,您需要一個容器(例如陣列),通常會參與廣播,以「受保護」免於廣播在所有元素上進行迭代的行為。透過將其置於另一個容器(例如單一元素 Tuple)中,廣播會將其視為單一值。

julia> ([1, 2, 3], [4, 5, 6]) .+ ([1, 2, 3],)
([2, 4, 6], [5, 7, 9])

julia> ([1, 2, 3], [4, 5, 6]) .+ tuple([1, 2, 3])
([2, 4, 6], [5, 7, 9])

實作

Julia 中的基本陣列類型是抽象類型 AbstractArray{T,N}。它由維度數 N 和元素類型 T 參數化。 AbstractVectorAbstractMatrix 是 1 維和 2 維案例的別名。對 AbstractArray 物件的操作使用較高層級的運算子和函數來定義,與底層儲存無關。這些操作通常可以正確地作為任何特定陣列實作的後備。

AbstractArray 類型包含任何模糊類陣列的內容,而它的實作可能與傳統陣列大不相同。例如,元素可能是依據要求計算,而不是儲存。然而,任何具體的 AbstractArray{T,N} 類型通常至少應該實作 size(A)(傳回 Int 元組)、getindex(A,i)getindex(A,i1,...,iN);可變陣列也應該實作 setindex!。建議這些運算具有近乎常數的時間複雜度,否則某些陣列函數可能會意外地變慢。具體類型通常也應該提供 similar(A,T=eltype(A),dims=size(A)) 方法,用於配置類似陣列以供 copy 和其他非原位運算使用。不論 AbstractArray{T,N} 在內部如何表示,T 都是由整數索引傳回的物件類型(當 A 不為空時,A[1, ..., 1]),而 N 應該是 size 傳回的元組長度。有關定義自訂 AbstractArray 實作的更多詳細資訊,請參閱 介面章節中的陣列介面指南

DenseArrayAbstractArray 的抽象子類型,用於包含所有元素以列優先順序連續儲存的陣列(請參閱 效能提示中的補充說明)。Array 類型是 DenseArray 的具體實例;VectorMatrix 是 1-d 和 2-d 情況的別名。除了所有 AbstractArray 所需的操作之外,專門針對 Array 實作的操作非常少;陣列函式庫的大部分是以通用方式實作,允許所有自訂陣列有類似的行為。

SubArrayAbstractArray 的專業化,透過與原始陣列共用記憶體來執行索引,而不是複製它。SubArray 是使用 view 函數建立的,呼叫方式與 getindex 相同(使用陣列和一系列索引引數)。view 的結果看起來與 getindex 的結果相同,但資料會保留在原處。view 將輸入索引向量儲存在 SubArray 物件中,稍後可用於間接索引原始陣列。透過在運算式或程式碼區塊前面加上 @views 巨集,該運算式中的任何 array[...] 切片都會轉換為建立 SubArray 檢視。

BitArray 是空間效率高的「封裝」布林陣列,每個布林值儲存一個位元。它們可以用於類似 Array{Bool} 陣列(每個布林值儲存一個位元組)的方式,並可透過 Array(bitarray)BitArray(array) 分別轉換為後者或從後者轉換而來。

如果陣列是以元素之間具有明確間距(步幅)的方式儲存在記憶體中,則該陣列為「步幅陣列」。具有受支援元素類型的步幅陣列可以透過傳遞其 指標 和每個維度的步幅,傳遞給外部(非 Julia)函式庫,例如 BLAS 或 LAPACK。 stride(A, d) 是沿著維度 d 的元素間距。例如,內建的 Arrayrand(5,7,2) 傳回,其元素以連續的欄位優先順序排列。這表示第一個維度的步幅(同一欄位中元素間的間距)為 1

julia> A = rand(5, 7, 2);

julia> stride(A, 1)
1

第二個維度的步幅是同一列中元素間的間距,會略過與單一欄位(5)中元素數量一樣多的元素。類似地,在兩個「頁面」(第三個維度)之間跳躍需要略過 5*7 == 35 個元素。此陣列的 步幅 是這三個數字的元組

julia> strides(A)
(1, 5, 35)

在這個特定案例中,記憶體中略過的元素數量與略過的線性索引數量相符。這僅適用於連續陣列,例如 Array(和其他 DenseArray 子類型),一般情況並非如此。具有範圍索引的檢視是非連續步幅陣列的良好範例;考慮 V = @view A[1:3:4, 2:2:6, 2:-1:1]。這個檢視 V 參照與 A 相同的記憶體,但會略過並重新排列其中一些元素。V 的第一個維度的步幅為 3,因為我們只從原始陣列中選取每三列

julia> V = @view A[1:3:4, 2:2:6, 2:-1:1];

julia> stride(V, 1)
3

這個檢視類似地從原始 A 選取每兩列,因此在第二個維度中移動索引時,需要略過等於兩個五元素欄位的元素

julia> stride(V, 2)
10

第三個維度很有趣,因為它的順序是相反的!因此,要從第一個「頁面」到第二個頁面,它必須在記憶體中往後移動,因此它在這個維度中的跨距是負的!

julia> stride(V, 3)
-35

這表示 V指標 實際上是指向 A 的記憶體區塊的中間,而且它指的是記憶體中往後和往前的元素。請參閱 跨距陣列介面指南,以取得更多關於定義您自己的跨距陣列的詳細資訊。 StridedVectorStridedMatrix 是許多內建陣列類型的方便別名,這些陣列類型被視為跨距陣列,讓它們可以傳送給選取的專門實作,這些實作使用高度調整和最佳化的 BLAS 和 LAPACK 函數,僅使用指標和跨距。

值得強調的是,跨距是關於記憶體中的偏移,而不是索引。如果您想在線性(單一索引)索引和笛卡兒(多重索引)索引之間進行轉換,請參閱 LinearIndicesCartesianIndices

  • 1iid,獨立同分布。