言語処理100本ノック 第2章: UNIXコマンドの基礎

はじめに

今回は言語処理100本ノック 第2章: UNIXコマンドの基礎です。

これまでの

ohshige.hatenablog.com

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

Python 3.7.0でやっていきます。
問題の解釈違い、間違い等ありましたら、教えていただけると幸いです。

github.com

10. 行数のカウント

問題

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

解答&出力

with open("hightemp.txt") as f:
    n = 0
    for line in f:
        n += 1
    print(n)
24

ひとこと

readlinesで全体を取得してlenでも良かったのですが、hightemp.txtがとてつもなく大きいファイルだったときのために敢えて1行ずつループしてカウントしてみました。
特に意味のない配慮です。

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

問題

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

解答&出力

with open("hightemp.txt") as f:
    for line in f:
        print(line.replace("\t", " "), end="")

11の実行結果

高知県 江川崎 41 2013-08-12
埼玉県 熊谷 40.9 2007-08-16
岐阜県 多治見 40.9 2007-08-16
山形県 山形 40.8 1933-07-25
山梨県 甲府 40.7 2013-08-10
和歌山県 かつらぎ 40.6 1994-08-08
静岡県 天竜 40.6 1994-08-04
山梨県 勝沼 40.5 2013-08-10
埼玉県 越谷 40.4 2007-08-16
群馬県 館林 40.3 2007-08-16
群馬県 上里見 40.3 1998-07-04
愛知県 愛西 40.3 1994-08-05
千葉県 牛久 40.2 2004-07-20
静岡県 佐久間 40.2 2001-07-24
愛媛県 宇和島 40.2 1927-07-22
山形県 酒田 40.1 1978-08-03
岐阜県 美濃 40 2007-08-16
群馬県 前橋 40 2001-07-24
千葉県 茂原 39.9 2013-08-11
埼玉県 鳩山 39.9 1997-07-05
大阪府 豊中 39.9 1994-08-08
山梨県 大月 39.9 1990-07-19
山形県 鶴岡 39.9 1978-08-03
愛知県 名古屋 39.9 1942-08-02

ひとこと

全行一気にやる方法もあるかと思いますが、今回は1行ずつやっています。
line.strip().replace("\t", " ")として普通にprintしても良いと思います。
が、行末にタブがあった場合にstripしてしまうと除去されてしまうので、厳密には問題と異なってくるかなと思い、解答のように実装しました。
章始めに「タブ区切り形式で格納したファイル」とあり、そういうことも有り得ないので、特に意味のない配慮です。

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

問題

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

解答&出力

with open("hightemp.txt") as f, open("col1.txt", "w") as f_col1, open("col2.txt", "w") as f_col2:
    for line in f:
        col1, col2, *_ = line.strip().split("\t")
        f_col1.write(col1 + "\n")
        f_col2.write(col2 + "\n")

12の実行結果

> cat col1.txt
高知県
埼玉県
岐阜県
山形県
山梨県
和歌山県
静岡県
山梨県
埼玉県
群馬県
群馬県
愛知県
千葉県
静岡県
愛媛県
山形県
岐阜県
群馬県
千葉県
埼玉県
大阪府
山梨県
山形県
愛知県
> cat col2.txt
江川崎
熊谷
多治見
山形
甲府
かつらぎ
天竜
勝沼
越谷
館林
上里見
愛西
牛久
佐久間
宇和島
酒田
美濃
前橋
茂原
鳩山
豊中
大月
鶴岡
名古屋

ひとこと

with構文で一気にファイルを開いて、splitでカラム毎に分割し、それぞれをファイル出力しているだけです。

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

問題

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

解答&出力

with open("col1.txt") as f_col1, open("col2.txt") as f_col2, open("col1_and_col2.txt", "w") as f:
    for col1, col2 in zip(f_col1, f_col2):
        f.write(f"{col1.strip()}\t{col2.strip()}\n")

13の実行結果

> cat col1_and_col2.txt
高知県   江川崎
埼玉県   熊谷
岐阜県   多治見
山形県   山形
山梨県   甲府
和歌山県    かつらぎ
静岡県   天竜
山梨県   勝沼
埼玉県   越谷
群馬県   館林
群馬県   上里見
愛知県   愛西
千葉県   牛久
静岡県   佐久間
愛媛県   宇和島
山形県   酒田
岐阜県   美濃
群馬県   前橋
千葉県   茂原
埼玉県   鳩山
大阪府   豊中
山梨県   大月
山形県   鶴岡
愛知県   名古屋

ひとこと

これまた、with構文で一気にファイルを開いて、それぞれのカラムをf-stringで文字列化して、ファイル出力しているだけです。

14. 先頭からN行を出力

問題

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

解答&出力

N = int(input())
with open("hightemp.txt") as f:
    for _, line in zip(range(N), f):
        print(line.strip())
3
高知県   江川崎   41  2013-08-12
埼玉県   熊谷  40.9    2007-08-16
岐阜県   多治見   40.9    2007-08-16

ひとこと

自然数Nは標準入力で受け取るようにしました。
readlinesで全てをリストとして取得して、stop指定で出力しても良かったのですが、hightemp.txtがとてつもなく大きいファイルだったときのために(略)

15. 末尾のN行を出力

問題

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

解答&出力

N = int(input())
with open("hightemp.txt") as f:
    result = f.readlines()[-N:]
    print("\n".join([r.strip() for r in result]))
3
山梨県   大月  39.9    1990-07-19
山形県   鶴岡  39.9    1978-08-03
愛知県   名古屋   39.9    1942-08-02

ひとこと

