更新中です
UNIXコマンドの練習の章ですが、Pythonでやったときと同じなのでUNIXコマンドは省略します。
10. 行数のカウント
行数をカウントせよ.確認にはwcコマンドを用いよ.
File.stream!/3を使って行ごとにファイルを読み込む。返ってくるのはFile.Stream構造体。(オプション指定で行ごとじゃなくてバイトごとの読み込みもできる。)Enum.to_list/1でListにしてからlengthで長さを取得。
def count_lines(file_path) do
file_path
|> File.stream!
|> Enum.to_list
|> length
end
def run() do
count_lines("data/hightemp.txt")
|> IO.inspect(label: "10. 行数のカウント")
end
11. タブをスペースに置換
タブ1文字につきスペース1文字に置換せよ.確認にはsedコマンド,trコマンド,もしくはexpandコマンドを用いよ.
File.read!/1を使ってファイルを読み込んでからString.replace/4
で置換
def alter_tub_with_space(file_path) do
file_path
|> File.read!
|> String.replace("\t", " ")
end
def run() do
alter_tub_with_space("data/hightemp.txt")
|> IO.inspect(label: "11. タブをスペースに置換")
end
12. 1列目をcol1.txtに,2列目をcol2.txtに保存
各行の1列目だけを抜き出したものをcol1.txtに,2列目だけを抜き出したものをcol2.txtとしてファイルに保存せよ.確認にはcutコマンドを用いよ.
Enumの代わりにStreamを使うと遅延評価になる。
各行をString.split/1
で列に分割してからEnum.at/3で列を抜き出し、
File.write!/3で一行ずつファイルに書き出す。File.write!/3
のオプションに:appendをつけると、上書きではなく追加になる。書き出すときに<>/2で改行コードと結合している。
Enum.each/2は要素ごとに関数を実行し、:ok
を返す。
2019.7.12 追記
このやり方だと関数を実行するたびに行が追加される。
今回の課題の場合は↓の13と同じようにして、実行のたびに上書き保存するほうが良さそう。
def extract_column(input_path, output_path, column_index) do
input_path
|> File.stream!
|> Stream.map(&String.split/1)
|> Stream.map(&Enum.at(&1, column_index))
|> Enum.each(fn line -> File.write!(output_path, line <> "\n", [:append]) end)
end
def run() do
[1, 2]
|> Enum.map(&extract_column("data/hightemp.txt", "data/col#{&1}.txt", &1 - 1))
|> IO.inspect(label: "12. 1列目をcol1.txtに,2列目をcol2.txtに保存")
end
13. col1.txtとcol2.txtをマージ
12で作ったcol1.txtとcol2.txtを結合し,元のファイルの1列目と2列目をタブ区切りで並べたテキストファイルを作成せよ.確認にはpasteコマンドを用いよ.
Stream.run/1を使うとStreamを実行できる。実行の副作用が目的で返り値がどうでもいいときに使う。Stream.into/3、File.stream!/3
と合わせると、メモリを節約して一行ずつファイルの読み書き処理ができる。
カラム同士をマージする前にString.trim/1で改行コードを取り除いて、マージ後にまた改行コードを足している。
def merge_columns(input_paths, output_path) do
input_paths
|> Enum.map(&File.stream!/1)
|> Enum.map(fn file_stream -> Stream.map(file_stream, &String.trim/1) end)
|> Stream.zip
|> Stream.map(&Tuple.to_list/1)
|> Stream.map(&Enum.join(&1, "\t"))
|> Stream.map(&Kernel.<>(&1, "\n"))
|> Stream.into(File.stream!(output_path))
|> Stream.run()
end
def run() do
merge_columns(["data/col1.txt", "data/col2.txt"], "data/col1_2.txt")
|> IO.inspect(label: "13. col1.txtとcol2.txtをマージ")
end
14. 先頭からN行を出力
自然数Nをコマンドライン引数などの手段で受け取り,入力のうち先頭のN行だけを表示せよ.確認にはheadコマンドを用いよ.
Enum.take/2で先頭の要素を取得する。
コマンドラインからの実行はファイルを分けた。
Code.require_file/2で必要なモジュールが定義されているファイルを読み込んでコンパイルする。引数はSystem.argv/0で取得し、OptionParser.parse/2でパースする。
これでelixir lib/chapter_two_command.exs --first 5
のように実行できる。
CLIを作りたいときはescriptを使うらしい。(Executables · Elixir School)
def take_first_lines(file_path, num_of_lines) do
file_path
|> File.stream!
|> Enum.take(num_of_lines)
end
def run() do
take_first_lines("data/hightemp.txt", 3)
|> IO.inspect(label: "14. 先頭からN行を出力 / N=3")
end
Code.require_file("chapter_two.ex", __DIR__)
args = System.argv
|> OptionParser.parse(strict: [first: :integer, last: :integer, split: :integer])
|> elem(0)
ChapterTwo.take_first_lines("data/hightemp.txt", args[:first])
|> IO.inspect(label: "14. 先頭からN行を出力 / N = #{args[:first]}")
15. 末尾のN行を出力
自然数Nをコマンドライン引数などの手段で受け取り,入力のうち末尾のN行だけを表示せよ.確認にはtailコマンドを用いよ.
Enum.take/2
は引数amount
に負の数を入れると末尾から要素を取得できる。
def take_last_lines(file_path, num_of_lines) do
file_path
|> File.stream!
|> Enum.take(-num_of_lines)
end
Code.require_file("chapter_two.ex", __DIR__)
args = System.argv
|> OptionParser.parse(strict: [first: :integer, last: :integer, split: :integer])
|> elem(0)
ChapterTwo.take_last_lines("data/hightemp.txt", args[:last])
|> IO.inspect(label: "15. 末尾のN行を出力 / N = #{args[:last]}")