morikomorou’s blog

自分が学んだことなどの備忘録的なやつ

【python】言語処理100本ノック2020を解く(第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章終わりました。
これからは難しくなっていきそうなので不安になってきました。