Julia - キーボードから入力を受ける

環境

  • OS: ArchLinux
  • Julia 1.0.5

※ 2019/12/22 Base.prompt の例を追加

キーボードからの入力

Python3でいうところの input はJuliaではBase.promptを使います。

julia> Base.prompt("What's your name?")
What's your name?: Bob # Bob と入力して Enter を押した。末尾の : は勝手につきます。
"Bob"

何も入力せずに Enter を押すと、空文字 "" が、Ctrl-D を押すと nothing が返ってきます。

julia> Base.prompt("What's your name?")
What's your name?:
""

julia> Base.prompt("What's your name?")
What's your name?:
julia> ans == nothing
true

default を設定すると、何も入力せずに Enter が押された場合 default に設定したあと値が返ってきます。

julia> Base.prompt("Are you Japanese?", default="yes")
Are you Japanese? [yes]:
"yes"

prompt を出さずに入力を受ける場合は readline() を使います。(Base.prompt も内部的には readline() を使っています。)

readline(io::IO=stdin; keep::Bool=false)
julia> a = readline()
hoge # hoge を入力後 Enter
"hoge"

julia> a = readline()
hoge piyo
"hoge piyo"

julia> split(a) # 文字列を分割する。カンマ , 区切りのときは split(a, ",") とする
2-element Array{SubString{String},1}:
 "hoge"
 "piyo"

julia> a = readline()
1
"1"

julia> parse(Int, a) # 整数に変換
1

julia> parse(Float64, a) # 浮動小数点に変換
1.0

任意の個数の入力を受ける

特定の文字が入力されたら停止する方法

0 が入力されたら停止させる例

julia> x = []
0-element Array{Any,1}

julia> while true
           a = readline()
           a == "0" && break
           push!(x, a)
       end
hoge
piyo
fuga
0

julia> println(x)
Any["hoge", "piyo", "fuga"]

Ctrl-D で停止する方法

readline で入力待ちの状態で Ctrl-D (EOF) をすると空の文字列 "" が代入されるので、それを終了条件に使います。

julia> x = []
0-element Array{Any,1}

julia> while (a = readline()) != "" # Base.prompt を使う場合は nothing を終了条件に使う。
           push!(x, a)
       end
hoge
piyo
fuga
      # <- ここで Ctrl-D をしている

julia> println(x)
Any["hoge", "piyo", "fuga"]

Julia - Aizu Online Judge の問題を Julia で解く

Aizu Online Judge (AOJ) を Julia で遊ぶための野良パッケージを作ったので使い方を紹介します。

AOJ とは?

AOJ(Aizu Online Judge:onlinejudge.u-aizu.ac.jp/home)は誰でも無料で利用できるプログラミング問題のオンライン採点システムです。

第1回 オンラインジャッジシステムを知ろう|Tech Book Zone Manatee より

AOJ を使ってアルゴリズムとデータ構造について学ぶ本もあるので、プログラミングをしている人ならば一度は聞いたことがあると思います。

具体的な問題を解きながら楽しくプログラミングについて学べるので利用している方も多いことでしょう。

そんな楽しい AOJ なのですが残念ながら Julia には対応しておりません。対応してくれると大変嬉しいのですが、R にも対応していないので Julia が対応されることはおそらくないでしょう。

一応テストケースはダウンロードしてこられるので、テストケースを入力しては模範解答と比較するということをひたすらすれば、どんなプログラミング言語でも遊べると言えば遊べます。誰もやらないと思いますが。

もちろんそんな単純で面倒な作業は人間がやるべきではないので、自動的に採点するためのパッケージを作成しました。

github.com

一応言っておきますと、このパッケージは AOJ 非公式のパッケージです。 問題がありましたら AOJ でなく、私にご連絡ください。

このパッケージで出来ること・出来ないこと

  • ローカル環境の Julia を使って AOJ の問題を採点することができます。
  • AOJ が Julia に対応していないので提出などは出来ず、またどの問題が解けたかなどの記録を残すことは出来ません。

元々、私が個人的に遊ぶために作ったパッケージなので過度の期待は禁物です。

環境

対応する Julia のバージョン

  • Julia 0.7
  • Julia 1.0

OS は Linux, Mac, Windows のいずれでも動くはずです。

インストール

野良パッケージなので Julia を起動し、以下のコマンドを実行してください。

using Pkg
Pkg.pkg"add https://github.com/goropikari/AizuOnlineJudge.jl"

使い方

実際にこのパッケージを使って問題を解いている様子を動画にしてみました。 雰囲気はこんな感じです。

問題を選ぶ

ここでは例として「アルゴリズムとデータ構造入門」にある ALDS1_1_B 最大公約数 を解いていきます。

プログラムを作る

ALDS1_1_B 最大公約数gcd を実装する問題です。*1

