LoginSignup
65
67

More than 3 years have passed since last update.

Juliaで役に立つ豆知識

Last updated at Posted at 2020-08-08

何かJuliaであまり知られていないっぽいけれど便利な機能をここに追記していこうと思います。
どんなトピックスがあるかは、右の目次を見てください。

確認環境

Julia 1.4.2
Mac OS 10.14.6

struct関連

structの値の初期化にkwdefを使う

Juliaのstructを使うときにあらかじめ値を入れておきたい場合があると思います。
一つの方法は

struct TestA
    a ::Float64
    b ::Array{Float64,1}

    TestA() = new(0,rand(3))
end

とstructの中に定義する方法ですね。

testA = TestA()
println(testA)
TestA(0.0, [0.6482247142585289, 0.3035639028122792, 0.697402899461609])

同様のことをkwdefを使ってできます。

Base.@kwdef struct TestAkw
    a ::Float64 = 0
    b ::Array{Float64,1} = rand(3)
end

kwdefを使うと、フィールドにそのまま値を入れることができるので、初期値を入れるときに非常に便利です。

structで定義したArrayは実は値を変えられる

例えば、上で定義したstructを使って、

Base.@kwdef struct TestAkw
    a ::Float64 = 0
    b ::Array{Float64,1} = rand(3)
end

testAkw = TestAkw()
println(testAkw)
testAkw.b[:] = rand(3)
println(testAkw)

ができます。structは変更不可能なはずなのに、bの値を変えられます。ただし、

testAkw.b = rand(3)
testAkw.b = rand(4)
ERROR: LoadError: setfield! immutable struct of type TestAkw cannot be changed
Stacktrace:

とかはエラーが出ます。これは、定義した配列に代入することは可能でも、新しい配列をフィールドとして定義できないということだと思います。
また、定義ずみの配列にpushすることは可能です。

push!(testAkw.b,2) 
println(testAkw)

TestAkw(0.0, [0.09444996612558998, 0.030326088326802836, 0.4934630346414506, 2.0])

とできます。

structを配列のように扱う

Base.@kwdef mutable struct TestB
    a ::Float64 = 0
    b ::Array{Float64,1} = rand(3)
end

というstructを定義したとします。このstructを配列のように扱いたければ、

function Base.setindex!(x::TestB,v,i) 
    x.b[i] = v
end

function Base.getindex(x::TestB,i)
    return x.b[i]
end

という関数を定義しておくと、

testb = TestB()
println(testb)
testb[2] = 3
println(testb)
v = testb[2]
println(v) 
TestB(0.0, [0.20274453089015654, 0.3365997667642584, 0.04122776256227012])
TestB(0.0, [0.20274453089015654, 3.0, 0.04122776256227012])
3.0

のようにtestb[2]のような形でフィールドのbに入れられます。
自分で作ったtypeをベクトルとみなす in Julia

Juliaの多重ディスパッチで遊んでみる:2次元1次元配列
も参照すると良いと思います。

structが持っているフィールド名を辞書型に格納する

あるstructの中身がわからないとして、その中身を取り出す方法があります。例えば、

function make_parametersdict(p::T) where T <: Any
    pnames = fieldnames(T)
    pdict = Dict()
    for i=1:length(pnames)
        pdict[String(pnames[i])] = getfield(p,pnames[i])
    end
    return pdict,pnames
end

という関数を定義しておくと、中身のわからないstruct pに対してmake_parametersdict(p)すれば、フィールド名とその値がセットになったものを取り出すことができます。
例えば、

Base.@kwdef mutable struct TestB
    a ::Float64 = 0
    b ::Array{Float64,1} = rand(3)
end

function make_parametersdict(p::T) where T <: Any
    pnames = fieldnames(T)
    pdict = Dict()
    for i=1:length(pnames)
        pdict[String(pnames[i])] = getfield(p,pnames[i])
    end
    return pdict,pnames
end

testB = TestB()
pdict,pnames = make_parametersdict(testB)
display(pdict)

をすると、

Dict{Any,Any} with 2 entries:
  "b" => [0.421301, 0.967549, 0.64976]
  "a" => 0.0     

が得られます。

