はじめに
前回の続きで言語処理100本ノック解いていきたいと思います。
今回は第2章です。
それではやっていきましょう
第2章: UNIXコマンド
研究やデータ分析において便利なUNIXツールを体験します.これらの再実装を通じて,プログラミング能力を高めつつ,既存のツールのエコシステムを体感します.
といいつつpythonでやっていきます。
事前準備
popular-names.txtは,アメリカで生まれた赤ちゃんの「名前」「性別」「人数」「年」をタブ区切り形式で格納したファイルである.以下の処理を行うプログラムを作成し,popular-names.txtを入力ファイルとして実行せよ.さらに,同様の処理をUNIXコマンドでも実行し,プログラムの実行結果を確認せよ.
入力ファイルパスを指定しておきます
file = './input/section2/popular-names.txt'
10. 行数のカウント
行数をカウントせよ.確認にはwcコマンドを用いよ.
ファイルを開いて全行ループしてカウンターを増やしていくことで行数を求めます
def count_line(file): cnt = 0 with open(file, 'r', encoding='utf-8') as f: for row in f: cnt += 1 return cnt print(count_line(file)) # output: # 2780
11. タブをスペースに置換
タブ1文字につきスペース1文字に置換せよ.確認にはsedコマンド,trコマンド,もしくはexpandコマンドを用いよ.
replaceを使えば簡単ですね。
def replace_tab(file): txt = [] with open(file, 'r', encoding='utf-8') as f: for row in f: txt.append(row.replace('\t', ' ')) return txt print(''.join(replace_tab(file)[:5])) # output: # Mary F 7065 1880 # Anna F 2604 1880 # Emma F 2003 1880 # Elizabeth F 1939 1880 # Minnie F 1746 1880
12. 1列目をcol1.txtに,2列目をcol2.txtに保存
各行の1列目だけを抜き出したものをcol1.txtに,2列目だけを抜き出したものをcol2.txtとしてファイルに保存せよ.確認にはcutコマンドを用いよ.
withのあとにopen関数を続けてかけば、複数のファイルを一気に操作できるので便利です。
def extract_col(file, col): output = './input/section2/col{}.txt'.format(col) with open(file, 'r', encoding='utf-8') as f1, \ open(output, 'w', encoding='utf-8') as f2: for row in f1: cols = row.rstrip().split() f2.write(cols[col - 1] + '\n') extract_col(file, 1) extract_col(file, 2)
13. col1.txtとcol2.txtをマージ
12で作ったcol1.txtとcol2.txtを結合し,元のファイルの1列目と2列目をタブ区切りで並べたテキストファイルを作成せよ.確認にはpasteコマンドを用いよ.
これも12番と同様複数ファイルを一気に開いて同じ行のテキストをタブ区切りで書き込みます。
def merge_files(file1, file2, output): with open(file1, 'r', encoding='utf-8') as f1, \ open(file2, 'r', encoding='utf-8') as f2, \ open(output, 'w', encoding='utf-8') as f3: for row1, row2 in zip(f1, f2): row = row1.rstrip() + '\t' + row2.rstrip() + '\n' f3.write(row) file1 = './input/section2/col1.txt' file2 = './input/section2/col2.txt' output = './input/section2/merge.txt' merge_files(file1, file2, output)
14. 先頭からN行を出力
自然数Nをコマンドライン引数などの手段で受け取り,入力のうち先頭のN行だけを表示せよ.確認にはheadコマンドを用いよ.
10番の応用で好きな数字まで出力していくだけです
def print_head(file, n): cnt = 0 with open(file, 'r', encoding='utf-8') as f: for row in f: print(row.rstrip()) cnt += 1 if cnt >= n: return print_head(file, 5) # output: # Mary F 7065 1880 # Anna F 2604 1880 # Emma F 2003 1880 # Elizabeth F 1939 1880 # Minnie F 1746 1880
15. 末尾のN行を出力
自然数Nをコマンドライン引数などの手段で受け取り,入力のうち末尾のN行だけを表示せよ.確認にはtailコマンドを用いよ.
10番の関数を使用して、行数を計算してから最後からn行前から最後の行まで出力します。
def print_tail(file, n): file_len = count_line(file) cnt = 0 with open(file, 'r', encoding='utf-8') as f: for row in f: cnt += 1 if cnt > file_len - n: print(row.rstrip()) else: continue print_tail(file, 5) # output: # Benjamin M 13381 2018 # Elijah M 12886 2018 # Lucas M 12585 2018 # Mason M 12435 2018 # Logan M 12352 2018
16. ファイルをN分割する
自然数Nをコマンドライン引数などの手段で受け取り,入力のファイルを行単位でN分割せよ.同様の処理をsplitコマンドで実現せよ.
行数がnで割り切れるかどうかで場合分けします。
割り切れる場合は行数をn等分します。
割り切れない場合は行数をn+1で割った数ずつファイルに分けます。
def n_split(file, n): file_len = count_line(file) if file_len % n == 0: file_num = file_len // n else: file_num = file_len // n + 1 res = [] files = [] cnt = 0 with open(file, 'r', encoding='utf-8') as f: for row in f: if cnt % file_num == 0 and cnt != 0: res.append(files) files = [] files.append(row.rstrip()) cnt += 1 if len(files): res.append(files) return res res = n_split(file, 100) # 最初のファイルのみ出力 for x in res: print('\n'.join(x)) break
Mary F 7065 1880 Anna F 2604 1880 Emma F 2003 1880 Elizabeth F 1939 1880 Minnie F 1746 1880 Margaret F 1578 1880 Ida F 1472 1880 Alice F 1414 1880 Bertha F 1320 1880 Sarah F 1288 1880 John M 9655 1880 William M 9532 1880 James M 5927 1880 Charles M 5348 1880 George M 5126 1880 Frank M 3242 1880 Joseph M 2632 1880 Thomas M 2534 1880 Henry M 2444 1880 Robert M 2415 1880 Mary F 6919 1881 Anna F 2698 1881 Emma F 2034 1881 Elizabeth F 1852 1881 Margaret F 1658 1881 Minnie F 1653 1881 Ida F 1439 1881 Annie F 1326 1881
17. 1列目の文字列の異なり
1列目の文字列の種類(異なる文字列の集合)を求めよ.確認にはcut, sort, uniqコマンドを用いよ.
集合を使えば重複を除去できるので種類数を求められます。
def uniq_count(file, col): res = set() with open(file, 'r', encoding='utf-8') as f: for row in f: cols = row.rstrip().split() res.add(cols[col]) return len(res) print(uniq_count(file, 0)) # output: # 136
18. 各行を3コラム目の数値の降順にソート
各行を3コラム目の数値の逆順で整列せよ(注意: 各行の内容は変更せずに並び替えよ).確認にはsortコマンドを用いよ(この問題はコマンドで実行した時の結果と合わなくてもよい).
特定の列でソートするにはsorted関数のkeyプロパティを指定します。
def sort_col2(file): ans = [] with open(file, 'r', encoding='utf-8') as f: for row in f: cols = row.rstrip().split() ans.append(cols) ans = sorted(ans, key=lambda x: int(x[2]), reverse=True) return ans print(sort_col2(file)[:5]) # output: # [['Linda', 'F', '99689', '1947'], ['Linda', 'F', '96211', '1948'], ['James', 'M', '94757', '1947'], ['Michael', 'M', '92704', '1957'], ['Robert', 'M', '91640', '1947']]
19. 各行の1コラム目の文字列の出現頻度を求め,出現頻度の高い順に並べる
各行の1列目の文字列の出現頻度を求め,その高い順に並べて表示せよ.確認にはcut, uniq, sortコマンドを用いよ.
出現頻度といえばcollections.Counterモジュールが簡単で便利です。
from collections import Counter def col_count(file, col): c = Counter() res = [] with open(file, 'r', encoding='utf-8') as f: for row in f: cols = row.rstrip().split() res.append(cols[col]) c.update(res) return c ans = col_count(file, 0) print(ans.most_common(10)) # output: # [('James', 118), ('William', 111), ('John', 108), ('Robert', 108), ('Mary', 92), ('Charles', 75), ('Michael', 74), ('Elizabeth', 73), ('Joseph', 70), ('Margaret', 60)]
おわりに
何とか2章終わりました。
これからは難しくなっていきそうなので不安になってきました。