問題文には出力の最後に改行を入れろという指示はありませんが、私のパッケージでは改行がないと正しく評価できないので最終行には改行を入れてください。

こんな感じに実装してファイルに保存します。

# gcd.jl
x, y = parse.(Int, split(readline()))
mygcd(x,y) = y == 0 ? x : mygcd(y, mod(x,y))
println(mygcd(x,y))

サンプルでテストする

実装ができたらまずはページ記載のサンプルでテストしてみましょう。

作ったファイルを julia gcd.jlinclude で取り込んで、自力でキーボードから入力例を入力するのでもよいですが、test_sample を使うと自動で全てのサンプルに対してテストできます。

test_sample の第一引数は解いている問題の ID、第二引数には作ったプログラムを入れてください。

using AizuOnlineJudge

test_sample("ALDS1_1_B", "gcd.jl")

問題の ID はページ上部に書いてあります。

f:id:goropikarikun:20181025142652p:plain

実行するとこんな感じです。 f:id:goropikarikun:20181025143332g:plain

正しい結果が得られると AC が出ます。

判定する

サンプルで問題なさそうなら judge を使って本格的に判定します。使い方は test_sample と一緒です。

judge("ALDS1_1_B", "gcd.jl")

正しい実装が出来ているとこんな感じ f:id:goropikarikun:20181025144623g:plain

間違えるとこんな感じ f:id:goropikarikun:20181025145003g:plain

その他に実行時にエラーが出ると RE、制限時間を超過すると TLE が出ます。デフォルトでは制限時間は3秒に設定しています。

メモリ制限には対応していないので ulimit 等を使って自分で制限を掛けてください。

テストケースやプログラムの出力結果は ~/.juliaAOJ に保存されています。 大した容量ではないですが消したい場合や判定が正しく動作しない場合は直接消すか、

AizuOnlineJudge.clean()

を実行してください。

制限時間を変える

制限時間を変えたい場合は第三引数に希望の制限時間を入れてください。単位は秒です。

judge("ALDS1_1_B", "gcd.jl", 5) # 制限時間を5秒に設定

注意点

解答の最終行では常に改行するようにしてください。そうしないと正しい評価が得られません。

既知の問題

仕様という名のバグ (私の技術不足に起因)

  • 浮動小数点数を含む問題では正しく評価されない
  • 解が一意に決まらない問題では正しく評価されない

このパッケージでは出力のを比較しているのではなく、出力を保存したファイルと模範解答のファイルのハッシュ値を比較して正誤を決めているため浮動小数点数を含む問題では正しく評価できません。
解が一意に定まらない問題も同様の理由で正しく評価できません。 そのため、正しいプログラムが書けていたとしても WA となることがあります。

この種の問題では判定結果を信用しないでください。

  • 実行時にエラーが起きているのに RE でなく TLE が出る

Julia のエラー処理は意外に時間が掛かるらしく制限時間を厳しく設定していると RE でなく TLE が出ることがあります。その場合は制限時間を伸ばしてください。

AOJに起因する問題

  • AOJ が提供しているテストケースが完全でない

出力結果が長い問題だと、AOJ からダウンロードできる出力結果自体が完全でない場合があるのでこれらの問題も正しく評価することはできません。

例えば、「プログラミング入門:トピック 3 テストケースの出力」など。

https://judgedat.u-aizu.ac.jp//testcases/ITP1_3_B/1

浮動小数点数を使用しておらず、プログラムを完璧に書いたはずなのに WA が出る時は、~/.juliaAOJ にあるテストケースを直接確認してみてください。

実行時間が掛かり過ぎじゃないの?

内部的には判定するプログラムを run(`julia gcd.jl`) で実行しているために各テストケース毎に Julia の起動時間 + コンパイル時間が上乗せされ実行時間が長くなっています。

現状は Julia という言語の仕様上これらの無駄に掛かる時間は回避できない (と思う) ので我慢してください。 回避方法があれば是非ご連絡ください。

終わりに

何か不具合が有りましたら GitHub に Issue を立ててください。

また、このパッケージを作成する時に他の既存の採点システムを参考にしようと思っていたのですが、慣れない言語で結局何書いてあるのか理解できず。。。 結局勢いで実装したため変な処理や無駄な部分が含まれている可能性が多分にあります。そういった報告もしていただけると泣いて喜びます。

特に結果の判定をハッシュ値で判断するのが一番簡単そうだったのでそうしましたが、もっといい方法があればぜひ教えてください。

github.com

デバッグがてら 「プログラミング入門」 のうち浮動小数点数を使わない問題は全問解いたので、どうしても WA になってしまう場合は参考にしてください。(プログラミング入門コースだから自力で実装するべきか、はたまた言語仕様として使える関数はどんどん使っていくべきか、どちらの方が教育的なのかと自問しながら解いていたので迷走したコードになっています。)

