1. Yusuke196

    Posted

    Yusuke196
Changes in title
+言語処理100本ノック第2章で学ぶUNIXコマンド
Changes in tags
Changes in body
Source | HTML | Preview
@@ -0,0 +1,218 @@
+## はじめに
+
+社内のメンバーを中心にした勉強会で[言語処理100本ノック](http://www.cl.ecei.tohoku.ac.jp/nlp100)を解いているのですが、その解答コードや、解く過程で便利だなと思った小技のまとめです。自分で調べたり検証したりした内容が多いですが、他の勉強会メンバーが共有してくれた情報も入っています。
+
+今回はUNIXコマンドの基礎についてまとめていきますが、先行する[@moriwoさん](https://qiita.com/moriwo/items/9d2a73a75f543e2ea6af)や[@segavvyさん](https://qiita.com/segavvy/items/fb50ba8097d59475f760)の記事でそこそこ詳しい解説がすでに書かれているので、この記事では解説の量を控えめにしたいと思います。以下を読んで分からない点があれば、上のリンクからお二方の記事なども見てみるのがおすすめです。
+
+## 環境
+
+- macOS
+- Python 3.8.1
+- JupyterLab
+
+## コード
+
+### 10. 行数のカウント
+
+```py:Python
+def count_lines():
+ with open('hightemp.txt') as file:
+ return len(file.readlines())
+
+count_lines()
+```
+
+```sh:UNIX
+!wc -l hightemp.txt
+```
+
+UNIXコマンドの方が圧倒的に簡潔に書けました。ちなみに`wc`の前についている`!`は、JupyterLabやNotebookでUNIXコマンドを実行するときに使うものです(場合によって`!`なしでも動作します)。
+
+### 11. タブをスペースに置換
+
+```py:Python
+def replace_tabs():
+ with open('hightemp.txt') as file:
+ return file.read().replace('\t', ' ')
+
+print(replace_tabs())
+```
+
+```sh:UNIX
+!cat hightemp.txt | sed $'s/\t/ /g'
+```
+
+UNIXの`sed`では、`$`記号をつけないと`\t`がタブ記号として認識されないという注意点があります。
+
+### 12. 1列目をcol1.txtに,2列目をcol2.txtに保存
+
+```py:Python
+def separate_columns():
+ df = pd.read_csv('hightemp.txt', sep='\t', header=None)
+ df.iloc[:,0].to_csv('col1.txt', header=False, index=False)
+ df.iloc[:,1].to_csv('col2.txt', header=False, index=False)
+
+separate_columns()
+```
+
+```sh:UNIX
+!cut -f 1 hightemp.txt > col1_unix.txt
+!cut -f 2 hightemp.txt > col2_unix.txt
+```
+
+### 13. col1.txtとcol2.txtをマージ
+
+```py:Python
+def merge_columns():
+ with open('col1.txt') as col1_file, open('col2.txt') as col2_file, \
+ open('merge.txt', mode='w') as new_file:
+
+ for col1_line, col2_line in zip(col1_file, col2_file):
+ new_file.write(f'{col1_line.rstrip()}\t{col2_line.rstrip()}\n')
+
+merge_columns()
+```
+
+```sh:UNIX
+!paste col[1-2].txt > merge_unix.txt
+```
+
+### 14. 先頭からN行を出力
+
+```py:Python
+def show_head():
+ n = int(input())
+
+ with open('hightemp.txt') as file:
+ for line in file.readlines()[:n]:
+ print(line.rstrip())
+
+show_head()
+```
+
+```sh:UNIX
+!head -3 hightemp.txt
+```
+
+Pythonについては、関数からリストを返すことにしたい場合、以下のように書くのもありかと思います。
+
+```py:Python
+def show_head():
+ n = int(input())
+
+ with open('hightemp.txt') as file:
+ return [line for line in file.readlines()[:n]]
+
+print(*show_head())
+```
+
+UNIXではPythonのようにコマンドラインで整数を受け取ることが難しいのですが、`-`の後ろに整数を書くと何行分表示するか指定できます。応用的な使い方としては、`12`の解答の最後で
+
+```sh:UNIX
+!cat hightemp.txt | sed $'s/\t/ /g' | head -5
+```
+
+と書いて、最初の5行だけ表示することもできたりします。
+
+### 15. 末尾のN行を出力
+
+```py:Python
+def show_tail():
+ n = int(input())
+
+ with open('hightemp.txt') as file:
+ for line in file.readlines()[-n:]:
+ print(line.rstrip())
+
+show_tail()
+```
+
+```sh:UNIX
+!tail -3 hightemp.txt
+```
+
+14とほぼ同じ。
+
+### 16. ファイルをN分割する
+
+```py:Python
+import math
+
+def split_file():
+ n = int(input())
+
+ with open('hightemp.txt') as file:
+ lines = file.readlines()
+ num = math.ceil(len(lines) / n)
+ for i in range(n):
+ with open('split{}.txt'.format(i + 1), mode='w') as new_file:
+ text = ''.join(lines[i * num:(i + 1) * num])
+ new_file.write(text)
+
+split_file()
+```
+
+```sh:UNIX
+!split -n 5 -d hightemp.txt split_unix
+```
+
+Linuxでは一般に`-n`コマンドが使えるようですが([@IT](https://www.atmarkit.co.jp/ait/articles/1711/24/news016.html))、自分のmacOS環境だと動作せず、このUNIXコマンドだけはColabで試しました。そうすると`split_unix00`から`split_unix04`まで5つのファイルができましたが、加えてそこに拡張子の`txt`をつけようとしたりすると、かなり面倒になりそうだと感じました。
+
+[@moriwoさんの記事](https://qiita.com/moriwo/items/9d2a73a75f543e2ea6af#16-%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%82%92n%E5%88%86%E5%89%B2%E3%81%99%E3%82%8B)では`awk`なども使った実装例が紹介されていますが、自分はPythonを使ったほうが読みやすいコードになるのかな、と思いました。
+
+### 17. 1列目の文字列の異なり
+
+```py:Python
+import pandas as pd
+
+def get_chars_set():
+ df = pd.read_csv('hightemp.txt', sep='\t', header=None)
+ return set(df.iloc[:, 0])
+
+print(get_chars_set())
+```
+
+```sh:UNIX
+!sort -u col1_unix.txt
+```
+
+並べ替えと重複除去の処理をパイプで繋いで、`!sort col1_unix.txt | uniq`とも書けます。
+
+### 18. 各行を3コラム目の数値の降順にソート
+
+```py:Python
+def sort_rows():
+ df = pd.read_csv('hightemp.txt', sep='\t', header=None)
+ df.rename(columns={0: 'Prefect', 1: 'City', 2: 'Temp', 3: 'Date'}, inplace=True)
+ df.sort_values(by='Temp', inplace=True)
+ return df
+
+sort_rows()
+```
+
+```sh:UNIX
+!sort hightemp.txt -k 3
+```
+
+「逆順」という言葉の意味が、元の逆ということなのか降順ということなのか判然としませんが、後者の解釈で解いてみました。この問題については、UNIXコマンドだと短く書けるということを特に強く感じられますね。
+
+### 19. 各行の1コラム目の文字列の出現頻度を求め,出現頻度の高い順に並べる
+
+```py:Python
+def count_freq():
+ df = pd.read_csv('hightemp.txt', sep='\t', header=None)
+ return df[0].value_counts()
+
+count_freq()
+```
+
+```sh:UNIX
+!cut -f 1 hightemp.txt | sort | uniq -c | sort -r
+```
+
+UNIXコマンドは少し長くなりましたが、まず1列目を切り出して(`cut -f 1`)、次にその出現頻度を求め(`sort | uniq -c`)、最後に出現頻度の逆順で並べる(`sort -r`)、という流れが分かりやすくなるように書いてみました。
+
+ただ、pandasの`value_counts()`の優秀さも考えると、ここではPythonを使ったほうが分かりやすいかなという気がします。
+
+## まとめ
+