4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

言語処理100本ノック with Elixir 第2章: UNIXコマンドの基礎

Last updated at Posted at 2019-07-04

更新中です
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/3File.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

chapter_two.ex
  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
chapter_two_command.exs
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に負の数を入れると末尾から要素を取得できる。

chapter_two.ex
  def take_last_lines(file_path, num_of_lines) do
    file_path
    |> File.stream!
    |> Enum.take(-num_of_lines)
  end
chapter_two_command.exs
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]}")
4
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?