github.com

*1:Julia では標準で gcd 使えますけどね

Julia - @enum で割り当てられた番号から変数名を知る

環境

Julia 1.0.1

string(EnumName(数字)) とするとその数字を割り当てられた変数の名を文字列で取得することができる。

julia> @enum Fruit apple orange kiwi

julia> apple
apple::Fruit = 0

julia> orange
orange::Fruit = 1

julia> Int(apple)
0

julia> Int(orange)
1

julia> Fruit(0)
apple::Fruit = 0

julia> Fruit(1)
orange::Fruit = 1

julia> string(Fruit(0))
"apple"

julia> string(Fruit(1))
"orange"

Julia - 標準入力・出力・エラー出力先を変える

環境

  • OS: ArchLinux
  • Julia 1.0.1

入出力先を変える

Python だと stdout を気軽に上書きして標準出力先をファイルにできますが、Julia の場合はそれができません。

julia> f = open("out.txt", "w")
IOStream(<file out.txt>)

julia> stdout = f
ERROR: cannot assign variable Base.stdout from module Main
Stacktrace:
 [1] top-level scope at none:0

標準出力先を変えたい時は redirect_stdout を使って変更します。

julia> originalstdout = stdout # 元の設定を避難しておく
Base.TTY(RawFD(0x0000000d) open, 0 bytes waiting)

julia> stdout
Base.TTY(RawFD(0x0000000d) open, 0 bytes waiting)

julia> (rd, wr) = redirect_stdout()
(Base.PipeEndpoint(RawFD(0x00000013) open, 0 bytes waiting), Base.PipeEndpoint(RawFD(0x00000014) open, 0 bytes waiting))

julia> stdout # 指しているところが wr と同じところになった。
Base.PipeEndpoint(RawFD(0x00000014) open, 0 bytes waiting)

julia> println("Hello") # print を書いても画面には表示されない

julia> println("World")

julia> close(wr)

julia> data = readavailable(rd) # バッファからデータを取ってくる
12-element Array{UInt8,1}:
 0x48
 0x65
 0x6c
 0x6c
 0x6f
 0x0a
 0x57
 0x6f
 0x72
 0x6c
 0x64
 0x0a

julia> close(rd)

julia> redirect_stdout(originalstdout) # 標準出力を元に戻す
Base.TTY(RawFD(0x0000000d) open, 0 bytes waiting)

julia> print(String(data))
Hello
World

標準出力先をファイルに変えることも出来ます。

julia> originalstdout = stdout
Base.TTY(RawFD(0x0000000d) open, 0 bytes waiting)

julia> f = open("out.txt", "w")
IOStream(<file out.txt>)

julia> redirect_stdout(f)
IOStream(<file out.txt>)

julia> println("Hello World")

julia> close(f)

julia> redirect_stdout(originalstdout)
Base.TTY(RawFD(0x0000000d) open, 0 bytes waiting)

shell> cat out.txt
Hello World

標準入力、標準エラー出力についても同じ様に出来ます。

標準入力

shell> cat in.txt
2 4

julia> originalstdin = stdin
Base.TTY(RawFD(0x0000000a) paused, 0 bytes waiting)

julia> f = open("in.txt", "r")
IOStream(<file in.txt>)

julia> redirect_stdin(f)
IOStream(<file in.txt>)

julia> n, m = parse.(Int, split(readline())) # キーボードから入力せずとも勝手にファイルから読み込んでくる
2-element Array{Int64,1}:
 2
 4

julia> println(n * m)
8

julia> close(f)

julia> redirect_stdin(originalstdin)
Base.TTY(RawFD(0x0000000a) paused, 0 bytes waiting)

標準エラー

julia> originalstderr = stderr
Base.TTY(RawFD(0x0000000f) open, 0 bytes waiting)

julia> f = open("out.txt", "w")
IOStream(<file out.txt>)

julia> redirect_stderr(f)
IOStream(<file out.txt>)

julia> println(stderr, "STDERR")

julia> close(f)

julia> redirect_stderr(originalstderr)
Base.TTY(RawFD(0x0000000f) open, 0 bytes waiting)

shell> cat out.txt
STDERR

Julia のソースコードを見る限り、元の標準出力等に戻すには上記の originalstdout のように自力で避難しておくしか方法はなさそうです。ちょっと不便。

使いどころ(?)

SHELL で

$ julia ex.jl < in.txt > out.txt

として動かしていたプログラムを run を使わずに Julia から実行できるようになります。

in.txt

2 4

gcd.jl

x, y = parse.(Int, split(readline()))
mygcd(x,y) = y == 0 ? x : mygcd(y, mod(x,y))
println(mygcd(x,y))

