Julia入門 配列

引き続き、Julia入門。
本日は配列について。型の話が避けられなかったので、ちょっとだけその話もする。

配列の宣言

julia> a = [1, 2, 3]
3-element Array{Int64,1}:
 1
 2
 3

julia> arr = ["this", "is", "an", "array"]
4-element Array{ASCIIString,1}:
 "this" 
 "is"   
 "an"   
 "array"

julia> arr[4]
"array"

julia> arr[1]
"this"

上記のように、[ ] で作る。
非常に重要なポイントとして、配列の最初のインデックスは0ではなくて1。

REPLからの出力に出ているが、、初期化の際の内容によって、何の配列なのかが決まる。
1番目の例では整数であるInt64、2番目の例では文字列のASCIIString型になっている。

Juliaでの文字列はASCII文字列のASCIIStringと他バイト文字のUTF8Stringで型が異なるため、上記のarrに日本語を追加するとエラーになる。

julia> push!(arr, "ゆにこーど")
ERROR: invalid ASCII sequence
 in convert at /opt/homebrew-cask/Caskroom/julia/0.3.8/Julia-0.3.8.app/Contents/Resources/julia/lib/julia/sys.dylib
 in push! at array.jl:460

日本語を追加する予定があるのであれば、宣言のときにUTF8String[]という形式で、型を指定してあげればよい。

julia> utf8_array = UTF8String["new", "utf8string", "array"]
3-element Array{UTF8String,1}:
 "new"       
 "utf8string"
 "array"     

julia> push!(utf8_array, "日本語でおk")
4-element Array{UTF8String,1}:
 "new"       
 "utf8string"
 "array"     
 "日本語でおk"    

どんな型でも入る配列が希望であれば、Any型というものがある。

julia> any_array = Any[123, "数字", 1.54]
3-element Array{Any,1}:
 123    
    "数字"
   1.54 

julia> typeof(any_array)
Array{Any,1}

julia> for val = any_array
           println(val, " ----> ", typeof(val))
       end
123 ----> Int64
数字 ----> UTF8String
1.54 ----> Float64

上記のようにtypeof関数を使えば、対象の変数の型を調べることができる。

基本的なメソッド

julia> arr = UTF8String["a", "b", "c"]
3-element Array{UTF8String,1}:
 "a"
 "b"
 "c"

julia> length(arr)     # 長さ
3

julia> push!(arr, "d")    # エレメントの追加
4-element Array{UTF8String,1}:
 "a"
 "b"
 "c"
 "d"

julia> push!(arr, "e", "f")     # 複数のエレメント追加
6-element Array{UTF8String,1}:
 "a"
 "b"
 "c"
 "d"
 "e"
 "f"

julia> new_arr = ["x", "y", "z"]
3-element Array{ASCIIString,1}:
 "x"
 "y"
 "z"

julia> append!(arr, new_arr)    # 配列の結合
9-element Array{UTF8String,1}:
 "a"
 "b"
 "c"
 "d"
 "e"
 "f"
 "x"
 "y"
 "z"

julia> insert!(arr, 7, "G")    # 指定したインデックスにエレメントを追加
10-element Array{ASCIIString,1}:
 "a"
 "b"
 "c"
 "d"
 "e"
 "f"
 "G"
 "x"
 "y"
 "z"

そのほかのメソッドこちらを参照のこと。
なお、上記のpop!, insert!, append! など、ビックリマークがついているとおりどれも破壊的な関数で、指定した配列自体が変更される。

ちょっと関数型っぽくないが、Julia全体として、あまりイミュータビリティにはこだわりがないというか。
上記の関数に非破壊的なバージョンもないし。なんとなくそういうポリシーだと思っておくと良いと思う。

2次元配列

これまたちょっとトリッキーなところです。

julia> array = [[1, 2], [3, 4], [5, 6]]
6-element Array{Int64,1}:
 1
 2
 3
 4
 5
 6