structが持っているフィールド名と同じ名前の変数を用意する

上と組み合わせて、

Base.@kwdef mutable struct TestB
    a ::Float64 = 0
    b ::Array{Float64,1} = rand(3)
end

function make_parametersdict(p::T) where T <: Any
    pnames = fieldnames(T)
    pdict = Dict()
    for i=1:length(pnames)
        pdict[String(pnames[i])] = getfield(p,pnames[i])
    end
    return pdict,pnames
end

testB = TestB()
pdict,pnames = make_parametersdict(testB)

for i=1:length(pnames)
    eval(Expr(:(=), pnames[i], pdict[String(pnames[i])]))
end

println("a = ", a)
println("b = ", b)

とします。出力結果は、

a = 0.0
b = [0.48592393007162893, 0.492182409060139, 0.609400886449827]

となります。aやbはあらわには定義していないはずなのに、ちゃんと定義されています。
これは、

eval(Expr(:(=), pnames[i], pdict[String(pnames[i])]))

がポイントで、pnames[i]がaあるいはbを表しています。
aあるいはbにpdict[String(pnames[i])]を代入するという意味の数式Exprを組み立て、それをevalで評価しています。
これを応用すると、

function loading()
    p = TestB()
    pdict,pnames = make_parametersdict(p)


    for i=1:length(pnames)
        eval(Expr(:(=), pnames[i], pdict[String(pnames[i])]))
    end

    if length(ARGS)  1
        include(pwd()*"/"*ARGS[1])
    end

    for i=1:length(pnames)
        pdict[String(pnames[i])] = eval(pnames[i])
        setfield!(p, pnames[i], eval(pnames[i]));
    end

    display(p)
    println("\n")

end

loading()

として、file.jlの中身を

a = 3.3

とすれば、

julia testj.jl file.jl
TestB(3.3, [0.44783084199216416, 0.7498487813060268, 0.361759560432239])

のように、structに対して、外部から与えられない値はそのまま、与えられた値は変更する、ということが可能です。

配列関連

collectで配列を取り出す

1:10みたいなものから配列を作るには、

a = 1:10
b = collect(a)

とすればよいです。出力は

10-element Array{Int64,1}:
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10

となります。なお、2飛ばしなどは

a = 1:2:10
b = collect(a)

で作れます。

5-element Array{Int64,1}:
 1
 3
 5
 7
 9

値を取り出したいときは

a = 1:2:10
println(a[1])
println(a[2])

のような感じで簡単にできます。

StaticArraysは速い

例えばこのようなコードがあったとします。

mutable struct TestB
    U::Array{Array{Float64,1},1} 
    TestB(n) = new(Array{Array{Float64,1},1}(undef,n))
end
n = 1000000
testB = TestB(n)

function loop1(testB)
    for i=1:length(testB.U)
        testB.U[i] = rand(4)
    end
end

function loop2!(testB)
    for i=1:length(testB.U)
        testB.U[i] = rand(4)
    end
end

println("loop1")
@time loop1(testB)
@time loop1(testB)
@time loop1(testB)
println("loop2!")

@time loop2!(testB)
@time loop2!(testB)
@time loop2!(testB)

これは、配列の配列であるUの中身を更新しています。
出力結果は例えば、

loop1
  0.122696 seconds (1.03 M allocations: 108.415 MiB, 36.20% gc time)
  0.097994 seconds (1000.00 k allocations: 106.812 MiB, 37.45% gc time)
  0.150192 seconds (1000.00 k allocations: 106.812 MiB, 58.67% gc time)
loop2!
  0.160977 seconds (1.03 M allocations: 108.294 MiB, 59.41% gc time)
  0.106944 seconds (1000.00 k allocations: 106.812 MiB, 46.52% gc time)
  0.108966 seconds (1000.00 k allocations: 106.812 MiB, 47.72% gc time)

となります。

これをStaticArraysを用いて、

mutable struct TestBst
    U::Array{SVector{4,Float64},1} 
    TestBst(n) = new(Array{SVector{4,Float64},1}(undef,n))
end
testBst = TestBst(n)

println("loop1")
@time loop1(testBst)
@time loop1(testBst)
@time loop1(testBst)

