第12回:テキストファイルの入出力

▶ テキストファイルの作成・書き込み

この節では,テキストファイルを作成し, テキストを書き込む方法を説明する. 一般のテキストファイルの拡張子は,txt である.

テキストファイルを作成するには,関数 openclose を組で用いる.

関数 open( fn, "w") は, ファイル名 fn のファイルを作成し, テキストを書き込む準備を行う. ファイル fn は,存在していてもよい.その場合は,元に書き込まれた内容は廃棄される.ファイルが作成できない場合には例外が発生する.

関数 open は,ファイル記述子 f を返す. 関数 print(f, s) は,ファイル記述子 f で指定されたファイルに文字列 s を書き込む. 関数 println(f, s) は,ファイル記述子 f で指定されたファイルに文字列 s を書き込んだ後に開業する.

書き込みが終わったら,関数 close(f) を呼び出し,書き込みを終了する.

f = open("hello1.txt", "w")
print(f, "Hello")
println(f, " again.")
close(f)

上のプログラムを実行したら,Windows のエクスプローラを用いて,ファイル hello1.txt が作成されることを確かめよ. Windowsの「メモ帳」アプリなどを用いて hello1.txt を開き,その通りのテキストが書き込まれていることを観察・確認せよ. (※ MacOSの場合は,「ファインダー」アプリと,「テキスト・エディット」アプリに読み替えよ)

上と同じ処理は,do...end 構文を用いて書くこともできる. end が終わると関数 close(f) が暗黙的に呼ばれる.

open("hello1.txt", "w") do f
   print(f, "Hello")
   println(f, " again.")
end

▶ 書式付き文字列と出力

書式を指定して,値を文字列に変換する場合には @sprintf() マクロを用いる. また,書式を指定して値を印字するには @printf() マクロを用いる. @sprintf@printf はどちらも, Printf パッケージに定義されている. using Printf で,このパッケージを読み込んでから用いる.

マクロ @sprintf(fmt,x) は, x を 書式 fmt に従って文字列にする. マクロ @printf(fmt, x) は,その文字列を印字する. 書式文字列 fmt は, C言語の printf 関数などで用いられる書式と,ほぼ同じである.

なお,Jupyter notebookでは,結果が右寄せされて表示されない場合がある.

書式文字列 %i は,整数を印字する.

  • i の前に整数(フィールドサイズ)を付けると,その整数の幅で右寄せされて印字される.指定されたフィールドサイズで収まらない場合には,幅を増やして印字される.
  • フィールドサイズを 0 で始めると,空白が 0 で埋められる.
  • フィールドサイズの前に + を付けると,非負の数の前に + 符号がつく.
julia> using Printf
julia> @printf("%5i\n", -41) -41
julia> @printf("%05i\n", 413)00413
julia> @printf("%+5i\n", 413) +413
julia> @printf("%+5i\n", -413) -413
julia> @printf("%+05i\n", -413)-0413
julia> @printf("%+5i\n", -41131)-41131

書式文字列 %f は,小数を印字する.

  • f の前に整数(フィールドサイズ)を付けると,その整数の幅で右寄せされて印字される.指定されたフィールドサイズで収まらない場合には,幅を増やして印字される.
  • フォールドサイズに続けて,ピリオド . と整数を付けると,小数点以下の桁数を指定する.指定された小数点以下桁数で表されない場合には,丸められる.
  • フィールドサイズを 0 で始めると,頭の空白が 0 で埋められる.
  • フィールドサイズの前に + を付けると,非負の数の前に + 符号がつく.
  • InfNaN は,その通り印字される.
julia> @printf("%8.3f\n", -2e-2)  -0.020
julia> @printf("%08.0f\n", -0.252)-0000000
julia> @printf("%8.1f\n", -0.252) -0.3
julia> @printf("%08.2f\n", -0.252)-0000.25
julia> @printf("%8.3f\n", -0.252) -0.252

書式文字列 %e は,指数形式で印字する.

  • e の前に整数(フィールドサイズ)を付けると,その整数の幅で右寄せされて印字される.フィールドサイズで収まらない場合には,幅を増やして印字される.
  • フォールドサイズに続けて,ピリオド . と整数を付けると,小数点以下の桁数を指定する.指定された小数点以下桁数で表されない場合には,丸められる.
  • フィールドサイズを 0 で始めると,頭の空白が 0 で埋められる.
  • フィールドサイズの前に + を付けると,非負の数の前に + 符号がつく.
  • InfNaN は,その通り印字される.
