Juliaの中心的な目標の1つは、数値データをすばやく、効率よく計算できることです。 それも、_大量_の数値データについてです。
通常、そうしたデータは配列(または関連する型)に格納されるので、配列がJulia言語の心臓部にあると言ってもよいでしょう。
配列は任意の大きさにでき(ハードウェアのメモリの制約を受けるだけです)、次元数もいくつでも増やせます。
配列のなかには、特別な名前がついたものがあります(線形代数から借りてきた名前です)。
1次元の配列はVector、2次元の配列はMatrixと呼びます。
どちらも、JuliaではArrayのサブタイプです。
この入門的なコンセプトでは、Vectorの基本を扱います。 多次元配列については、シラバスの後半で説明します。
Juliaでは、Vectorは、インデックス番号でアクセスできる、順序づけられた値の並びです。
Vectorを作るもっとも簡単な方法は、値を角括弧([])で囲んで並べることです:
julia> num_vec = [1, 4, 9]
3-element Vector{Int64}:
1
4
9
julia> str_vec = ["arrays", "are", "important"]
3-element Vector{String}:
"arrays"
"are"
"important"
Vectorの型は、各要素の型に合わせて決まります。
厳密に言うと、これは_列_ベクトルですが、線形代数に馴染みのない方は今は気にしないでください。 多次元配列のコンセプトを読めば、もっとよくわかるはずです。
では、Vectorはすべて同じ型でなければならないのでしょうか?
ある意味では、そのとおりです。最高のパフォーマンスを求めるなら、そうすることをおすすめします。
ただし、Juliaは(必要なときには)Any型を使うことで、この制約を回避できます:
julia> mixed_vector = [1, "str", 'c']
3-element Vector{Any}:
1
"str"
'c': ASCII/Unicode U+0063 (category Ll: Letter, lowercase)
空のVectorを作ると、Juliaは要素の型をデフォルトでAnyにしますが、空の配列に希望の要素の型を注釈として付ければ、もっと具体的に指定できます。
julia> empty_vec = []
Any[]
julia> empty_int_vec = Int64[]
Int64[]
すべての要素が0、またはすべてが1のVectorから始めることは、非常によくあります。
そのために、(名前のとおり)zeros()とones()という関数が用意されています。これらはVectorのサイズを入力として受け取ります。
デフォルトの型はFloat64ですが、必要に応じて別の数値型を指定することもできます。
同じ考え方で、fill()関数は、繰り返す値と、作りたいVectorのサイズの両方を受け取ります。
julia> zeros(3)
3-element Vector{Float64}:
0.0
0.0
0.0
julia> ones(3)
3-element Vector{Float64}:
1.0
1.0
1.0
julia> ones(Int64, 3)
3-element Vector{Int64}:
1
1
1
julia> fill(42, 3)
3-element Vector{Int64}:
42
42
42
ほかにもたくさんの選択肢があります。詳しくはマニュアルを参照してください。
次のルールは、ぜひ読んで覚えてください:
これは、ほかのデータサイエンス系の言語(R、Matlab、Fortranなど)を使ったことがある人にはお馴染みですが、C言語系のバックグラウンドを持つコンピューター科学者には驚きかもしれません。
それ以外は、インデックスは予想どおりに動きます。角括弧で囲むだけです:
squares = [0, 1, 4, 9, 16]
squares[1] # 0
squares[3] # 4
squares[begin] # 0 ("begin" is synonym for 1)
squares[end] # 16 ("end" is synonym for length(squares))
end(とても便利です)とbegin(おそらくそうでもありません)を使ったインデックスの便利さにも注目してください。
Pythonプログラマーなら、負のインデックスはどうなっているのだろうと思ったかもしれません。
残念ながら、Juliaにはありません。負のインデックスを使うとBoundsErrorが発生し、1より小さいインデックスやlength(squares)より大きいインデックスでも同じです。
JuliaのVectorは_ミュータブル_です。個々の要素の中身を書き換えられます。
julia> vals = [1, 3, 5, 7]
4-element Vector{Int64}:
1
3
5
7
julia> vals[2] = 4 # reassign the value of this element
4
# Only the value at position 2 changes:
julia> vals
4-element Vector{Int64}:
1
4
5
7
Vectorを操作するための関数はたくさんありますが、Juliaのドキュメントでは、これらはVectorではなく「コレクション」として一般化されています。
慣習として、入力を変更する関数の名前には!が付きます。
コンパイラーがこれを強制することはありませんが、Juliaの世界ではきわめて_強い_慣習です。
便利な関数をいくつか紹介します。
push!()を使います。pop!()を使います。pushfirst!()とpopfirst!()を使います。insert!()とdeleteat!()があります。julia> vals = [1, 3]
2-element Vector{Int64}:
1
3
julia> push!(vals, 5, 6) # can add multiple values
4-element Vector{Int64}:
1
3
5
6
julia> pop!(vals) # mutates vals, return popped value
6
julia> vals
3-element Vector{Int64}:
1
3
5
Vectorの連結はかなり簡単です。ただし、マニュアルをちらりと見ると、多次元配列を考えると複雑なことが待ち受けていると警告しています。
append!()関数は、複数のVectorと単一の要素を混ぜたものを入力として受け取れます。
julia> append!([1, 2], [3, 4], [-1, -2], 15)
7-element Vector{Int64}:
1
2
3
4
-1
-2
15