Vectorsのコンセプトでは、「配列はJulia言語の中心です」と述べ、ベクトルは1次元であると説明しました。
これを踏まえると、ベクトルに対して_何かをする_ための多様で強力な方法を、言語が数多く提供してくれると期待するのももっともです。それが何を意味するにせよ。
ごく単純な関数の中には、ベクトルを入力として受け取り、スカラーを返すものがあります。
v = [2, 3, 4]
length(v) # => 3
sum(v) # => 9
数値のベクトルがあり、各値から0.5を引きたいとします。
julia> v = [1.2, 1.5, 1.7]
3-element Vector{Float64}:
1.2
1.5
1.7
julia> v - 0.5
ERROR: MethodError: no method matching -(::Vector{Float64}, ::Float64)
これは失敗します。では、別のベクトルを引いたらどうでしょうか?
julia> v - [0.5, 0.5, 0.5]
3-element Vector{Float64}:
0.7
1.0
1.2
成功しますが、ベクトルが長くなるにつれて、かなり面倒でメモリを多く消費します。
幸いなことに、Juliaにはこの問題をとても簡単に解決する「魔法の」ドットがあります。必要なのはv .- 0.5だけです。
なぜなのかは次のセクションで説明します。
つまり、v - 0.5は失敗しますが、v .- 0.5は成功します。ここで、ドットが何をしているのかを理解する必要があります。
2つのことが組み合わさって、望みの結果が得られます。
まず、任意の中置演算子の前にドットを付けると、「この演算を各要素に個別に適用する」という意味になります。
同様に、関数名の_後ろ_にドットを付けると、その関数がスカラー入力用に書かれていても「ベクトル化」されます。
julia> sqrt.([1, 4, 9])
3-element Vector{Float64}:
1.0
2.0
3.0
前の例で、同じ長さのベクトル同士は引けることを見ました。ただし、.-が-よりも_安全_な演算子であることは理解しておいてください。要素ごとに操作する意図が明確になるからです。
julia> v .- [0.5, 0.5, 0.5]
3-element Vector{Float64}:
0.7
1.0
1.2
長さが異なるベクトルではどうでしょうか?
julia> v .- [0.5, 0.5]
ERROR: DimensionMismatch: arrays could not be broadcast to a common size
julia> v .- [0.5,]
3-element Vector{Float64}:
0.7
1.0
1.2
一般に、長さが異なるとエラーになりますが、一方の長さが1の場合(技術的には「シングルトン」次元)は_例外_です。
[0.5,]や単なる0.5のようなシングルトンは、繰り返しによって必要な長さに自動的に拡張されます。
これがbroadcastingの核心です。
インデックス番号でベクトルの要素を選択する方法は、これまでのコンセプトで説明しました。
a = collect('A':'Z') # => 26-element Vector{Char}
# index with an integer
a[2] # => 'B'
# index with a range
a[12:2:18] # => ['L', 'N', 'P, 'R']
# index with another vector
a[ [1, 3, 5] ] # => ['A', 'C', 'E']
ある論理式(技術的には「述語」)を満たす要素を選択することもできます。 これには通常、ブロードキャストが必要です。
julia> a[a .< 'D']
3-element Vector{Char}:
'A': ASCII/Unicode U+0041 (category Lu: Letter, uppercase)
'B': ASCII/Unicode U+0042 (category Lu: Letter, uppercase)
'C': ASCII/Unicode U+0043 (category Lu: Letter, uppercase)
より複雑な式になると、ドットが増えがちです(ただし、小さくて入力しやすいです)。
julia> a[a .< 'D' .|| a .> 'W']
6-element Vector{Char}:
'A': ASCII/Unicode U+0041 (category Lu: Letter, uppercase)
'B': ASCII/Unicode U+0042 (category Lu: Letter, uppercase)
'C': ASCII/Unicode U+0043 (category Lu: Letter, uppercase)
'X': ASCII/Unicode U+0058 (category Lu: Letter, uppercase)
'Y': ASCII/Unicode U+0059 (category Lu: Letter, uppercase)
'Z': ASCII/Unicode U+005A (category Lu: Letter, uppercase)
「ベクトル」は実際には、範囲など、適切な順序付きイテラブルであれば何でもよいことを思い出してください。
julia> n = 3:10
3:10
julia> n[isodd.(n)]
4-element Vector{Int64}:
3
5
7
9
過去7日間に庭へ何羽の鳥が訪れたかを記録している、熱心なバードウォッチャーです。
取り組む課題は6つあり、どれも庭に訪れた鳥の数に関するものです。
今日、庭に何羽の鳥が訪れたかを返すtoday()関数を実装しましょう。
鳥の数は日付順に並んでいて、最初の要素が最も古い日の数、最後の要素が今日の数です。
julia> birds_per_day = [2, 5, 0, 7, 4, 1]
julia> today(birds_per_day)
1
今日の数を1増やすincrement_todays_count()関数を実装しましょう。
julia> birds_per_day = [2, 5, 0, 7, 4, 1]
julia> increment_todays_count(birds_per_day)
[2, 5, 0, 7, 4, 2]
鳥が1羽も庭に訪れなかった日があればtrueを返し、そうでなければfalseを返すhas_day_without_birds()関数を実装しましょう。
julia> birds_per_day = [2, 5, 0, 7, 4, 1]
julia> has_day_without_birds(birds_per_day)
true
週の初めから庭に訪れた鳥の数を返すcount_for_first_days()関数を実装しましょう。ただし、数えるのは週の初めから指定された日数分だけに限定します。
julia> birds_per_day = [2, 5, 0, 7, 4, 1]
julia> count_for_first_days(birds_per_day, 4)
14
日によっては、ほかの日より忙しいこともあります。
忙しい日とは、庭に5羽以上の鳥が訪れた日のことです。
忙しい日を数えて返すbusy_days()関数を実装しましょう。
julia> birds_per_day = [2, 5, 0, 7, 4, 1]
julia> busy_days(birds_per_day)
2
記録を増やして、複数の週の数を残すことにしました。 どの週も、数は曜日ごとに並んでいて、最初の項目が月曜日、最後の項目が日曜日です。
2週間分の平均を返すaverage_per_day()関数を実装しましょう。
julia> week1 = [7, 2, 9, 1, 3, 0, 10]
julia> week2 = [2, 6, 4, 1, 3, 8, 9]
julia> average_per_day(week1, week2)
[4.5, 4.0, 6.5, 1.0, 4.0, 3.0, 9.5]