ギークなエンジニアを目指す男

機械学習系の知識を蓄えようとするブログ

言語処理100本ノックをやってみた「第2章: UNIXコマンドの基礎」

f:id:taxa_program:20190227105050p:plain

こんにちは。takapy(@takapy0210)です。

本記事は、転職カウントダウンカレンダー 8日目の記事です。

www.takapy.work

はじめに

言語処理100本ノックを少しずつやっていこうシリーズです。本記事は第2章のまとめです。

実行環境は下記です。

OS:Mac OS Mojave
Python:Python 3.6.0

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

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

本記事ではUNIXコマンドについては記載しておりません。

10. 行数のカウント

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

fname = 'hightemp.txt'
with open(fname) as f:
    text = f.readlines()
    print(len(text))

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

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

fname = 'hightemp.txt'
with open(fname) as f:
    text = f.read()
    text = text.replace('\t', ' ')
print(text)

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

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

fname = 'hightemp.txt'
with open(fname) as f, open('col1.txt', 'w') as col1_file, open('col2.txt', 'w') as col2_file:
    for line in f:
        cols = line.split('\t')
        col1_file.write(cols[0] + '\n')
        col2_file.write(cols[1] + '\n')

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

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

fname1 = 'col1.txt'
fname2 = 'col2.txt'

with open(fname1) as col1_file, open(fname2) as col2_file, open('merge.txt', 'w') as out_file:
    for col1_line, col2_line in zip(col1_file, col2_file):
        #out_file.write(col1_line + '\t' + col2_line + '\n') # 無駄な改行が入ってしまうのでこれではダメ
        out_file.write(col1_line.strip() + '\t' + col2_line.strip() + '\n')

14. 先頭からN行を出力

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

fname = 'hightemp.txt'
n = int(input('N--> '))

with open(fname) as f:
    for i, line in enumerate(f):
        if i >= n:
            break
        print(line.rstrip())

enumerate()関数を使うと、forループの中でリスト(配列)などのイテラブルオブジェクトの要素と同時にインデックス番号(カウント、順番)を取得できる。

15. 末尾のN行を出力

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

fname = 'hightemp.txt'
n = int(input('N--> '))

with open(fname) as f:
    lines = f.readlines()
    for line in lines[-n:]:
        print(line.strip())

readlines()でファイル内の全ての行を読み込むことができる。その後スライスで末尾からn行目までを取り出す。

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

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

import math

fname = 'hightemp.txt'
n = int(input('N--> '))

with open(fname) as f:
    lines = f.readlines()
    count = len(lines)
    unit = math.ceil(count / n) # 切り上げ 1ファイルあたりの行数
    
    for i, offset in enumerate(range(0, count, unit), 1):
        with open('child_{:02d}.txt'.format(i), mode='w') as out_file:
            for line in lines[offset:offset + unit]: # offset〜offset + unit(1ファイルあたりの行数)までをループ
                out_file.write(line)

offsetは0からcountまでunitずつステップアップする変数になっている。

range()関数は下記のような動作をする。

list(range(0, 30, 5))
→[0, 5, 10, 15, 20, 25]  

3つ目の引数を省略した場合は1がデフォルト設定されます。

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

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

fname = 'hightemp.txt'

with open(fname) as f:
    set_prefectures = set()
    for line in f:
        cols = line.split('\t') # タブで分割
        set_prefectures.add(cols[0]) # 分割した0列目(県名)を取得

for n in set_prefectures:
    print(n)

リスト型のように、set型も同じく複数の値を格納できる型です。 しかし、リスト型との決定的な違いは「重複した要素がない」「要素に順番がない」という部分にあります。

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

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

fname = 'hightemp.txt'

with open(fname) as f:
    lines = f.readlines()
    for line in f:
        cols = line.split('\t')[2] # タブで分割した3カラム目を取得
        lines.sort(key=cols)

for line in lines:
    print(line, end='')

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

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

from itertools import groupby

fname = 'hightemp.txt'
with open(fname) as f:
    lines = f.readlines()
    prefectures = [line.split('\t')[0] for line in lines]

# 都道府県で集計
prefectures.sort()
result = []
for prefecture, group in groupby(prefectures):
    result += [(prefecture, len(list(group)))]

# ソート
result.sort(key = lambda result: result[1], reverse=True)

# 出力
for n in result:
    print('{}({})'.format(n[0], n[1]))

まとめ

  • with open(ファイル名) as f:でファイルのオープンとクローズをやってくれる。
  • with open(ファイル名) as f, open(ファイル名, 'w') as col1_file:と続けて複数ファイルを操作することも可能。wオプションをつけると書き込みモードでファイルを操作できる。
  • strip()は、文字列の先頭と末尾から任意の文字列を削除してくれる。引数を省略した場合はスペース、タブ、改行を取り除いてくれる。
  • rstrip()は文字列の末尾から文字列を削除する。引数を省略した場合はスペース、タブ、改行を取り除いてくれる。
  • range()はリストを生成するのに便利。
  • set型はデータの重複を許可しない。

以上