言語処理100本ノック2020解いてみた②

 今回は第2章に取り組みました。UNIXコマンドは毎回ググりながら使っていたので今回もめちゃくちゃググりました。検索エンジンがある時代の人間でよかったなぁ…。

下準備

 txtファイルをダウンロードして、/dataの下に格納しました。普段何か作る時は小規模でも自分がいじるものと参照するだけのものは場所を分けるようにしています。間違えてやらかす時あるので。
 また、今回はpythonとUNIXコマンドどちらでも出来るようにしましょうという内容なのでどっちもやっていきます。実は最近MacBookPROを新調してCatalinaになったんですがzshデフォルトってことを失念していて一瞬えっ!?ってしてました。
実行環境:macOS Catalina 10.15.4 / zsh 5.7.1 (x86_64-apple-darwin19.0)

10.行数のカウント

解法 (python)
今後のことを考え、今回はpandasの便利機能をどんどん使っていきます。

import pandas as pd

df = pd.read_csv("./data/popular-names.txt",header=None)
print(len(df))

解法 (UNIXコマンド)
wc (word count)の-lは--linesの略で、改行の数を表示してくれます。

#!/bin/zsh
wc -l ./data/popular-names.txt

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

解法 (python)
タブ区切りで読み出して、スペース区切りで出力します。sepかdelimiterで区切り文字を指定します。また今回はヘッダーカラムがないのでheader=Noneとします。

import pandas as pd

df = pd.read_csv("./data/popular-names.txt", sep="\t",header=None)
df.to_csv("./out/ex11_py.txt",sep=" ",index=False, header=None)

解法 (UNIXコマンド)
sed (stream editor)はs/置換前/置換後/で文章置換ができます。文字が複数含まれている場合に全部を置き換えるために、末尾にgをつけました。自分の環境ではs/'\t'/' '/gだとタブがそのまま生存してしまったので、ANSI-C Quotingを用いて$'\t'と表記することで回避しました。無知が露呈しています。

#!/bin/zsh
sed s/$'\t'/$' '/g ./data/popular-names.txt > ./out/ex11_unix.txt

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

解法 (python)
データフレームの任意の列を抜き出して保存します。

import pandas as pd

df = pd.read_csv("./data/popular-names.txt", sep="\t", header=None)
df[0].to_csv("./out/col1.txt",index=False, header=None)
df[1].to_csv("./out/col2.txt",index=False, header=None)

解法 (UNIXコマンド)
cutの-fコマンドで任意の列を持ってきて保存しました。-dはdelimiterです。

#!/bin/zsh
cut -f1 -d $'\t' ./data/popular-names.txt > ./out/col1_unix.txt
cut -f2 -d $'\t' ./data/popular-names.txt > ./out/col2_unix.txt

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

解法 (python)
concatで2つのデータフレームを結合します。

import pandas as pd

df1 = pd.read_csv("./out/col1.txt", header=None)
df2 = pd.read_csv("./out/col2.txt", header=None)

df = pd.concat((df1,df2),axis=1)
df.to_csv("./out/ex13_py.txt",sep="\t",index=False, header=None)

解法 (UNIXコマンド)
pasteでくっつけました。

#!/bin/zsh
paste -d $'\t' ./out/col1.txt ./out/col2.txt > ./out/ex13_unix.txt

14. 先頭からN行を出力

解法 (python)
実行時に引数でNが入力されていればその行数、そうでなければNを聞くみたいな優しさを見せつつも、とどのつまり冒頭N行プリントしているだけです。

import pandas as pd
import sys

args = sys.argv

if len(args)==1:
	print("Please set number. N =")
	N=int(input())
else:
	N=int(args[1])

df = pd.read_csv("./data/popular-names.txt", sep="\t", header=None)
print(df[:N])

解法 (UNIXコマンド)
headで行数を指定して確認しました。

#!/bin/zsh
head -n 5 ./data/popular-names.txt

15. 末尾のN行を出力

解法 (python)
14と基本は一緒、出力の時に末尾からN行指定すればよさそうです。

import pandas as pd
import sys

args = sys.argv

if len(args)==1:
	print("Please set number. N =")
	N=int(input())
else:
	N=int(args[1])

df = pd.read_csv("./data/popular-names.txt", sep="\t", header=None)
print(df[-N:])

解法 (UNIXコマンド)
tailで同様のことをやります。

#!/bin/zsh
tail -n 5 ./data/popular-names.txt

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

解法 (python)
pandasで読み込むときにchunknizeで指定した行数ごとで分割することができます。しかし、今回の問題はN行ごとに分割、ではなくN分割だったので、一回全体の長さを取得してから、N分割したら1つの塊が何行になるのかを計算して、引き渡してあげています。確実に無駄だと思うので賢いやり方募集中。

import pandas as pd
import numpy as np
import sys

args = sys.argv

if len(args)==1:
	print("Please set number. N =")
	N=int(input())
else:
	N=int(args[1])

df = pd.read_csv("./data/popular-names.txt", sep="\t", header=None)
df = pd.read_csv("./data/popular-names.txt", sep="\t", header=None, chunksize=int(np.ceil(len(df)/N)))

for i,d in enumerate(df):
	d.to_csv("./out/ex16_%s.txt"%i,sep="\t",header=None,index=None)

解法 (UNIXコマンド)
こっちもほぼ同じ仕組みで。nを与えてから行数を$countに入れ、分割単位の行数lineを求めてから、splitで分割…なんですけどこれ実は行数が割り切れないと端数が漏れてしまうので、出来栄え的には70点くらいです。
ちゃんと端数処理しているコードを書いている方の記事はこちらです。
https://qiita.com/segavvy/items/993ea169a1111c6f6f69

#!/bin/zsh
echo -n "Please set number. N = "
read n

count=`wc -l ./data/popular-names.txt | awk '{print $1}'`
line=`expr $count / $n`
split -l $line ./data/popular-names.txt ./out/ex16_

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

解法 (python)
pandasのuniqueを使います。。

import pandas as pd

df = pd.read_csv("./data/popular-names.txt", sep="\t", header=None)
print(sorted(df[0].unique()))

解法 (UNIXコマンド)

#!/bin/zsh
cut -f1 data/popular-names.txt | sort | uniq

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

解法 (python)
pandasのsort_valuesを使います。逆順なのでascendingをFalseに指定してソートします。

import pandas as pd

df = pd.read_csv("./data/popular-names.txt", sep="\t", header=None)
df.sort_values(2,ascending=False).to_csv("./out/ex18_py.txt",sep="\t",index=None, header=None)

解法 (UNIXコマンド)
3カラム目は数値データが入っているので、-nをつけないと文字列でソートしてしまうので注意が必要ですね。

#!/bin/zsh
sort -k 3 -n -r ./data/popular-names.txt > ./out/ex18_unix.txt

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

解法 (python)
pandasのvalue_countsを使います。

import pandas as pd

df = pd.read_csv("./data/popular-names.txt", sep="\t", header=None)
print(df[0].value_counts())

解法 (UNIXコマンド)
まずは、cutで1コラム目だけ取り出してソートしてuniqで重複をカウント、その後カウント数を数値ソートして表示でいいでしょうかね。

#!/bin/zsh
cut -f1 -d $'\t' ./data/popular-names.txt | sort | uniq -c | sort -k1nr -t $' '

感想

 UNIXコマンドがちょっとだけわかるようになりました。あとそろそろ1つの記事に10問載せるの辛いような気がしてきたので次回から前編・後編とかにしようかなと思います。まぁまだ全然解いてないんですけど…。

創作活動及び成果の発信などの形でみなさんにお返しできたらと思います。