julia> @printf("%11.5e\n", -0.0078125)-7.81250e-03
julia> @printf("%12.4e\n", -0.0078125) -7.8125e-03
julia> @printf("%13.3e\n", -0.0078125) -7.812e-03
julia> @printf("%13.2e\n", -0.0078125) -7.81e-03
julia> @printf("%13.1e\n", -0.0078125) -7.8e-03

書式文字列 %s は,文字列形式で印字する.

  • s の前に整数(フィールドサイズ)を付けると,その整数の幅で右寄せされて印字される.フィールドサイズで収まらない場合には,幅を増やして印字される.
julia> @printf("%8s\n", "Hello")   Hello
julia> @printf("%8s\n", "Hello world")Hello world
  • 書式は復数使用してもよい.書式があるのに,印字すべき値がないと例外が発生する.
  • 書式を指定しない文字は,そのまま印字される.
  • \n は改行文字を表す.
  • \t はタブ(TAB)文字を表す.
julia> @printf("%.1f %15.7e\n", 0.025, -0.0078125)0.0  -7.8125000e-03
julia> @printf("x=%.1f y=%15.7e\n", 0.025, -0.0078125) # 引数が足りない例x=0.0 y= -7.8125000e-03
julia> @printf("%.1f %15.7e\n", 0.025)ERROR: ArgumentError: Number of format specifiers and number of provided args differ: 2 != 1

▶ 書式を指定して,テキストファイルに書き込む.

正規乱数 10 個からなるCSVファイルを書き出そう. 関数 @printf の第一引数に,ファイル記述子 f を入れると,f で示されたファイルに印字される.

using Printf
open("test1.txt", "w") do f
   for i = 1:10
      @printf(f, "%15.7f\n", randn())
   end
end

上のプログラムが正しく動作したら,test1.txt が作成されるはずである. このファイルを開き,数字が指定された書式で書き込まれたことを観察・確認せよ. さらに,「Excel」アプリを起動し,このファイルを1列のデータとして読み込んでみよ.

▶ CSVファイルへの書き込み

カンマ , で区切られた値が並ぶ行から構成されるテキストファイルを CSV(Comma Separated Value) ファイルという. 表計算ソフトウエアとのデータの輸入輸出によく用いられる. 拡張子は csv である.

関数 @printf を用いて,整数と浮動小数点数をカンマで区切った行を書き出そう.

なお,CSVファイルの1行目に,カンマ , で区切られた文字列を,各列の表題として書くことが多い.

using Printf
open("test2.csv", "w") do f
   println(f, "i,x")
   for i = 1:10
      @printf(f, "%i,%15.7f\n", i, randn())
   end
end

上のプログラムが正しく動作したら,test2.csv が作成されるはずである. このファイルを開き,プログラムの指示通り書き込まれたか観察・確認せよ. さらに,「Excel」アプリを起動し,2列のデータとして読み込んでみよ.

▶ テキストファイルからの読み込み

▶ テキストファイルからの行単位の読み込み

関数 open( fn ) は,ファイル名 fn のファイルをテキストとして,読み込む準備を行う. ファイル fn が読み込めない場合には例外が発生する.

テキストファイルを読み込むためにも,関数 openclose を組で用いる.

以下のプログラムは,テキストファイルから,1行ずつ読み込み,それを印字する.

関数 eachline(f) は,ファイル記述子 f からテキストを読み込み,行に分割する.このとき,行末の改行文字は取り除かれる.

f = open("hello1.txt")
for line in eachline(f)
   print(line)
end
close(f)
Hello again.

書き込みの場合と同様に,do...end 構文を用いて書くこともできる. end が終わると関数 close(f) が暗黙的に呼ばれる.

open("hello1.txt") do f
   for line in eachline(f)
      print(line)
   end
end
Hello again.

▶ 文字列から数に変換する

文字列を数に変換するには,関数 parse(t, s)を用いる. 第一引数には変換先の型を,第二引数には,変換したい文字列を書く.文字列の前後の空白は無視される.

julia> parse(Float64, " 12.3")12.3
julia> parse(Float64, " 12.3e-2 ")0.123
julia> parse(Int64, " 123 ") # 変換できない例123
julia> parse(Int64, " 12.3 ")ERROR: ArgumentError: invalid base 10 digit '.' in " 12.3 "

上のプログラムで作成したファイル test1.txt から1行ずつ読み込み,浮動小数点数に変換して印字してみよう.

open("test1.txt") do f
   for line in eachline(f)
      x = parse(Float64, line)
      @printf("%15.7f\n", x)
   end
end
     -0.3844436
      0.6654147
      2.0245069
     -0.0008339
      0.7039418
     -0.7263235
      0.0881307
     -0.5442422
     -0.2035987
     -1.1870459