プログラムを実行するための関数

function run_file(input_filename::String, output_filename::String, filename::String)
    originalstdin = stdin
    originalstdout = stdout
    fin = open(input_filename, "r")
    redirect_stdin(fin)
    fout = open(output_filename, "w")
    redirect_stdout(fout)

    include(filename)

    close(fin)
    close(fout)
    redirect_stdout(originalstdout)
    redirect_stdin(originalstdin)
end
julia> @time run(pipeline(`julia gcd.jl`, stdin="in.txt", stdout="out.txt"));
  0.666613 seconds (394.68 k allocations: 20.278 MiB)

julia> @time run(pipeline(`julia gcd.jl`, stdin="in.txt", stdout="out.txt"));
  0.406997 seconds (65 allocations: 2.656 KiB)

julia> @time run(pipeline(`julia gcd.jl`, stdin="in.txt", stdout="out.txt"));
  0.413124 seconds (65 allocations: 2.656 KiB)

julia> @time run_file("in.txt", "out.txt", "gcd.jl");
  0.239453 seconds (392.67 k allocations: 20.973 MiB)

julia> @time run_file("in.txt", "out.txt", "gcd.jl");
  0.031852 seconds (18.40 k allocations: 972.514 KiB, 20.19% gc time)

julia> @time run_file("in.txt", "out.txt", "gcd.jl");
  0.026645 seconds (18.40 k allocations: 972.482 KiB)

run を使った方法よりも実行時間は大分改善されます。(メモリは大分消費するようですが。)

競技プログラミングでの Julia の実行時間の計測方法の改善に応用できないかなと思ったのですが、システムを構築するのがとても面倒そうだなと思いました。

参考

Julia - VSCode で Julia 1.0 を使う (仮)

julia-vscode v0.11.0 から Julia 1.0 に対応したのでこの記事を読む必要はありません。master ブランチのものを入れたい場合は手順だけ参考になるかもしれません。(2019/1/29)

現状、VSCode の Marketplace から入れることの出来る Julia の拡張機能を使うと、Julia 1.0 では REPL を起動することができません。

Julia 拡張の Wiki には一応 Julia 1.0 でのやり方も書いてあるのですが、初見では私にはさっぱりわかりませんでした*1。 この点に関しては調べたらすぐに解決したのでよいのですが、Plot Pane にプロットを表示させる方法はソースコードを読まないと解決できないと思うので記録として残しておきます。*2

github.com

Julia 拡張の README に書いてある以下の機能の内 syntax highlighting, snippets, integrated julia REPL の3つは使えました。*3

  • syntax highlighting
  • snippets
  • latex snippets
  • julia specific commands
  • integrated julia REPL
  • code completion
  • hover help
  • a linter
  • code navigation
  • tasks for running tests, builds, benchmarks and build documentation

環境

  • OS: ArchLinux
  • Julia 1.0.1
  • npm 6.4.1
  • typescript 3.1.1
  • VSCode 1.28.1

julia extension を入れる

Wiki には julia-vscode を開発している方のリポジトリを使うようにとありますが、 現在の julia-vscode の master ブランチにはその方の PR がマージされているので本家の master を使います。 すでに過去の Julia 拡張をインストールしてある場合はそれを消去してください。

sudo pacman -S npm typescript # Ubuntu の場合は sudo apt install npm node-typescript
cd ~/.vscode/extensions
git clone https://github.com/JuliaEditorSupport/julia-vscode
cd julia-vscode
npm run update2latest

これだけで REPL は使えるようになると思います。適当な Julia のファイルを開いて Ctrl-Enter をすれば REPL 上で実行されるはずです。

Plots.jl での描写を Plot Pane に出力する

私の環境では何も考えずに Julia 1.0 で

using Plots
plot(sin)

とすると、backend に PyPlot を指定した場合は、

/usr/lib/python3.7/site-packages/matplotlib/figure.py:457: UserWarning: matplotlib is currently using a non-GUI backend, so cannot show the figure
  "matplotlib is currently using a non-GUI backend, "

と出るだけでプロットは表示されず、GR を使った場合は別枠のウィンドウが開きました。

Julia 0.7 だとちゃんと Plot Pane にプロットされるんですけどね。不思議です。

using Plots
popdisplay()
plot(sin)

とするとちゃんと Plot Pane に表示されます。

f:id:goropikarikun:20181013175713p:plain

何故 popdisplay() すると上手くいくのか気になる人は Plots.jl と Julia 本体のソースコードを読んでください。

参考

*1:私は JavaScript 使ったことありません

*2:issue 出したので現在はすぐに解決策見つかりますけど。

*3:私は VSCode 使ったことがなく、設定の仕方等は一切わからないので単純に設定を変えれば動く可能性はあります。