Juliaを独習していて、その内容や気になったことをメモ書きとして残しています。
前回まで:
3日目 マニュアル読みの続き
算術演算子と初等関数
算術演算子、論理演算子、ビット演算子
概ね普通なので目についたものだけ触れる。
- 整数での除算は
x ÷ y- ゼロ方向に丸められる整数除算
-
fld,cldなど、丸め方が異なる整数除算もある -
%は、÷に対応する剰余演算子。一方、丸め方の異なる剰余演算子modもある
-
x \ yというのがあり、y / xと同じ意味である - ただし、試したところ引数の評価順序は、前者がx,yの順であるのに対し、後者はy,xの順であった -
xのy乗はx^y -
√は単項演算子のように振る舞う。\sqrt-tabで入力 - bitwise xorは
⊻で、\xor-tabで入力できる - bitwise nand
⊼, bitwise nor⊽もそれぞれ、\nand,\norとtabで入力できる - 右シフトは算術シフト。論理右シフトは
>>> -
+=などの演算子も用意されている - Pythonのような比較演算子の連続にも対応している
1 < 2 <= 2 < 3 == 3 > 2 >= 1 == 1 < 3 != 5- 値の評価順序はundefinedであることがドキュメントに明記されており、副作用を持った項を使うことは推奨されない
これら演算子は、(+)のようにすることで、関数の形で呼び出すこともできる。
julia> (-)(9, 4)
5
julia> (-)(3)
-3
vectrized dot
各演算には、 .+ や .^ などのドット演算が自動的に定義され、配列の各要素に対し、演算が行われる。
julia> [1,2,3] .+ 10
3-element Vector{Int64}:
11
12
13
演算子を関数形式で呼び出す記法では、以下のように書く。 (+).([1, 2, 3], 10)
関数についても、ドット演算を行える。
julia> sin.([0, π/2, π, 3π/2, 2π])
5-element Vector{Float64}:
0.0
1.0
1.2246467991473532e-16
-1.0
-2.4492935982947064e-16
1.+1は、1. + 1なのか1 .+ 1なのか分からないため、このような記法は禁止されている。
演算子の優先順位や結合順序は以下で見られる。
julia> Base.operator_precedence(:+), Base.operator_precedence(:*)
(11, 12)
julia> Base.operator_associativity(:+), Base.operator_associativity(:*)
(:none, :none)
+や*の結合順序がnoneなのは、1 + 2 + 3は(+)((+)(1, 2), 3)や(+)(1, (+)(2, 3))ではなく、(+)(1, 2, 3)と解釈されるかららしい。
複素数と有理数
虚数単位はimで、定数倍や足し算により、複素数を作ることができる。
julia> im
im
julia> typeof(im)
Complex{Bool}
julia> 1im
0 + 1im
julia> typeof(ans)
Complex{Int64}
julia> 1.0 + im
1.0 + 1.0im
julia> typeof(ans)
ComplexF64 (alias for Complex{Float64})
complex(1, 2)のようにしても作ることができる。
1 // 2 で、有理数 $\frac{1}{2}$が作れる。
文字と文字列
Charは32ビット整数であり、Unicodeを扱うこともできる。ただし、1コードポイントに対応しないものは扱えない。
julia> 'A'
'A': ASCII/Unicode U+0041 (category Lu: Letter, uppercase)
julia> 'あ'
'あ': Unicode U+3042 (category Lo: Letter, other)
julia> '𠮟'
'𠮟': Unicode U+20B9F (category Lo: Letter, other)
julia> '😊'
'😊': Unicode U+1F60A (category So: Symbol, other)
julia> '🇫🇮ERROR: syntax: character literal contains multiple characters
Stacktrace:
[1] top-level scope
@ none:1
文字列リテラルはダブルクオートや、三重のダブルクオートで書く。
インデックスや、レンジを与えることで、文字や文字列を取り出せる。
julia> "hello"[1]
'h': ASCII/Unicode U+0068 (category Ll: Letter, lowercase)
julia> "hello"[2:4]
"ell"
長い文字列の部分をコピーせずに取り出すには、@views "hello"[2:4]のようにするといいらしい。
文字列のインデックスは、何文字目、ではなく、UTF-8で書いたときに何バイト目、を表していて、中途半端な参照をするとエラーが発生する。
julia> "aあb"[1:1]
"a"
julia> "aあb"[1:2]
"aあ"
julia> "aあb"[1:3]
ERROR: StringIndexError: invalid index [3], valid nearby indices [2]=>'あ', [5]=>'b'
Stacktrace:
[1] string_index_err(s::String, i::Int64)
@ Base ./strings/string.jl:12
[2] getindex(s::String, r::UnitRange{Int64})
@ Base ./strings/string.jl:278
[3] top-level scope
@ REPL[124]:1
julia> "aあb"[1:5]
"aあb"
nextind/previndを用いて、次/前のインデックスを求めることができる。
julia> nextind("aあb", 1)
2
julia> nextind("aあb", 2)
5
julia> nextind("aあb", 3)
5
julia> nextind("aあb", 5)
6
1文字ずつ取り出すには、こういう方法がある
julia> s = "aあb"
"aあb"
julia> for i = eachindex(s)
println(s[i])
end
a
あ
b
julia> for c in s
println(c)
end
a
あ
b
文字列リテラル中の"$x"は、変数xに置き換えられる。'$'そのものを記述したい場合は"$"のようにエスケープする。
r"..." によって、正規表現を書くことができる。(Pythonのraw文字列にあたるものはraw"...")
julia> typeof(r"")
Regex
julia> match(r"a.c", "abc")
RegexMatch("abc")
replaceは以下のように行う。
julia> replace("first second", r"(\w+) (?<agroup>\w+)" => s"\g<agroup> \1")
"second first"
b"..." はバイト配列リテラル、v"0.1"はバージョン番号リテラル(semver)
4〜6日目 関数とタプル
Functionsを読む。
関数
関数は以下のように定義できる。最後に評価された式の値が返される。(明示的にreturnで値を返すこともできる)
値を返したくない場合は、nothingを返すとよい(return nothing)
julia> function f(x,y)
x + y
end
f (generic function with 1 method)
julia> f(x,y) = x + y
f (generic function with 1 method)
無名関数は、以下のように定義できる。
julia> x -> x^2 + 2x - 1
#1 (generic function with 1 method)
julia> function (x)
x^2 + 2x - 1
end
#5 (generic function with 1 method)
引数の型や返り値の型は、::TypeNameでつけることができる。
f(x::Int64)::Int64 = x
定義した関数のvectorize版はドット記法により使うことができる。
julia> inc(x::Int)::Int = x + 1
inc (generic function with 1 method)
julia> inc.([1, 2, 3])
3-element Vector{Int64}:
2
3
4
hcat, vcat, hvcatなど
[1, 2, 3] のように書くと、Vectorになる。
julia> [1,2,3]
3-element Vector{Int64}:
1
2
3
[1 2 3] のように、カンマを入れずに書くと、hcat(1, 2, 3)と同じ意味で扱われ、横方向にそれらを並べたMatrixになる。
julia> [1 2 3]
1×3 Matrix{Int64}:
1 2 3
julia> hcat(1, 2, 3)
1×3 Matrix{Int64}:
1 2 3
[[1, 2] [3, 4] [5, 6]] とすることで、2x3のMatrixとなる。
julia> [[1, 2] [3, 4] [5, 6]]
2×3 Matrix{Int64}:
1 3 5
2 4 6
[[1 2] [3 4] [5 6]] は、全てを横に並べたような形になる。[[1 2], [3 4], [5 6]]は、MatrixのVectorである。
julia> [[1 2] [3 4] [5 6]]
1×6 Matrix{Int64}:
1 2 3 4 5 6
julia> [[1 2], [3 4], [5 6]]
3-element Vector{Matrix{Int64}}:
[1 2]
[3 4]
[5 6]
[1; 2; 3] のように書くと、vcat(1, 2, 3) と同じ意味で扱われ、縦方向にそれらを並べる。
julia> [1; 2; 3]
3-element Vector{Int64}:
1
2
3
julia> [[1; 2]; 3]
3-element Vector{Int64}:
1
2
3
Matrixをvcatで並べると、MatrixのVectorではなくMatrixとなる。
julia> [[1 2]; [3 4]; [5 6]]
3×2 Matrix{Int64}:
1 2
3 4
5 6
[1 2; 3 4; 5 6]も、Matrixとなり、これはhvcat((2, 2, 2), 1, 2, 3, 4, 5, 6)の意味である。
julia> [[1 2]; [3 4]; [5 6]]
3×2 Matrix{Int64}:
1 2
3 4
5 6
julia> hvcat((2, 2, 2), 1, 2, 3, 4, 5, 6)
3×2 Matrix{Int64}:
1 2
3 4
5 6
'を後置することで、ArrayやMatrixの転置複素共役(エルミート共役。実数においては単なる転置)を返す。複素数においては複素共役を返し、実数においては変化しない。好みや慣れの問題だと思うが、この記法はどうなのか?
julia> A = [1.0+1im 2+2im; 3+3im 4+4im]
2×2 Matrix{ComplexF64}:
1.0+1.0im 2.0+2.0im
3.0+3.0im 4.0+4.0im
julia> A'
2×2 adjoint(::Matrix{ComplexF64}) with eltype ComplexF64:
1.0-1.0im 3.0-3.0im
2.0-2.0im 4.0-4.0im
julia> A''
2×2 Matrix{ComplexF64}:
1.0+1.0im 2.0+2.0im
3.0+3.0im 4.0+4.0im
julia> A = [1.0+1im 2+2im 3+3im]
1×3 Matrix{ComplexF64}:
1.0+1.0im 2.0+2.0im 3.0+3.0im
julia> A'
3×1 adjoint(::Matrix{ComplexF64}) with eltype ComplexF64:
1.0 - 1.0im
2.0 - 2.0im
3.0 - 3.0im
julia> A''
1×3 Matrix{ComplexF64}:
1.0+1.0im 2.0+2.0im 3.0+3.0im
julia> x = 1.0 + 2.0im
1.0 + 2.0im
julia> x'
1.0 - 2.0im
julia> x''
1.0 + 2.0im
A[i]はgetindex(A, i)に等しく、A[i] = xはsetindex!(A, x, i)に等しい。stylistic conventionsから、末尾に!がつくものは、引数を変更していることを表す。
タプル
(1, "x", 3.0) や (1,) 、()はタプルである。
julia> typeof((1, "x", 3.0))
Tuple{Int64, String, Float64}
タプルは、getindexで参照ができる。setindex!で変更はできない。
julia> t = (1, "x", 3)
(1, "x", 3)
julia> t[1]
1
julia> t[2]
"x"
julia> t[3]
3
julia> t[1] = 2
ERROR: MethodError: no method matching setindex!(::Tuple{Int64, String, Int64}, ::Int64, ::Int64)
Stacktrace:
[1] top-level scope
@ REPL[58]:1
複数の値を返す関数は、タプルを返す関数として作ることができる。このあたりはPythonと同じ。
以下のように名前付きタプル(named tuple)を作ることができる。名前付きタプルは、インデックスでも名前でも参照することができる。
julia> x = (a=10, b=20)
(a = 10, b = 20)
julia> x[1]
10
julia> x.a
10
分割代入も行える。
左辺の数が足りない場合は、エラーなどにはならない。右辺の数が足りない場合は、BoundsErrorが出される。
julia> a, b = (1, 2, 3)
(1, 2, 3)
julia> b
2
julia> a, b, c = (1, 2)
ERROR: BoundsError: attempt to access Tuple{Int64, Int64} at index [3]
Stacktrace:
[1] indexed_iterate(t::Tuple{Int64, Int64}, i::Int64, state::Int64)
@ Base ./tuple.jl:88
[2] top-level scope
@ REPL[71]:1
行列を右辺に取ったときの挙動は少し思っていたのと違った。
julia> a, b = [1 2; 3 4]
2×2 Matrix{Int64}:
1 2
3 4
julia> a
1
julia> b
3
Julia 1.6以降は、左辺の最後の要素に...をつけると、残りの要素が全て代入される。
julia> a, b... = (1, 2, 3, 4)
(1, 2, 3, 4)
julia> a
1
julia> b
(2, 3, 4)
julia> a, b... = [1, 2, 3, 4]
4-element Vector{Int64}:
1
2
3
4
julia> a
1
julia> b
3-element Vector{Int64}:
2
3
4
julia> a, b... = 1:4
1:4
julia> a
1
julia> b
3-element Vector{Int64}:
2
3
4
julia> a, b, c... = (1, 2)
(1, 2)
julia> c
()
julia> a, b, c... = [1, 2]
2-element Vector{Int64}:
1
2
julia> c
Int64[]
julia> a, b... = [1 2 3; 4 5 6; 7 8 9]
3×3 Matrix{Int64}:
1 2 3
4 5 6
7 8 9
julia> a
1
julia> b
8-element Vector{Int64}:
4
7
2
5
8
3
6
9
Julia 1.9からは、...をどの位置でも使えるようになった。
julia> a, b..., c = 1:4
1:4
julia> a
1
julia> b
2-element Vector{Int64}:
2
3
julia> c
4
julia> a..., b, c = 1:4
1:4
julia> a
2-element Vector{Int64}:
1
2
julia> b
3
julia> c
4
f((x..., y)) = x という記法は面白い。
julia> f((x..., y)) = x
f (generic function with 1 method)
julia> f(1:4)
3-element Vector{Int64}:
1
2
3
# これは以下と同じ
julia> function f(xs)
x..., y = xs
x
end
f (generic function with 1 method)
julia> f(1:4)
3-element Vector{Int64}:
1
2
3
無名関数で、このような記法をするためには、タプルの後ろにカンマが必要。 ((x..., y), ) -> x
名前付きタプルの分割代入は (; )によって行える。
julia> (; b, a) = (a=1, b=2, c=3)
(a = 1, b = 2, c = 3)
julia> a
1
julia> b
2
これらは、関数の第一引数を分割しているが、それとは別で、可変長引数の関数を f(x, y, z...) = x のように定義できる。可変長引数では、...は最後の引数にしか付けられない。
f(x, y=1) = x + yのように、デフォルト値付き引数の関数を定義することができる。
キーワード引数は f(x; y=1) や f(x; options...) のように定義できる。