ujimushi(@旧sradjp(15364))の日記

旧スラドの日記の引越先です

Juliaのテキストファイル処理 [ボケ]

茶番(少しの間お遊びにおつきあい下さい)

4月26日金曜日の会社の夕方休憩時間中,いつもの通りZennとかQiitaのJulia情報を探していたところ, ふと一つの記事が目についた。

Juliaでテキストファイルを読み込みたい

その書き出しはこんな感じだった。

「Julia テキストファイル 読み込み」でググってもパッとする記事が引っかからないので ...

え゛っ

同じキーワードでググって,確かにreadline(s)とか使っているものが多いけどそこそこ ちゃんとしてるのが引っかかるじゃんとか思って…

で記事を読んで…

え゛っ

これがこの著者の「パッとする記事」なのか?

そこで私はふと考える。

これは日本のJuliaユーザーに対する侮辱だ。いや中小企業でパッとしない立場の 数学者でもないプログラマーでもない,なんちゃって一人研究部署のJuliaユーザーに対する 挑戦だ。

私は祝一平氏でもないしすごぶる甘等でもないので,誰の挑戦でも受ける訳ではないし, おごられるアップルパイも全ては食べられないが,この挑戦は受けるべきではないか?

挑戦を受けるためにはまず調査だ。「彼を知り己を知れば百戦殆からず」とは誰の言葉だったか。

家に帰ってJuliaユーザーご用達「二冊の鈍器」実践Julia入門Juliaプログラミング大全の中身をパラパラと開いてみる。 何と二冊合わせても「大全」の13.2ストリームぐらいしか言及がない。

ふむ

認めよう。確かに情報は少ない。

しかし,この記事を「パッとする記事」だとすると世界に対して日本のJuliaユーザーレベルに疑いをもたれてしまう。

それなら,低辺のJuliaユーザーであるこの私が「パッとする記事」を書くことで日本のJuliaユーザーの格は 相対的に飛躍的に上昇するだろう。

ならば書くしかない。この私が。

テキストファイル処理

だんだん飽きてきたので,この辺で普通の口調に戻して進めたいと思います。

まず,Juliaのopen関数の戻り値は次の通りIOStreamなので,変数はfileよりioの方が適切です。

julia> io = open("mozc_julia_latex_dict.txt", "r")
IOStream(<file mozc_julia_latex_dict.txt>)

元記事はPythonの影響かとも思ったのですが,

>>> file = open('mozc_julia_latex_dict.txt', 'r')
>>> type(file)
<class '_io.TextIOWrapper'>

とIOのラッパーだったりするので,確実なところだとC言語の影響でしょうか?

で,ここでテキストファイルの読み込み方は大きく三種類。ベタ,原理主義,最小限です。

ベタな方法は次の通り。

io = open("mozc_julia_latex_dict.txt", "r")
text = read(io, String)
close(io)
println(text)

「ベタ」というのは close忘れがあること,処理中の変数が全部外に流れるので 処理中変数の海にさらされることが多いためです。

また,クローズしたioが中ぶらりんになるのも今一つです。 ただ,Windows OSではファイルclose処理が遅く「あえて」全体の処理が終わるまでクローズしない という技等に使えます。

なお, zipfile.readerで開いたファイルがclose出来ない 等での回答でもあるように,Windowsではclose忘れが致命的になることが多いので, ちゃんとしたい時は次の「原理主義」を使う方がいいでしょう。

次は原理主義です。

text = open("mozc_julia_latex_dict.txt", "r") do io
    read(io, String)
end
println(text)

私が「原理主義」と呼ぶのは「close忘れがない」ためです。 また,do節の中で複雑な処理をして最終行にtextに入れる値を置けば 応用も効きやすいので基本はこれを使うべきです。

ただ,ネットの情報ではdo節の最終行を返り値として代入できることを知らないのか printlnで内容を表示している例が多いですが,基本は上のような形で使うべきです。 べき,べき,べき。だから「原理主義」。

次は「最小限」です。

text = read("mozc_julia_latex_dict.txt", String)
println(text)

元々,Julia言語を使っているのは「自分が書くコードを最小にできそう」「自分が書くコードを少なくするためだったら何でもする」という理由からなので,私は断然「最小限」を選びます。

第1引数が関数の関数(余談)

余談にはなるのですが, text = open("in.txt", "r") do io ... のような書き方ができるのは, open(f:Function, arg...; kwargs...)のように第1引数に関数を取れる関数がある時に記述できます。

