Clojureで「言語処理100本ノック 2015」(その3)

Clojureで「言語処理100本ノック 2015」を解いてみようの続き

目次

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

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

10. 行数のカウント

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

(require '[clojure.string :as str])
 (count (str/split (slurp "hightemp.txt") #"\n"))
; => 24
$ wc -l hightemp.txt 
      24 hightemp.txt

slurp 便利すぎ。(slurp "http://clojuredocs.org/") のようにHTTPもHTTPSも対応しているのね。

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

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

(require '[clojure.string :as str])
(map #(println %) (map #(str/replace % #"\t" " ")(str/split (slurp "http://www.cl.ecei.tohoku.ac.jp/nlp100/data/hightemp.txt") #"\n")))

; 高知県 江川崎 41 2013-08-12
; 埼玉県 熊谷 40.9 2007-08-16
; 岐阜県 多治見 40.9 2007-08-16

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

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

(require '[clojure.string :as str])

(def hightemp (map #(str/split % #"\t") (str/split (slurp "http://www.cl.ecei.tohoku.ac.jp/nlp100/data/hightemp.txt") #"\n")))

(defn save-file [file-name file-body]
    (with-open [f-out (clojure.java.io/writer  file-name :append true)]
        (.write f-out file-body)))

(def col1-body (str/join "\n" (map #(first %) hightemp)))
(def col2-body (str/join "\n" (map #(second %) hightemp)))

(save-file "col1.txt" col1-body)
(save-file "col2.txt" col2-body)

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

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

(require '[clojure.string :as str])

(def col1 (str/split (slurp "col1.txt") #"\n"))
(def col2 (str/split (slurp "col2.txt") #"\n"))
(println (str/join "\n" (map #(str/join "\t" %) (partition 2 (interleave col1 col2)))))

;; => 高知県 江川崎
;; 埼玉県   熊谷
;; 岐阜県   多治見
;; 山形県   山形
;; (以下略)

14. 先頭からN行を出力

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

(require '[clojure.string :as str])

(def hightemp-txt (str/split (slurp "http://www.cl.ecei.tohoku.ac.jp/nlp100/data/hightemp.txt") #"\n")))
(defn head [no input] (map #(println (str %)) (take no input)))

(head 5 hightemp-txt)
;; => 高知県 江川崎   41  2013-08-12
;; 埼玉県   熊谷  40.9    2007-08-16
;; 岐阜県   多治見   40.9    2007-08-16
;; 山形県   山形  40.8    1933-07-25
;; 山梨県   甲府  40.7    2013-08-10

15. 末尾のN行を出力

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

(defn tail [no input] (map #(println (str %)) (take-last no input)))
(tail 3 hightemp-txt)
;; => 山梨県 大月  39.9    1990-07-19
;; 山形県   鶴岡  39.9    1978-08-03
;; 愛知県   名古屋   39.9    1942-08-02

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

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

(require '[clojure.string :as str])

(def hightemp-seq (str/split (slurp "http://www.cl.ecei.tohoku.ac.jp/nlp100/data/hightemp.txt") #"\n"))
(defn split-no [no] (/ (count hightemp-seq) no))
(defn division [no] (map #(println % "\n") (map #(str/join "\n" %) (partition (split-no no) hightemp-seq)))) 

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

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

(require '[clojure.string :as str])

(def hightemp (map #(str/split % #"\t") (str/split (slurp "http://www.cl.ecei.tohoku.ac.jp/nlp100/data/hightemp.txt") #"\n")))
(distinct (map #(first %) hightemp))

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

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

(require '[clojure.string :as str])

(def hightemp (map #(str/split % #"\t") (str/split (slurp "http://www.cl.ecei.tohoku.ac.jp/nlp100/data/hightemp.txt") #"\n")))

(map println (map #(str/join "\t" %) (reverse (sort-by #(nth % 2) hightemp))))

;; user => 高知県    江川崎   41  2013-08-12
;; 岐阜県   多治見   40.9    2007-08-16
;; 埼玉県   熊谷  40.9    2007-08-16
;; 山形県   山形  40.8    1933-07-25
;; 山梨県   甲府  40.7    2013-08-10

sort-by の使い方にハマった。(sort-by keyfn coll) なので、3コラム目を取得するような関数を渡せば良い。

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

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

(def hightemp (map #(str/split % #"\t") (str/split (slurp "http://www.cl.ecei.tohoku.ac.jp/nlp100/data/hightemp.txt") #"\n")))

(reverse (sort-by second (frequencies  (map #(first %) hightemp))))
;; user => (["山梨県" 3] ["埼玉県" 3] ["群馬県" 3] ["山形県" 3] ["愛知県" 2] ["岐阜県" 2] ["静岡県" 2] ["千葉県" 2] ["和歌山県" 1] ["高知県" 1] ["大阪府" 1] ["愛媛県" 1])

frequencies 関数はこの問題のためにあるような関数ですね。

第2章を終えて

簡単なアルゴリズムならCojureで書けるようになった。 100本ノックは、一旦終わりにする。

今後は、並列処理を中心に細かいトピックを拾っていきたいのと、どこかで使っていきたい。