この記事は Qiita に 2018/08/04 に投稿したものです。2020/3/26 移行しました。
目次
print
や類似の関数の基本的な使い方から、一緒に使うと便利な関数について紹介したいと思います。
今回はREPLの表示をそれなりにきれいにすることを目標とします。
人の美への追求は際限なく 「IJulia 使えば Markdown も使えるからキレイに出力できる!」と考えるかもしれませんが、掛けた労力に対して得られる成果があまりに微々たるものなので今回は扱いません。1つの数字を MathJax で表示するくらいなら良いのですが配列が絡んでくると地獄です。
例えば "量子力学と言えば Dirac 表記でしょ!" といった軽いノリで 下記のように表示されていたものを
0.7071067811865475 0.0 0.0 0.7071067811865475
MathJaxを使って下記のように表示させようとするのはおすすめしません。地獄を見ます。 $$0.707 | 00 \rangle + 0.707 | 11 \rangle$$
たかが1行の出力のために何故600行以上もコードを書いているんだと虚無感が襲ってきます。
できるようになること
今まで↓のように表示させていたものが
Name Age Point Carmen.Blanco 21 11.7303 Corrales.Cristobal 21 1.5121000000000002 Wendolin.Urías 30 1.1905999999999999 Nazario.Roberto 43 2.7626 zSantacruz 47 6.9193999999999996 Sisneros.Emilio 30 2.5084 Delia.Galarza 31 19.1949 Collado.Tomás 43 10.206900000000001 Abraham80 50 7.512499999999999 wAlonso 24 4.7661
↓のように表示させることができるようになります。
Name Age Point Carmen.Blanco 21 11.7303 Corrales.Cristobal 21 1.5121 Wendolin.Urías 30 1.1906 Nazario.Roberto 43 2.7626 zSantacruz 47 6.9194 Sisneros.Emilio 30 2.5084 Delia.Galarza 31 19.1949 Collado.Tomás 43 10.2069 Abraham80 50 7.5125 wAlonso 24 4.7661
自作の型の表示が↓から
Horse("ハリボテエレジー", "手作好太郎", "ダンボウルガクエン", "ガムテイプマツリ", 6, [8, 8, 8, 8, 8, 8, 8, 8, 8, 8 … 8, 8, 8, 8, 8, 8, 8, 8, 8, 8])
↓になります。
Horse 競走馬名: ハリボテエレジー 騎手 : 手作好太郎 父馬 : ダンボウルガクエン 母馬 : ガムテイプマツリ 馬齢 : 6 着順 8 8 8 ⋮ 8 8
一見すると簡単に出来そうですが、この記事の長さが物語っているようにこの程度の出力を得るだけでもすごーく面倒です。
環境
julia> versioninfo() Julia Version 1.0.0 Commit 5d4eaca0c9 (2018-08-08 20:58 UTC) Platform Info: OS: Linux (x86_64-pc-linux-gnu) CPU: Intel(R) Core(TM) i5-4460T CPU @ 1.90GHz WORD_SIZE: 64 LIBM: libopenlibm LLVM: libLLVM-6.0.0 (ORCJIT, haswell) Environment: JULIA_SHELL = /usr/bin/zsh JULIA_EDITOR = nvim
今回使うJulia は安定版の v1.0 です。Julia v0.6 とは文法がほとんど違うので注意!
基本
他のプログラミング言語よろしく、print
を使うと文字列や変数を画面に出力することができます。
ここで println
を使うと最後に改行が入ります。
julia> print("Hello World") Hello World julia> println("Hello World"); println("Hello goropikari") Hello World Hello goropikari
各変数をカンマ区切りで渡すと連結されて出力されます。
julia> print("Hello", 1, "World") Hello1World
print
と似たもので show
もありますが、こちらは print
よりも変数について時として詳しく表示されます。
julia> x = 10 10 julia> println(x) 10 julia> show(x) 10 julia> x = Float16(1) Float16(1.0) julia> println(x) 1.0 julia> show(x) Float16(1.0)
print
と show
はちょいちょい表示のされ方が違うので要注意。
julia> print("Hello\n") Hello julia> show("Hello\n") "Hello\n"
出力先を明示的に指定しなかった場合、結果は標準出力に出力されます。出力先を指定したい場合は print
の第一引数に出力先を指定します。
# 標準出力 julia> println(stdout, "Hello World") Hello World # 標準エラー出力 julia> println(stderr, "Hello World") Hello World
JuliaにはC言語スタイルで print するためのマクロ @printf
が用意されています。
これを使うとC言語と同じ様に print することができます。
julia> using Printf julia> @printf("Hello%010d", 12345) Hello0000012345
print
や @printf
に似たものに sprint
と @sprintf
があります。
違いは何かと言えば頭に s がついているほうは print したものを画面に表示するのではなく文字列として返します。
julia> using Printf julia> @sprintf("Hello%04d", 3) "Hello0003" julia> @sprintf("Hello%04d", 3); # 標準出力に出しているわけではないので # ; をつけると何も表示されない
@sprintf
の方は比較的直感的です。一方で sprint
の方はどうでしょう?
julia> sprint("Hello") ERROR: MethodError: no method matching sprint(::String) Closest candidates are: sprint(::Function, ::Any...; context, sizehint) at strings/io.jl:97 Stacktrace: [1] top-level scope at none:0
ありゃま、エラーが出ました。@sprintf
と違い sprint
には第一引数に関数を与えなければなりません。また、与える関数は第一引数に IO
を受け取れるようなものに限ります。例えば print
や show
など。
julia> sprint(print, "Hello") "Hello" julia> sprint(print, 12345) "12345"
IOContext
凝った出力をするため、また、出力結果を期待通りにするために IOContext
の理解は避けては通れないのでここで紹介します。
IOContext
は簡単に言えばどこにどう出力するかをまとめたものです。
"どこ" には標準出力や標準エラー出力、書き込み可能なファイルなど。 "どう" はコンパクトに表示する、省略して表示する、色をつけて表示するなどなどです。
基本文法は
IOContext(io::IO, key1 => value1, key2 => value2, ...)
です。ここで key の部分は自分で決めることができますが、以下のよく使われているものに関しては同じ名前をつけると不具合が出る可能性があるので他の名前をつけましょう。
- :compact 値をコンパクトに表示するか否か
- :limit 表示を省略するか否か。Juliaでは配列は省略されて表示されますがそれはこれが
true
になっているから。 - :displaysize 表示部分のサイズ。
- :color 出力の文字に色をつけるか否か
- :typeinfo 私は使ったことがないのでよくわかりません。すいません。
show
を使ったときには表示が変わるなぁということだけ確認しました。
julia> show(Float16(1)) Float16(1.0) julia> show(IOContext(stdout, :typeinfo => Float16), Float16(1)) 1.0
例えば、コンパクトに表示させたときとそうでないときの差は以下のとおりです。
julia> using Random; Random.seed!(2018); x = rand() 0.6545394330653942 julia> io = IOContext(stdout, :compact => true) IOContext(Base.TTY(RawFD(0x0000000d) open, 0 bytes waiting)) julia> println(io, x) 0.654539 julia> io = IOContext(stdout, :compact => false) IOContext(Base.TTY(RawFD(0x0000000d) open, 0 bytes waiting)) julia> println(io, x) 0.6545394330653942
IO
が特定の key を持っているか否かを調べる時は haskey
を使います。
key を持っていたらその値を、持ってなかったら指定したの値を返すようにするには get
を使います。
julia> io = IOContext(stdout, :compact => false) IOContext(Base.TTY(RawFD(0x0000000d) open, 0 bytes waiting)) julia> haskey(io, :compact) true julia> haskey(io, :hoge) false julia> get(io, :compact, true) false julia> get(io, :hoge, true) true
普段遣いのときには IOContext
はあまり使わないと思いますが、自分のパッケージを作って後述の pretty-print をするときは結構重要になってきます。
特に変数の値でなく、表示のされ方をテストしたいときは IOContext
を知らないと、ちゃんとしたテストケースが書けません。
出力をきれいに魅せる
カラフルにする
printstyled
を使うと色付きだったりボールド体で出力することができます。
色の指定方法は色の名前を指定するか、0〜255までの整数値で決めることができます。
julia> printstyled("Hello\n", color=:cyan) julia> printstyled("Hello\n", color=:reverse) # 反転 julia> printstyled("Hello\n", color=:underline) # アンダーラインを引く julia> printstyled("Hello\n", color=:light_blue, bold=true) # Julia v0.5 を使ったことがある人には懐かしのスタイル
文字幅
数字を普通に出力すると以下の様に左寄せに出力されますが、時として位の位置を揃えて出力したいと思いますよね。
julia> for i in [1, 10, 100, 1000] println(i) end 1 10 100 1000
このように。
1 10 100 1000
第一の方法は @printf
を使う方法です。
julia> for i in [1, 10, 100, 1000] @printf("%4d\n", i) end 1 10 100 1000
しかし私はC言語風の書き方に慣れていないので @printf
は使いづらいです。
変数の数が増えてくるとどこがどこに対応しているのかパット見だとわからないので好きになれません。そのため私は lpad
を使っています。
julia> for i in [1, 10, 100, 1000] println(lpad(i, 4)) end 1 10 100 1000
lpad
は指定した文字数 $n$ よりも文字列 $s$ が短かったら、足りない分だけ $s$ の左に空白を入れた文字列 $s'$ を返します。右に空白を入れる場合は rpad
を使います。
第3引数も指定すると空白以外の文字で埋めることができます。0埋めの連番ファイルを作りたいときなどにも便利です。
julia> lpad("Hello", 10) " Hello" julia> lpad("Hello", 10, "-") "-----Hello" julia> lpad(1, 10, "hoge") "hogehogeh1" julia> rpad("Hello", 20) "Hello " julia> rpad("Hello", 20, "-") "Hello---------------" julia> lpad(1, 6, "0") "000001"
小数・複素数の表示をきれいにする Base.alignment
表示させたいものが整数ならば lpad
を使えば位を揃えて表示することが出来ました。次は小数や複素数をきれいに表示させてみましょう。
配列を作った時、小数だったら位の位置を揃えて、複素数だったら実部と虚部を結ぶ符号の位置が揃って表示されますが、それを自力で実現するための方法を紹介します。
julia> x = randn(3) * 100 3-element Array{Float64,1}: -99.60800864461076 28.202047839771822 -158.385848352147 julia> randn(ComplexF64, 3) * 100 3-element Array{Complex{Float64},1}: -11.13643420355874 + 21.75267947789508im 41.913378452353996 - 56.76768974241606im 83.73402073531838 + 0.15488964757358356im
完全に自力でやろうとすると整数部が何桁で小数部が何桁、実部が何桁で虚部が何桁・・・とやらないといけないわけですが、幸いそれらをやってくれる関数があります。それが Base.alignment
です。
ちなみに Base.alignment
はマニアックな関数なので Julia の公式ドキュメントに使い方は書いてありません。あまりに誰も使わないせいか docstring も2年近く間違ったまま放置されています。それくらいマニアックです。
Base.alignment
の基本文法
Base.alignment(io::IO, x)
Base.alignment
に IO
と数字を入れると要素数2のタプルが返ってきます。もし入れた数字が小数ならば第1要素は整数部の桁数、第2要素は点を含めた小数部の桁数になります。
print
や show
と違い、 Base.alignment
の場合は IO
を省略できません。何故かと言えば context (compact とか) によって同じ数字でも表示される桁数が違うからです。
julia> x = rand() 0.6022015583915965 julia> println(IOContext(stdout, :compact => true), x) 0.602202 julia> io = IOContext(stdout, :compact => true) IOContext(Base.TTY(RawFD(0x0000000d) open, 0 bytes waiting)) julia> Base.alignment(io, x) (1, 7) julia> println(IOContext(stdout, :compact => false), x) 0.6022015583915965 julia> io = IOContext(stdout, :compact => false) IOContext(Base.TTY(RawFD(0x0000000d) open, 0 bytes waiting)) julia> Base.alignment(io, x) (1, 17)
それでは、実際に Base.alignment
を使って小数をきれいに表示させてみましょう。
方針は配列の要素全てについて Base.alignment
で調べ、整数部の桁数の最大値と小数部の桁数の最大値の和を文字幅とするように表示させます。整数部、小数部の足りない分の桁は repeat
をつかって空白で埋めます。
julia> x = randn(5) * 10 5-element Array{Float64,1}: -9.212777882398562 -0.6891109738516901 -2.6882962369764787 -36.9082211306946 4.914965667105987 julia> l = r = 0 0 julia> a= [] 0-element Array{Any,1} julia> io = IOContext(stdout, :compact => true) IOContext(Base.TTY(RawFD(0x0000000d) open, 0 bytes waiting)) julia> for i in x atmp = Base.alignment(io, i) global l = max(l, atmp[1]) global r = max(r, atmp[2]) push!(a, atmp) end julia> for (al, item) in zip(a, x) ls = repeat(" ", l - al[1]) rs = repeat(" ", r - al[2]) println(io, ls, item, rs) end -9.21278 -0.689111 -2.6883 -36.9082 4.91497
1つ目の例を再現
ここまでで冒頭の1つ目の例を再現することができます。
julia> # using Pkg; Pkg.add("Faker") julia> using Faker, Random julia> Random.seed!(0); julia> un = Faker.user_name user_name (generic function with 1 method) julia> age() = rand(20:50) age (generic function with 1 method) julia> point() = round(abs(randn()), digits=5) point (generic function with 1 method) julia> n = 10 10 julia> data = Matrix{Any}(undef,n,3); julia> for i in 1:n data[i,1] = un() data[i,2] = age() data[i,3] = point() * 10 end julia> begin name = "Name" a = "Age" p = "Point" io = IOContext(stdout, :compact => true) wn = max(length(name), maximum(length.(data[:,1]))) + 5 wa = 7 l = r = 0 for i in 1:n aij = Base.alignment(io, data[i,3]) global l = max(l, aij[1]) global r = max(r, aij[2]) end println( rpad(name, wn) * rpad(a, wa) * rpad(p, max(10,l+r))) for i in 1:10 aij = Base.alignment(io, data[i,3]) ls = repeat(" ", l - aij[1]) rs = repeat(" ", r - aij[2]) println(io, rpad(data[i,1], wn), rpad(data[i,2], wa), ls, data[i,3], rs) end end Name Age Point Valladares.Cynthia 43 4.7313 Rolando20 49 1.5648 Gabino16 45 4.4951 Gabino.Cepeda 25 9.6989 Isabela.Pulido 35 13.5565 Eloy25 43 25.1382 KGuillen 35 5.1041 Sonia54 39 18.9942 Anabel.Mesa 32 28.9205 nGracia 50 6.3489
配列 Base.print_array
続いて配列の表示についてです。
配列を print してみた時、「思ってたのと違う」と感じたことはないでしょうか?
julia> x = rand(5) 5-element Array{Float64,1}: 0.8564231118379442 0.3957938354126007 0.0803914002136299 0.919405100907382 0.3769282818406279 julia> println(x) [0.856423, 0.395794, 0.0803914, 0.919405, 0.376928] julia> y = rand(5,5) 5×5 Array{Float64,2}: 0.528446 0.948737 0.996994 0.862299 0.675343 0.633248 0.637487 0.34968 0.767471 0.960373 0.868802 0.253233 0.235227 0.709841 0.134395 0.587811 0.637926 0.33784 0.455068 0.180991 0.912758 0.550064 0.916355 0.58957 0.386093 julia> println(y) [0.528446 0.948737 0.996994 0.862299 0.675343; 0.633248 0.637487 0.34968 0.767471 0.960373; 0.868802 0.253233 0.235227 0.709841 0.134395; 0.587811 0.637926 0.33784 0.455068 0.180991; 0.912758 0.550064 0.916355 0.58957 0.386093]
変数を定義した時に表示される方法で出力してほしいのであって、一行にずらずら要素を出力してほしいわけじゃないんだ!!!と思ったことはないでしょうか?
そんな時は show
を使います。しかし、そのまま使うと print
と大差ありません。 IO
と MIME
を指定すれば望みの結果が得られます。
REPL のときの MIME は "text/plain" と覚えとけば、とりあえず良いと思います。
julia> println(x) [0.856423, 0.395794, 0.0803914, 0.919405, 0.376928] julia> show(x) [0.856423, 0.395794, 0.0803914, 0.919405, 0.376928] julia> io = IOContext(stdout, :limit => true, :compact => false) IOContext(Base.TTY(RawFD(0x0000000d) open, 0 bytes waiting)) julia> show(io, "text/plain", x) 5-element Array{Float64,1}: 0.8564231118379442 0.3957938354126007 0.0803914002136299 0.919405100907382 0.3769282818406279
さてさて、無事に望みの結果を得ることが出来ましたが、人によってはヘッダー部( 5-element Array{Float64,1}:
など)を表示させたくないと望むことでしょう。そんなときは Base.print_array
を使います。
ちなみに Base.alignment
同様、Base.print_array
も公式ドキュメントには使い方が載っていないマニアックな関数です。ただ、alignment
と比べて実用性高いのでドキュメントに載せて良いのではと個人的には思います。
Base.print_array
の基本文法
Base.print_array(io::IO, x)
julia> io = IOContext(stdout, :limit => true, :compact => false) IOContext(Base.TTY(RawFD(0x0000000d) open, 0 bytes waiting)) julia> Base.print_array(io, x) 0.8564231118379442 0.3957938354126007 0.0803914002136299 0.919405100907382 0.3769282818406279 julia> Base.print_array(io, rand(30)) 0.826103394472415 0.12252942018471757 0.2578897738095447 0.9962502442037657 0.7355190804809946 0.7585607760377631 0.9151773834286789 0.3929985249254522 0.6641753306579214 0.33175433327494197 ⋮ 0.7833300985202769 0.18328052862651867 0.23765181941856928 0.023377483952839784 0.09753998271209197 0.5774230030542189 0.8859464218457049 0.6723385022029065 0.47453039665141294 julia> io = IOContext(stdout, :limit => false, :compact => false) IOContext(Base.TTY(RawFD(0x0000000d) open, 0 bytes waiting)) julia> Base.print_array(io, rand(30)) 0.7545909477271988 0.04426425050940841 0.8374874234291312 0.1487924398894196 0.9042352827314581 0.6163873731935219 0.8876188540406658 0.941831168132194 0.5473667368995183 0.06389212057729132 0.31826618585326116 0.5844167820961697 0.2901193343171964 0.04053208788398743 0.12097218515312957 0.008122564658366693 0.26497805735815083 0.6564738355733903 0.058178777741022536 0.3444346924786441 0.3245624908138902 0.8014413904453013 0.9545200342276534 0.154475460973849 0.7799280363802161 0.5400078688551904 0.8594465295979896 0.881281761468725 0.8220828940691287 0.9481617822907571
~~ここでは扱いませんが Markdown で配列を表示させたい人は Base.print_matrix_row
の使い方を覚えると良いかもしれません。*1
pretty-print
最後に pretty-print です。pretty-printの正確な定義がよくわかりませんが、垢抜けない野暮ったい出力をプリティーにすることでしょう(多分)。
下図の「ここ」とくくった部分をなんて呼ぶのか、または、「ここ」の部分を表示させる機能をなんて呼ぶのかわからないので説明しづらいのですが、「ここ」とくくった部分が何故このように表示されるかといったら、それはもちろんそのように定義されているからです。
そしてこの「ここ」の部分の表示のさせ方は型によって特徴付けられています。
この「ここ」の部分の表示方法を変えたい場合は Base.show
のメソッドを上書きすることで実現できます。
基本的な書き方
function Base.show(io::IO, ::MIME"text/plain", x::MyType) # MyType の部分を好みの型に変える println(io, "A is ", x.a) # print に io を入れることを忘れないように! println(io, "B is ", x.b) ... end
2つ目の例を再現
新たに作った Horse 型の表示方法を定義してみます。
julia> struct Horse name::String jockey::String sire::String dam::String age::Int finpos::Vector{Int} end julia> x = Horse("ハリボテエレジー", "手作好太郎", "ダンボウルガクエン", "ガムテイプマツリ", 6, fill(8,100)) Horse("ハリボテエレジー", "手作好太郎", "ダンボウルガクエン", "ガムテイプマツリ", 6, [8, 8, 8, 8, 8, 8, 8, 8, 8, 8 … 8, 8, 8, 8, 8, 8, 8, 8, 8, 8]) julia> function Base.show(io::IO, ::MIME"text/plain", x::Horse) summary(io, x) println(io) println(io, " 競走馬名: ", x.name) println(io, " 騎手 : ", x.jockey) println(io, " 父馬 : ", x.sire) println(io, " 母馬 : ", x.dam) println(io, " 馬齢 : ", x.age) println(io, " 着順") Base.print_array(io, x.finpos) end julia> x Horse 競走馬名: ハリボテエレジー 騎手 : 手作好太郎 父馬 : ダンボウルガクエン 母馬 : ガムテイプマツリ 馬齢 : 6 着順 8 8 8 8 8 8 8 8 8 8 ⋮ 8 8 8 8 8 8 8 8 8
MIME を "text/markdown" で定義すると、 Jupyter Notebook を使っている場合 "text/markdown" で定義した方法が優先されます。 私がやるとしたら下記のように配列を含んでいなかったら Markdown への対応を考えるかもしれません。
function Base.show(io::IO, ::MIME"text/markdown", x::Number) println(io, "\$", x, "\$") end rand()
配列を含んでいるとどう面倒なのかは下記の例を見ていただくと納得いただけるのではないかと思います。以下は先程の例を "text/markdown"
に変えただけです。
function Base.show(io::IO, ::MIME"text/markdown", x::Horse) summary(io, x) println(io) println(io, " 競走馬名: ", x.name) println(io, " 騎手 : ", x.jockey) println(io, " 父馬 : ", x.sire) println(io, " 母馬 : ", x.dam) println(io, " 馬齢 : ", x.age) println(io, " 着順") Base.print_array(io, x.finpos) end x
まとめ
- 凝りだすと際限がないので程々のところでやめましょう。
- print を凝りたい場合、公式ドキュメントは非力なので Julia のソースコードを読んでください。