知っていて当たり前-11 その他の関数
1. 式の評価 eval()
文字列を式として評価(実行)する。
eval(Meta.parse("1 + 2"))
3
Meta.parse()
は,Julia の抽象構文木(AST) を表す Expr 型のオブジェクトを返す。
Meta.parse("1 + 2")
:(1 + 2)
typeof(Meta.parse("1 + 2")), typeof(:(1 + 2))
(Expr, Expr)
eval(:(1 + 2)) # つまり eval(Meta.parse("1 + 2")) と同じ
3
文字列,Expr の中では $
による補間も使える。
a = 3
eval(Meta.parse("1 + $a"))
4
eval(:(1 + $a))
4
1.1. 関数を定義する例
func = "add"
eval(Meta.parse("$func(a, b) = a + b"))
add(10, 20) # 30
30
2. パイプ |>
多くの関数型のプログラム言語では,処理を行うために関数を入れ子で書くことが普通であった。
inv(sum(x -> x.^2, [1:5;]))
0.01818181818181818
データを「パイプ演算子 |>
」 で次の関数の引数として渡すような書き方をすれば,処理の順番にプログラムを書くことができる。
[1:5;] |> x -> x.^2 |> sum |> inv
0.01818181818181818
適用する関数が複数の引数を保つ場合には,無名関数を使うことによって任意の位置の引数に渡すことができる。
using RDatasets, DataFrames
iris = dataset("datasets", "iris");
select(iris, :SepalLength) |> x -> first(x, 5)
5 rows × 1 columns
SepalLength | |
---|---|
Float64 | |
1 | 5.1 |
2 | 4.9 |
3 | 4.7 |
4 | 4.6 |
5 | 5.0 |
ベクトルをパイプで関数ベクトルにつなげると,要素に対応した関数が適用される。
["a", "list", "of", "strings"] .|> [uppercase, reverse, titlecase, length]
4-element Vector{Any}:
"A"
"tsil"
"Of"
7
3. 関数の合成 ∘
f(g(x))
は 関数 g
に引数 x
を与えた結果を関数 f
の引数にすることである。
「合成演算子 ∘
」 を使って (f ∘ g)(x)
と書くことができる。
sqrt(sum(1:10))
7.416198487095663
(sqrt ∘ sum)(1:10)
7.416198487095663
1:10 |> sum |> sqrt
7.416198487095663
map(first ∘ reverse ∘ uppercase, split("you can compose functions like this"))
6-element Vector{Char}:
'U': ASCII/Unicode U+0055 (category Lu: Letter, uppercase)
'N': ASCII/Unicode U+004E (category Lu: Letter, uppercase)
'E': ASCII/Unicode U+0045 (category Lu: Letter, uppercase)
'S': ASCII/Unicode U+0053 (category Lu: Letter, uppercase)
'E': ASCII/Unicode U+0045 (category Lu: Letter, uppercase)
'S': ASCII/Unicode U+0053 (category Lu: Letter, uppercase)
4. リスト内包表記
リスト内包表記は Python での用語で,Julia ではリスト内包表記というのは変だと思う。Julia では ["123", "45", "6789"]
はリストではないから。
Julia では,これを使う意味はない(使ってはならない)。for ループで書けば 10万倍速い(後述)。
[parse(Int, i) for i in ["123", "45", "6789"]]
3-element Vector{Int64}:
123
45
6789
4.1. enumerate()
を使う場合
enumerate(x)
は 添字(インデックス)とベクトルの要素を同時に使用する。
[println("$i, $j") for (i, j) in enumerate(["123", "45", "6789"])]
1, 123
2, 45
3, 6789
3-element Vector{Nothing}:
nothing
nothing
nothing
4.2. zip()
を使う場合
zip(a, b, ...)
はそれぞれのベクトルの要素を同時に使用する。
[println("$i, $j") for (i, j) in zip(["123", "45", "6789"], ["a", "b", "c"])]
123, a
45, b
6789, c
3-element Vector{Nothing}:
nothing
nothing
nothing
@time x = [sqrt(i) for i in 1:100000000];
0.237202 seconds (47.67 k allocations: 765.485 MiB, 2.92% gc time, 6.48% compilation time)
@time for i in 1:100000000
sqrt(i)
end
0.000001 seconds
4.3. 後置 if を使う場合
後半に if ...
がある場合には,foo in bar
中の foo
が if
の条件を満たすものに対してのみ処理が行われる。
[i^2 for i in 1:10 if i % 2 == 0]
5-element Vector{Int64}:
4
16
36
64
100
4.4. 二重の内包表記
for を 1 回書くのと,2 回書くのでは違いがでてくる。
[10a + b for a=1:3, b=8:9]
3×2 Matrix{Int64}:
18 19
28 29
38 39
[10a + b for a=1:3 for b=8:9]
6-element Vector{Int64}:
18
19
28
29
38
39
5. 辞書内包表記 Dict()
応用すべき例が思いつかない。
a = Dict(k for k = zip(["jp","USA","UK"], [0,1,2]))
Dict{String, Int64} with 3 entries:
"USA" => 1
"UK" => 2
"jp" => 0