直感的に上記のようにかくと、すべて連結されてただの1次元の配列になる。
シンプルな2次元配列の宣言と初期化は以下の通り。

julia> array = [1 2; 3 4; 5 6]
3x2 Array{Int64,2}:
 1  2
 3  4
 5  6

julia> array[2,2]
4

Juliaでは、「2次元配列」と「配列の配列」は全く異なるものであることを覚えておこう。
配列の配列を宣言したければ、以下の通り。

julia> array_of_array = Array{Int64}[[1, 2, 3], [4, 5, 6], [7, 8, 9]]
3-element Array{Array{Int64,N},1}:
 [1,2,3]
 [4,5,6]
 [7,8,9]

julia> array_of_array[3][2]
8

3次元以上の配列

3次元以上の配列はリテラルでは宣言と同時に初期化はできない。
Array(T, d1, d2, d3) という形式で宣言し、部分ごとに割り当てる。

julia> three_d = Array(UTF8String, 2, 2, 3)
2x2x3 Array{UTF8String,3}:
[:, :, 1] =
 #undef  #undef
 #undef  #undef

[:, :, 2] =
 #undef  #undef
 #undef  #undef

[:, :, 3] =
 #undef  #undef
 #undef  #undef

jjulia> three_d[:, :, 1] = UTF8String["the" "first"; "2x2" "array"]
2x2 Array{UTF8String,2}:
 "the"  "first"
 "2x2"  "array"

julia> three_d[:, :, 2] = UTF8String["the" "second"; "2x2" "array"]
2x2 Array{UTF8String,2}:
 "the"  "second"
 "2x2"  "array" 

julia> three_d[:, :, 3] = UTF8String["the" "third"; "2x2" "array"]
2x2 Array{UTF8String,2}:
 "the"  "third"
 "2x2"  "array"

julia> three_d
2x2x3 Array{UTF8String,3}:
[:, :, 1] =
 "the"  "first"
 "2x2"  "array"

[:, :, 2] =
 "the"  "second"
 "2x2"  "array" 

[:, :, 3] =
 "the"  "third"
 "2x2"  "array"

julia> three_d[1, 2, :]
1x1x3 Array{UTF8String,3}:
[:, :, 1] =
 "first"

[:, :, 2] =
 "second"

[:, :, 3] =
 "third"

上記のように:(コロン)は対象の次元のデータすべて、という意味になる。

数字の場合には、上記の形式で宣言すると初期化されていない状態(変な数字が入る)になってしまうので、zeros関数で宣言するのが良いと思う。

下の例は各次元が2ずつの4次元配列を宣言する例。

num_array = zeros(Float64, 2, 2, 2, 2)
2x2x2x2 Array{Float64,4}:
[:, :, 1, 1] =
 0.0  0.0
 0.0  0.0

[:, :, 2, 1] =
 0.0  0.0
 0.0  0.0

[:, :, 1, 2] =
 0.0  0.0
 0.0  0.0

[:, :, 2, 2] =
 0.0  0.0
 0.0  0.0

例えば640x480xRGBA各8bitの画像データを配列で表現した時に一番上の列を全部255にしたかったら以下のような形。
ちなみにrandでzerosやArrayと同じ形式で、ランダムな数字の入った配列を宣言できる。
2行目のfillで、指定した形を1番目の引数で埋めた配列を作れる。
(pythonで[255] * 16 とやっていたのを、juliaではfill(255, 16) と書く)

julia> vga_rgba_data = rand(UInt8, 640, 480, 4)
(略)
julia> vga_rgba_data[:, 1, :] = fill(255, 640, 1, 4)
640x1x4 Array{Int64,3}:
(略)

そのほか

内包表記

pythonと同様に書ける

julia> [i for i in 1:16]
16-element Array{Int64,1}:
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16

Vector, Matrix

Vector, Matrixという二つの型があるのだけど、それぞれ1次元配列、2次元配列の別名である。

julia> Vector{Any} == Array{Any, 1}
true

julia> Matrix{Any} == Array{Any, 2}
true

いじょう。