println("loop2!")
@time loop2!(testBst)
@time loop2!(testBst)
@time loop2!(testBst)

とすると、

loop1
  0.303754 seconds (651.65 k allocations: 44.886 MiB, 9.10% gc time)
  0.000873 seconds (10.00 k allocations: 1.068 MiB)
  0.000544 seconds (10.00 k allocations: 1.068 MiB)
loop2!
  0.009977 seconds (39.01 k allocations: 2.593 MiB)
  0.000544 seconds (10.00 k allocations: 1.068 MiB)
  0.000540 seconds (10.00 k allocations: 1.068 MiB)

と速くなります。
StaticArraysとは
https://github.com/JuliaArrays/StaticArrays.jl
これで、
長さ一定の配列に制限することで速度が上がります。
二種類があって、Sがつくものは長さ一定かつ値も一定です。Mがつくものは値は変化しても良いものです。
Mの方は

mutable struct TestBstm
    U::Array{MVector{4,Float64},1} 
    TestBstm(n) = new(Array{MVector{4,Float64},1}(undef,n))
end
testBstm = TestBstm(n)

println("loop1")
@time loop1(testBstm)
@time loop1(testBstm)
@time loop1(testBstm)

println("loop2!")
@time loop2!(testBstm)
@time loop2!(testBstm)
@time loop2!(testBstm)

ですが、これは

loop1
  0.099530 seconds (2.04 M allocations: 154.782 MiB, 21.14% gc time)
  0.124582 seconds (2.00 M allocations: 152.588 MiB, 37.75% gc time)
  0.094865 seconds (2.00 M allocations: 152.588 MiB, 29.09% gc time)
loop2!
  0.132371 seconds (2.03 M allocations: 154.112 MiB, 43.11% gc time)
  0.093484 seconds (2.00 M allocations: 152.588 MiB, 31.07% gc time)
  0.124293 seconds (2.00 M allocations: 152.588 MiB, 46.67% gc time)

速くなっていません。サイズが小さい配列だからかと思います。Sの方は物理では何らかの場の値を扱うときに使うと良さそうですね。

色々

発生する乱数を毎回同じにする

計算をデバッグするときとかに、毎回同じ乱数が出てくれると便利なことがあると思います。

using Random
Random.seed!(123)
testAkw = TestAkw()
println(testAkw)
testAkw = TestAkw()
println(testAkw)
testAkw = TestAkw()
println(testAkw)

seed!の中の値を変えると、出てくる乱数が変わります。ここを固定しておけば、何回実行しても同じ乱数が得られます。乱数列を固定している、ということですね。

エラーを起こしたところで止める

何かエラーが起きた時にメッセージを出して止めたいことがあると思います。
@assertはまだ知られていますが、errorはあまり知られていないと思うので紹介します。

a = -1
if a < 0
    println("error! a should be positive a = $a")
    exit()
end

とすると、

error! a should be positive a = -1

となり、aが負だとエラーが出て止まります。これが一番素朴な感じですね。

これは、

a = -1
@assert a < 0 "error! a should be positive a = $a"

ともできて、この場合は

error! a should be positive a = -1

となって止まります。

また、errorという関数を使うと、

a = -1
if a < 0
    error("error! a should be positive a = $a")
end

ERROR: LoadError: error! a should be positive a = -1
Stacktrace:

と止まります。errorを使う場合、どこの呼び出しで止まったかという情報が出てきてソースコードの番号が表示されるので、便利です。

Plotsの読み込みを高速化する。

PackageCompiler.jlでPlotsの呼び出しを高速化する2020年7月版
をやってみてください。

GoogleColabを使用

手元でJupyter NotebookでJuliaを実行して、ファイルを.ipynbで保存していたとします。
そのファイルをGoogle Colabでも実行してみたいことがありますよね?
そんなときは
Google ColabでJuliaを使う:元々あるノートブックを使う
こちらを参考にしてやってみてください。

データの入出力関係

出力あれこれ

printlnの使い方についてまとめます。
たとえば、

a = rand(3)
b = 4
println(a,"\t",b) #文字列を入れなくても変数でも書き出せるし、何個でも入れられる。\tはTab
println("$a $b") #このように$aなどとすると変数を文字列の中に入れられる
println("$a\t$b") #もちろんTabも入れられる