▶ 文字列を分割する

関数 split(s, c) は,文字列を分割する. 第一引数の文字列 s を,第二引数の文字列のどれか一つの文字が出現したら分割する.結果として,文字列を要素とする配列が返される.

英文テキストを,空白で区切る.

julia> split("one cup of brown suger", " ")5-element Vector{SubString{String}}:
 "one"
 "cup"
 "of"
 "brown"
 "suger"

行をカンマで区切って,それぞれ整数と浮動小数点数に変換する.

julia> line = "   15  , 0.2401863""   15  , 0.2401863"
julia> s = split(line, ",")2-element Vector{SubString{String}}: " 15 " " 0.2401863"
julia> parse(Int64, s[1])15
julia> parse(Float64, s[2])0.2401863

上のプログラムで作成した test2.csv を読み込み,各行を整数と浮動小数点数に変換し,印字する. ただし,1行目は読み飛ばす.

using Printf
open("test2.csv") do f
   i = 0
   for line in eachline(f)
      i += 1
      i == 1 && continue
      s = split(line, ",")
      x = parse(Int64, s[1])
      y = parse(Float64, s[2])
      @printf("%5i %15.7f\n", x, y)
   end
end
    1       0.2658296
    2      -1.2140886
    3       0.3546363
    4      -0.9952465
    5       0.4713023
    6      -0.5732707
    7      -0.9260864
    8      -1.0978113
    9      -2.5159664
   10      -1.5457950

▶ 配列に要素を加える

関数 push!(a,x) は,配列 a に,値 x を付け加える.配列 a は書き換えられる.

julia> # Int64 を要素とする配列
       xs = [1, 2]2-element Vector{Int64}:
 1
 2
julia> push!(xs, 3)3-element Vector{Int64}: 1 2 3
julia> xs3-element Vector{Int64}: 1 2 3
julia> push!(xs, 4)4-element Vector{Int64}: 1 2 3 4
julia> xs4-element Vector{Int64}: 1 2 3 4

要素がない配列を作るには 関数 zeros(t, 0) を使えばよい. 第一引数 t は,作成する配列の要素である.

以下では,Float64 型の配列(要素数 $0$)を最初に作成した.

julia> xs = zeros(Float64, 0)Float64[]
julia> push!(xs, 1.0)1-element Vector{Float64}: 1.0
julia> xs1-element Vector{Float64}: 1.0
julia> push!(xs, 2.0)2-element Vector{Float64}: 1.0 2.0
julia> xs2-element Vector{Float64}: 1.0 2.0

▶ CSVファイルから配列を作る

以上を組合せて,CSVファイルを読み込み,値を配列としてまとめることができる.

using Printf
xs = zeros(Int64, 0)
ys = zeros(Float64, 0)

open("test2.csv") do f
   i = 0
   for line in eachline(f)
      i += 1
      i == 1 && continue
      s = split(line, ",")
      x = parse(Int64, s[1])
      push!(xs, x)
      y = parse(Float64, s[2])
      push!(ys, y)
   end
end
@show xs
@show ys
10-element Vector{Float64}:
  0.2658296
 -1.2140886
  0.3546363
 -0.9952465
  0.4713023
 -0.5732707
 -0.9260864
 -1.0978113
 -2.5159664
 -1.545795

続けて,データを描くと,

using Plots
scatter(ys, shape=:circle)

▶ CSVパッケージ

CSV パッケージを用いると,CSVファイルを容易に扱うことができる.

CSV パッケージを設置(インストール)していない場合は,次のように設置する.

import Pkg;
Pkg.add("CSV")
   Resolving package versions...
  No Changes to `~/work/memoProgClass2024w.jl/memoProgClass2024w.jl/docs/Project.toml`
  No Changes to `~/work/memoProgClass2024w.jl/memoProgClass2024w.jl/docs/Manifest.toml`

CSV パッケージに定義されている関数 CSV.read(fn, DataFrame) を用いると,CSVファイル fn を読み込むことができる. 結果として,拡張された配列( DataFrame 型)が返される.

# CSV
using CSV, DataFrames
df = CSV.read("test2.csv", DataFrame)
# 1番目のデータ配列
@show df[:,1]
# 2番目のデータ配列
@show df[:,2]
#
using Plots
scatter(df[:,2], shape=:circle)

データを描いた結果は,上と同じである.

★ 今回のまとめ

  • 文字列の扱い
    • ,出力
    • 文字列の分割
    • 文字列から数への変換
  • 配列に要素を加える
  • テキストファイル
    • 書き込み
    • 読み込み
    • CSVファイル