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...)
のように定義できる。