言語処理100本ノック第2章 解答と解説 Q12~Q13まで

こんにちは。福田直起です。

前回に引き続き、言語処理100本ノック2020を解いているアウトプットとして、「第2章 : UNIXコマンド」の10問のうち、Q12とQ13の2問を解説していきます。
他の問題の回答は、まとめページにリンクを貼ってあります。

解答・解説の前に

この記事の

  • 主な対象者と目的
  • 実行環境
  • 解答・解説を読む際の注意

は、以前の記事で説明していますので、解答・解説を読む前にこれらを知っておきたい、という方はそちらを参照していただくようお願いします。

また、長い解説は折りたたんであります。折りたたんである解説は「解説を読む」をクリックすると見ることができます。

解答・解説

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

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

Python

解答コード

(別解があるため、指定されたファイル名のあとに連番をつけています)

# 以降の解答でも使用
PATH_COL1_1 = "data/col1-1.txt"
PATH_COL2_1 = "data/col2-1.txt"

with open("popular-names.txt") as input_file:
    lines = input_file.readlines()
    
with open(PATH_COL1_1, mode="w") as col1, open(PATH_COL2_1, mode="w") as col2:
    for line in lines:
        split = line.split()
        col1.write(split[0] + "\n")
        col2.write(split[1] + "\n")
        
print("ファイルの書き出しが完了しました")

実行結果

ファイルの書き出しが完了しました

解説

解説を読む

Pythonで複数のファイルを同時に開く方法

with open(ファイルまでのパス) as 変数名, open(ファイルまでのパス) as 変数名:
のようにすることで、一行で複数のファイルを同時に開くことができ、ネストが深くなるのを防ぐことができます。

使用例:

with open(PATH_COL1_1, mode="w") as col1, open(PATH_COL2_1, mode="w") as col2:
    for line in lines:
        split = line.split()
        col1.write(split[0] + "\n")
        col2.write(split[1] + "\n")

また、以下にこの問題をtry finallyを使って解いた別解も載せておきます。

PATH_COL1_2 = "data/col1-2.txt"
PATH_COL2_2 = "data/col2-2.txt"

input_file = None
col1 = None
col2 = None

# try finallyを使う例
try:
    input_file = open("popular-names.txt")
    
    col1 = open(PATH_COL1_2, mode="w")
    col2 = open(PATH_COL2_2, mode="w")
    
    for line in input_file:
        split = line.split()
        col1.write(split[0] + "\n")
        col2.write(split[1] + "\n")
        
finally:
    if input_file is not None:
        input_file.close()
    
    input_file = None
    
    if col1 is not None:
        col1.close()
        
    col1 = None
    
    if col2 is not None:
        col2.close()
    
    col2 = None
    
print("ファイルの書き出しが完了しました")

withだとネストが深くなるか、一行が長くなるかのどちらかの問題を抱えることになってしまうので、今回のように複数のファイルを同時に開きたい場合は、try finallyを使うほうがわかりやすいかもしれません。 このtry finallyでファイルを開く方法は、以前の記事で詳しく解説しています。

解答コードに戻る

UNIXコマンド

解答コード

!echo "Pythonの出力ファイルとUNIXコマンドで同様の処理をした結果を比較:"
!echo "diff <(cut -f 1 popular-names.txt)" $PATH_COL1_1
!diff <(cut -f 1 popular-names.txt) $PATH_COL1_1

!echo
!echo "diff <(cut -f 2 popular-names.txt)" $PATH_COL2_1
!diff <(cut -f 2 popular-names.txt) $PATH_COL2_1

実行結果

Pythonの出力ファイルとUNIXコマンドで同様の処理をした結果を比較:
diff <(cut -f 1 popular-names.txt) data/col1-1.txt

diff <(cut -f 2 popular-names.txt) data/col2-1.txt

解説

解説を読む

UNIXcutコマンドについて

cut -f 列番号 対象ファイル
のように指定することで、タブ区切りのファイルから指定した列を切り出すことができるコマンドです。 Pythonなどのプログラミング言語に慣れている方は、列番号で一列目を指定したいなら0、二列目なら1……のように指定するものだと思うかもしれませんが、cutコマンドでは一列目なら1、二列目なら2を指定する必要があることに注意しましょう。のちの問題で使うsortコマンドなど、他のUNIXコマンドも同じような仕様のものがあります。

使用例:

cut -f 2 popular-names.txt

参考: 【 cut 】コマンド――行から固定長またはフィールド単位で切り出す:Linux基本コマンドTips(60) - @IT

解答コードに戻る

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

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

Python

解答コード

# 以降の解答でも使用
PATH_Q13 = "data/q13.txt"

# Q12で定義した定数を使用
with open(PATH_COL1_1) as col1:
    lines_col1 = col1.readlines()
    
with open(PATH_COL2_1) as col2:
    lines_col2 = col2.readlines()

with open(PATH_Q13, mode="w") as q13:
    for line1, line2 in zip(lines_col1, lines_col2):
        # 1列目のファイルの改行をタブ文字に置換することで、タブ区切りとしている
        q13.write(line1.replace("\n", "\t") + line2)
        
print("ファイルの書き出しが完了しました")

実行結果

ファイルの書き出しが完了しました

解説

コード中にもコメントしていますが、1列目のファイルの改行をタブ文字に置換することで、タブ区切りで並べたテキストファイルを出力するという工夫をしています。

UNIXコマンド

解答コード

!echo "Pythonの出力ファイルとUNIXコマンドで同様の処理をした結果を比較:"
!echo "diff <(paste" $PATH_COL1_1 $PATH_COL2_1")" $PATH_Q13_1
!diff <(paste $PATH_COL1_1 $PATH_COL2_1) $PATH_Q13_1

実行結果

Pythonの出力ファイルとUNIXコマンドで同様の処理をした結果を比較:
diff <(paste data/col1-1.txt data/col2-1.txt) data/q13-1.txt

解説

解説を読む

UNIXpasteコマンドについて

paste 結合したいファイル1 結合したいファイル2 のようにすることで、ファイル2つを行単位で結合した結果を出力することができるコマンドです。

使用例:

paste fileA.csv fileB.csv

行数の異なるファイルを結合した場合、以下のように一方だけにある行は片方だけのまま出力されます。

# 2行あるファイルと1行しかないファイルをpasteした例
paste paste_test.csv paste_test2.csv 
John    M 50000 2000    John    M 50000 2000
John    M 50000 2000

参考: 【 paste 】コマンド――複数のファイルを行単位で連結する:Linux基本コマンドTips(101) - @IT

解答コードに戻る

おわりに

以上、言語処理100本ノックの第2章のQ12とQ13について解説しました。
今回紹介したcutpasteは、データが保存されたテキストファイルを扱う際に覚えておくと役に立つと思います。

他の問題の回答は、まとめページにリンクを貼ってあります。

また、ブレインズコンサルティングでは一緒に働いてくれる仲間を募集しています。 ご興味のある方は、ぜひ一度採用サイトをご覧ください。