自然数Nは標準入力で受け取るようにしました。
今回は特に無駄な配慮もなく、readlinesで全てをリストとして取得して、startのマイナス指定で最後のN行だけを出力するようにしました。

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

問題

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

解答&出力

N = int(input())
with open("hightemp.txt") as f:
    lines = f.readlines()
    n = (len(lines) + N - 1) // N
    split_list = [lines[i * n:(i + 1) * n] for i in range(N)]
    print("\n".join(["".join(x) for x in split_list]))

16の実行結果

5
高知県   江川崎   41  2013-08-12
埼玉県   熊谷  40.9    2007-08-16
岐阜県   多治見   40.9    2007-08-16
山形県   山形  40.8    1933-07-25
山梨県   甲府  40.7    2013-08-10

和歌山県    かつらぎ    40.6    1994-08-08
静岡県   天竜  40.6    1994-08-04
山梨県   勝沼  40.5    2013-08-10
埼玉県   越谷  40.4    2007-08-16
群馬県   館林  40.3    2007-08-16

群馬県   上里見   40.3    1998-07-04
愛知県   愛西  40.3    1994-08-05
千葉県   牛久  40.2    2004-07-20
静岡県   佐久間   40.2    2001-07-24
愛媛県   宇和島   40.2    1927-07-22

山形県   酒田  40.1    1978-08-03
岐阜県   美濃  40  2007-08-16
群馬県   前橋  40  2001-07-24
千葉県   茂原  39.9    2013-08-11
埼玉県   鳩山  39.9    1997-07-05

大阪府   豊中  39.9    1994-08-08
山梨県   大月  39.9    1990-07-19
山形県   鶴岡  39.9    1978-08-03
愛知県   名古屋   39.9    1942-08-02

ひとこと

自然数Nは標準入力で受け取るようにしました。
ファイル出力の指定ではなかったので空行区切りの標準出力にしました。
(len(lines) + N - 1) // Nでひとつの区切りが何行になるかを計算しています。
これは、math.ceil(len(lines) // N)len(lines) // N if len(lines) % N == 0 else len(lines) // N + 1と同じ処理で、簡単に書く方法です。

あとから気づいた(教えていただいた)ことですが、問題文としてはファイルをN分割と読めるものの、splitコマンドはファイルをN行ずつ分割するコマンドですね。
splitコマンドで確認せよということは後者の通りに実装する必要がありそうです。
ですが、リスト内包表記のnNを入れ替えれば簡単に修正は可能ということにして、今回はとりあえずこのままということにします。

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

問題

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

解答&出力

with open("hightemp.txt") as f:
    types = {line.strip().split()[0] for line in f}
    print(types)
{'和歌山県', '大阪府', '群馬県', '山形県', '埼玉県', '愛知県', '岐阜県', '千葉県', '静岡県', '愛媛県', '高知県', '山梨県'}

ひとこと

単純に、集合内包表記を使っているだけです。

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

問題

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

解答&出力

with open("hightemp.txt") as f:
    lines = [line.strip().split() for line in f]
    lines.sort(key=lambda x: float(x[2]), reverse=True)
    print("\n".join(["\t".join(line) for line in lines]))

18の実行結果

高知県    江川崎   41  2013-08-12
埼玉県   熊谷  40.9    2007-08-16
岐阜県   多治見   40.9    2007-08-16
山形県   山形  40.8    1933-07-25
山梨県   甲府  40.7    2013-08-10
和歌山県    かつらぎ    40.6    1994-08-08
静岡県   天竜  40.6    1994-08-04
山梨県   勝沼  40.5    2013-08-10
埼玉県   越谷  40.4    2007-08-16
群馬県   館林  40.3    2007-08-16
群馬県   上里見   40.3    1998-07-04
愛知県   愛西  40.3    1994-08-05
千葉県   牛久  40.2    2004-07-20
静岡県   佐久間   40.2    2001-07-24
愛媛県   宇和島   40.2    1927-07-22
山形県   酒田  40.1    1978-08-03
岐阜県   美濃  40  2007-08-16
群馬県   前橋  40  2001-07-24
千葉県   茂原  39.9    2013-08-11
埼玉県   鳩山  39.9    1997-07-05
大阪府   豊中  39.9    1994-08-08
山梨県   大月  39.9    1990-07-19
山形県   鶴岡  39.9    1978-08-03
愛知県   名古屋   39.9    1942-08-02

ひとこと

sortメソッドの、keyで3カラム目を小数としてソート対象に指定し、reverseで降順に指定して、全体をソートしています。
lambda式のfloatがなくても今回の場合は同じ結果にはなりますが、気温が100度などの地域があった場合に文字列としてのソートになるので正しくない結果になってしまいます。

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

問題

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

解答&出力

from collections import Counter

with open("hightemp.txt") as f:
    lines = [line.strip().split()[0] for line in f]
    counter = Counter(lines)
    print(sorted(counter.items(), key=lambda x: x[1], reverse=True))
[('埼玉県', 3), ('山形県', 3), ('山梨県', 3), ('群馬県', 3), ('岐阜県', 2), ('静岡県', 2), ('愛知県', 2), ('千葉県', 2), ('高知県', 1), ('和歌山県', 1), ('愛媛県', 1), ('大阪府', 1)]

ひとこと

collections.Counterを使えば簡単に出現頻度を求められます。

おわりに

Pythonは便利な機能が揃っているので、今回も特に難しいところはなく、さくさくと実装できました。
もしhightemp.txtがとてつもなく大きなファイルだった場合には、メモリを大量に使ってしまう書き方をしている箇所もあります。
そのような条件の場合にどう書けばいいかも改めて考えられれば、よりスキルアップにつながる気がします。

続き

まだ