晴耕雨読

working in the fields on fine days and reading books on rainy days

[Elixir] 言語処理100本ノック(第2章)

はじめに

自然言語処理と Python のトレーニングのため、 東北大学の乾・岡崎研究室 Web ページにて公開されている言語処理100本ノックを Elixir で挑戦していきます。

第2章: UNIXコマンドの基礎

hightemp.txt は,日本の最高気温の記録を「都道府県」「地点」「℃」「日」のタブ区切り形式で格納したファイルである. 以下の処理を行うプログラムを作成し, hightemp.txt を入力ファイルとして実行せよ.さらに,同様の処理を UNIX コマンドでも実行し,プログラムの実行結果を確認せよ.

10. 行数のカウント

行数をカウントせよ.確認には wc コマンドを用いよ.

filepath = "hightemp.txt"

File.stream!(filepath)
|> Enum.to_list
|> length
|> IO.inspect

11. タブをスペースに置換

タブ 1 文字につきスペース 1 文字に置換せよ.確認には sed コマンド, tr コマンド,もしくは expand コマンドを用いよ.

filepath = "hightemp.txt"

File.read!(filepath)
|> String.replace("\t", " ")
|> IO.inspect

12. 1列目をcol1.txtに,2列目をcol2.txtに保存

各行の 1 列目だけを抜き出したものを col1.txt に,2列目だけを抜き出したものを col2.txt としてファイルに保存せよ. 確認には cut コマンドを用いよ.

filepath = "hightemp.txt"

# 空のファイルの作成
File.write!("col1.txt", "", [:write])
File.write!("col2.txt", "", [:write])

File.stream!(filepath)
|> Enum.each(fn line ->
    [col1, col2 | _] = String.split(line, "\t")
    File.write!("col1.txt", col1 <> "\n", [:append])
    File.write!("col2.txt", col2 <> "\n", [:append])
  end)

13. col1.txtとcol2.txtをマージ

12 で作った col1.txt と col2.txt を結合し,元のファイルの 1 列目と 2 列目を タブ区切りで並べたテキストファイルを作成せよ.確認には paste コマンドを用いよ.

col1 = File.stream!("col1.txt")
col2 = File.stream!("col2.txt")

content = Stream.zip(col1, col2)
  |> Stream.map(fn tuple ->
      tuple |> Tuple.to_list |> Enum.map(&String.trim/1) |> Enum.join("\t")
    end)
  |> Enum.join("\n")

File.write!("merge.txt", content, [:write])

14. 先頭からN行を出力

自然数 N をコマンドライン引数などの手段で受け取り,入力のうち先頭の N 行だけを表示せよ. 確認には head コマンドを用いよ.

filepath = "hightemp.txt"
n = hd(System.argv) |> String.to_integer

File.stream!(filepath)
|> Enum.take(n)
|> IO.inspect

15. 末尾のN行を出力

自然数 N をコマンドライン引数などの手段で受け取り,入力のうち末尾の N 行だけを表示せよ. 確認には tail コマンドを用いよ.

filepath = "hightemp.txt"
n = hd(System.argv) |> String.to_integer

File.stream!(filepath)
|> Enum.take(-n)
|> IO.inspect

16. ファイルをN分割する

自然数 N をコマンドライン引数などの手段で受け取り,入力のファイルを行単位で N 分割せよ. 同様の処理を split コマンドで実現せよ.

filepath = "hightemp.txt"
file_n = hd(System.argv) |> String.to_integer

content = File.stream!(filepath) |> Enum.to_list
max_row = div(length(content), file_n)

0..(file_n - 1)
|> Enum.map(fn n ->
    sliced_content = Enum.slice(content, (n * max_row)..((n+1) * max_row) - 1)
    File.write!("slice#{n}.txt", sliced_content, [:write])
  end)

17. 1列目の文字列の異なり

1 列目の文字列の種類(異なる文字列の集合)を求めよ.確認には sort , uniq コマンドを用いよ.

filepath = "hightemp.txt"

File.stream!(filepath)
|> Enum.map(fn line -> hd(String.split(line, "\t")) end)
|> MapSet.new
|> MapSet.to_list
|> IO.inspect
# => ["千葉県", "和歌山県", "埼玉県", "大阪府", "山形県", "山梨県",
#     "岐阜県", "愛媛県", "愛知県", "群馬県", "静岡県", "高知県"]

18. 各行を3コラム目の数値の降順にソート

各行を 3 コラム目の数値の逆順で整列せよ(注意: 各行の内容は変更せずに並び替えよ). 確認には sort コマンドを用いよ(この問題はコマンドで実行した時の結果と合わなくてもよい).

filepath = "hightemp.txt"

File.stream!(filepath)
|> Enum.sort_by(fn line ->
    [_, _, col3 | _] = String.split(line, "\t")
    col3
  end, &>=/2)
|> Enum.join("\n")
|> IO.puts
# => 高知県	江川崎	41	2013-08-12
#    埼玉県	熊谷	40.9	2007-08-16
#    岐阜県	多治見	40.9	2007-08-16
#      :
#    山梨県	大月	39.9	1990-07-19
#    山形県	鶴岡	39.9	1978-08-03
#    愛知県	名古屋	39.9	1942-08-02

19. 各行の1コラム目の文字列の出現頻度を求め,出現頻度の高い順に並べる

各行の 1 列目の文字列の出現頻度を求め,その高い順に並べて表示せよ. 確認には cut , uniq , sort コマンドを用いよ.

filepath = "hightemp.txt"

File.stream!(filepath)
|> Enum.map(fn line -> hd(String.split(line, "\t")) end)
|> Enum.reduce(%{}, fn word, acc -> Map.update(acc, word, 1, &(&1 + 1)) end)
|> Enum.sort_by(fn {_k, v} -> v end, &>=/2)
|> IO.inspect
# => [{"埼玉県", 3}, {"山形県", 3}, {"山梨県", 3}, {"群馬県", 3},
#     {"千葉県", 2}, {"岐阜県", 2}, {"愛知県", 2}, {"静岡県", 2},
#     {"和歌山県", 1}, {"大阪府", 1}, {"愛媛県", 1}, {"高知県", 1}]

第3章につづく (後日追記:続きません)