こんなことができます。

$を使えば、このように

println("$(2*a)\t$b")
println("$(2*a)\t$(sqrt(b))")

計算して出力することもできます。
もしたくさんある場合には、printlnではなく改行のないprintも使えます。このように、

c = rand(1:10,10)
for ci in c
 print(ci,"\t")
end

とすると、

4   3   3   6   8   7   1   3   4   10

こんな感じに1行に並べることも可能です。

もう少しシンプルにも書けて、

c = rand(1:10,10)
[print("$ci ") for ci in c]

でも可能です。

print_matrixなど

https://twitter.com/LirimyDh/status/1295310732435705856?s=20
で知ったのですが、以下のようなやり方が可能です。

    a = rand(4,4)
    println("空白")
    Base.print_matrix(stdout,a,""," ","\n")
    println("タブ")
    Base.print_matrix(stdout,a,"","\t","\n")
    println(",")
    Base.print_matrix(stdout,a,"",",","\n")

一つ目の値は出力先で、stdoutとすると画面に出力します。次が配列です。
残りの三つですが、一つ目は表示の頭に何か入れる場合です。ここでは空白にしています。
二つ目は値と値の間をどう区切るか、です。三つ目は最後の文字の後にどうするか、です。ここに\nを入れているのは、改行をしたいためです。
出力結果はこうなります。

空白
0.6768908174832444  0.3817345277763453 0.42646007837750055 0.17014559504201232
0.06283236014992255 0.8604594763575402 0.9500945643866037  0.7974495350151196
0.9238853594136449  0.6583229009292564 0.43140441711350563 0.8170287950442048
0.06778870328883024 0.4142144775490266 0.6645669648543755  0.334802102829435
タブ
0.6768908174832444  0.3817345277763453  0.42646007837750055 0.17014559504201232
0.06283236014992255 0.8604594763575402  0.9500945643866037  0.7974495350151196
0.9238853594136449  0.6583229009292564  0.43140441711350563 0.8170287950442048
0.06778870328883024 0.4142144775490266  0.6645669648543755  0.334802102829435
,
0.6768908174832444 ,0.3817345277763453,0.42646007837750055,0.17014559504201232
0.06283236014992255,0.8604594763575402,0.9500945643866037 ,0.7974495350151196
0.9238853594136449 ,0.6583229009292564,0.43140441711350563,0.8170287950442048
0.06778870328883024,0.4142144775490266,0.6645669648543755 ,0.334802102829435

Base.と毎回つけるのが面倒な場合には、

import Base:print_matrix

としておけば、print_matrixだけでいけます。

REPLと同じように出力してみたい

Juliaで立ち上げるとREPLモードになり、いろいろできます。この時、

julia> a = rand(4,4)
4×4 Array{Float64,2}:
 0.312639  0.317392   0.751933   0.651802
 0.520705  0.0688579  0.0148247  0.57883
 0.833153  0.542301   0.261739   0.933407
 0.225191  0.725512   0.0661146  0.512802

のようないい感じな出力をしてくれます。これをファイルを読み込んで実行するときにもやりたい場合には、

display.jl
a = rand(4,4)
display(a)

とします。型情報もついているので、デバッグで配列などを出力したいときに見やすいですね。

データの保存と読み込み

HDF5が便利です。
Juliaでのデータの保存と読み込み:HDF5を使ってみる

テキストデータ読み込みのテンプレート

テキストデータが

cat te.dat 
space 3 4.2 3.1 3.4
tab    7   4.9 3.4 3.3

だったとします。これを読み込むには、

filename = "te.dat"
data = readlines(filename) #データ読み込み
num = countlines(filename) #データのラインの数の確認
for i=1:num
  u = split(data[i]) #空白やtabを区切りとして読みこむ
  st = u[1] #文字なら
  println(st)
  it = parse(Int64,u[2]) #整数なら
  println(it)
  dt = parse(Float64,u[3]) #倍精度実数なら
  println(dt)
  dts = parse.(Float64,u[4:end]) #まとめて配列として読み込むなら
  println(dts)
end

などとします。

65
67
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
65
67