ですので,

text = open(io -> read(io, String), "mozc_julia_latex_dict.txt", "r")

も「原理主義」のソースと同じ意味になります。

過去の日記の例を解説

実はテキスト処理についてはPlots.jlのGRバックエンド用フォントパッチ(Linux用)にさらっと使用例があります。

といっても元がテキストファイルではなく,コマンドの実行結果のテキスト情報を使っている違いがあります。 該当部分は次の通りです。

function gr_custom_fontlist_linux()
    コマンド出力 = pipeline(`fc-list`, `egrep "[.](ttf|ttc|otf)"`,`cut -d : -f 1`)
    読込(x) = read(x, String)
    終端改行削除(x) = chomp(x)
    行分割(x) = split(x, "\n")
    ファイル名抽出(x) = basename(x)
    fnames = コマンド出力 |> 読込 |> 終端改行削除 |> 行分割 .|> ファイル名抽出
    Dict([(chopsuffix(f, r"[.](ttf|ttc|otf)") , f) for f in fnames])
end

コマンド出力のところは,fc-listのテキスト出力の各行を : で区切って1列目を選択して出力しています。 ですので次の読込で読み込む文字列と同じになります。

読込のところの文字列は次のような感じです。

"""
/usr/share/fonts/truetype/lato/Lato-Medium.ttf
/usr/share/fonts/truetype/noto/NotoSansTelugu-CondensedThin.ttf
/usr/share/fonts/truetype/msttcorefonts/comicbd.ttf
/usr/share/fonts/truetype/noto/NotoSerifHebrew-CondensedLight.ttf
/usr/share/fonts/truetype/noto/NotoSans-SemiCondensedExtraLightItalic.ttf
/usr/share/fonts/truetype/noto/NotoSansGurmukhi-ExtraCondensedMedium.ttf
/usr/share/fonts/truetype/noto/NotoSerif-ExtraCondensedThinItalic.ttf
/usr/share/fonts/truetype/noto/NotoSerifKhmer-Medium.ttf
/usr/share/fonts/opentype/inter/InterDisplay-MediumItalic.otf
/usr/share/fonts/truetype/noto/NotoSansEthiopic-ExtraLight.ttf
/usr/share/fonts/truetype/noto/NotoSansArabic-CondensedThin.ttf
"""

行分割のところで次のようなパス文字列配列に変換します。

["/usr/share/fonts/truetype/lato/Lato-Medium.ttf",
 "/usr/share/fonts/truetype/noto/NotoSansTelugu-CondensedThin.ttf",
 "/usr/share/fonts/truetype/msttcorefonts/comicbd.ttf",
 "/usr/share/fonts/truetype/noto/NotoSerifHebrew-CondensedLight.ttf",
 "/usr/share/fonts/truetype/noto/NotoSans-SemiCondensedExtraLightItalic.ttf",
 "/usr/share/fonts/truetype/noto/NotoSansGurmukhi-ExtraCondensedMedium.ttf",
 "/usr/share/fonts/truetype/noto/NotoSerif-ExtraCondensedThinItalic.ttf",
 "/usr/share/fonts/truetype/noto/NotoSerifKhmer-Medium.ttf",
 "/usr/share/fonts/opentype/inter/InterDisplay-MediumItalic.otf",
 "/usr/share/fonts/truetype/noto/NotoSansEthiopic-ExtraLight.ttf",
 "/usr/share/fonts/truetype/noto/NotoSansArabic-CondensedThin.ttf",
 ...
 ]

ファイル名抽出のところでパス文字配列のファイル名文字列部分だけ抽出します。

["Lato-Medium.ttf",
 "NotoSansTelugu-CondensedThin.ttf",
 "comicbd.ttf",
 "NotoSerifHebrew-CondensedLight.ttf",
 "NotoSans-SemiCondensedExtraLightItalic.ttf",
 "NotoSansGurmukhi-ExtraCondensedMedium.ttf",
 "NotoSerif-ExtraCondensedThinItalic.ttf",
 "NotoSerifKhmer-Medium.ttf",
 "InterDisplay-MediumItalic.otf",
 "NotoSansEthiopic-ExtraLight.ttf",
 "NotoSansArabic-CondensedThin.ttf",
 ...
 ]

といった感じで処理を進めています。

限界...

とまぁこれぐらいが今日私が書ける限界になります。 最後までボケにつき合っていただいてありがとうございました。