きょうこログ

初心忘れず、最後まで全力で頑張ります。よろしくお願いします。

言語処理100本ノック【第2章 後編】

課題研究で言語処理100本ノック 2015に取り組んでいます。前回の続きです。使用言語はPython3です。今回は第2章後半の5つを説明します。

15. 末尾のN行を出力

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

ソースコード

import sys

args = sys.argv

f = open("hightemp.txt", "r")
lines = f.readlines()

for i in range(len(lines) - int(args[1]), len(lines)):
  print(lines[i], end = "")

f.close()

前回説明した、14. 先頭からN行を出力 と全体的な構成がほとんど同じです。このプログラムでは末尾のN行を出力するので、range()内の値を工夫しました。range()は指定して引数に従ってリストオブジェクトを返す関数です。range()の第一引数に、テキストファイルの全体の行数 - 自然数Nを指定することで、末尾のN行の先頭の行番号から末尾までの行を出力します。

UNIXコマンド

tail -n N hightemp.txt

tailはテキストファイルの先頭のN行またはNバイトを抜き出すコマンドです。-nオプションを指定すると、指定したテキストファイルの先頭からN行を抜き出します。(headと同じ使い方!)

実行結果

N = 3 の時

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

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

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

ソースコード

import sys

args = sys.argv

f = open("hightemp.txt", "r")
lines = f.readlines()

n = 0

for i in range(int(args[1])):
  a = open("16out/out" + str(i) + ".txt", "w")
  for j in range((len(lines) + i) // int(args[1])):
    a.write(lines[n])
    n += 1
  a.close()

f.close()

この問題は、少しあやふやなところがあって、研究室内でもちょっとした議論になりました。議論の詳細はもうちょっと後に書きます。まずはプログラムの説明からです。

このプログラム上ではA行のテキストをN個のファイルにA / N 行ずつ均等に分けます。例えば、5行のテキストを3個のファイルに分けると下の図の様になります。

f:id:f_respectful12:20180304233612p:plain

この例で考えると、この問題のポイントは分けたあとのファイルが2行だったり1行だったりすることです。そこで分けた後の何番目のファイルに何行が対応するかを計算式で与えていきます。初めのfor分で何番目のファイルかを i に記録し、2つ目のfor文で ( ( 元のファイルの行数 len(lines) + 分けたあとのファイルの順番 i ) // 分けたあとのファイルの数 N ) を計算して、何行が対応するかを求めました。ここで//演算子は、切り捨て除算を表します。

実行結果

N = 7の場合(out0.txtからout6.txtまで生成される)

out0.txt

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

前述の通り、この問題には少しあやふやなところがあります。問題の解説文を確認すると、

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

とあります。N分割というのは一般的に全体をN個に分けることを言うのでは無いかと思っていました。しかし、この問題で言及されているsplitは、全体をN個ずつ分けるコマンドです。どっちが問題の本質なのか…もっと凄いやり方があるのか… よければ是非、ご指摘&ご意見頂けると嬉しいです。

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

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

ソースコード

f = open("hightemp.txt", "r")
lines = f.readlines()

a = set([])

for i in range(len(lines)):
  line = lines[i].split("\t")
  a.add(line[0])

print(a)

f.close()

問題で与えられたファイルhightemp.txtの1列目は、都道府県の名前です。2列目は重複しない都市の名前ですが、1列目は重複する場合があります。この問題では、その重複を考えず、1列目で得られる都道府県の種類を答えます。

はじめに、空のリストをset()でセットに変換します。セットは集合を表すデータ型なので、重複した値を持ちません。この性質を利用して、split("\t")で分割した行の1列目をadd()メソッドでセットに全て追加しました。

UNIXコマンド

cat hightemp.txt | awk '{print $1}' | sort | uniq

|(パイプ)は複数のコマンドを組み合わせる為に用いられます。|の左側の処理の出力を右側の処理に渡すのが主な働きです。ここでは、|を使ってcat awk sort uniq の4つのコマンドを組み合わせています。4つのコマンドそれぞれの働きは、下の表で説明します。

コマンド名 コマンドの働き
cat 指定したファイルを全て出力する
awk テキストのパターン処理を行う('{print $1}' は1列目を抜き出す)
sort 行ごとに辞書順に並び替える
uniq 連続して重複した行を1つにまとめる

実行結果

{'愛知県', '山梨県', '群馬県', '静岡県', '岐阜県', '和歌山県', '大阪府', '千葉県', '愛媛県', '高知県', '埼玉県', '山形県'}

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

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

ソースコード

import sys

args = sys.argv

f = open("hightemp.txt", "r")
lines = f.readlines()

a = {}

for i in range(len(lines)):
  line = lines[i].split("\t")
  a[lines[i]] = line[2]

b = sorted(a.items(), key=lambda x: x[1])

for i in reversed(b):
  print(i[0], end = "")

f.close()

このプログラムでは各行の内容とその行の3カラム目の内容を辞書で対応させて、3カラム目の内容でソートしました。辞書のソートにはsorted()関数とlambda記法を用いました。 sorted( 辞書名.items(), key = lambda x: x[1]) とすることで、[ ( 辞書のキー1, 辞書の値1 ), ( 辞書のキー2, 辞書の値2 ) ... ] といった、タプルのリストが返ってきます。 またreversed()はリストの順番を反転させる関数で、これによって降順のソートに並び替えられます。

UNIXコマンド




ごめんなさい。まだ出来てません。しばしお待ちください。

実行結果

高知県    江川崎   41  2013-08-12
岐阜県   多治見   40.9    2007-08-16
埼玉県   熊谷  40.9    2007-08-16
(中略)
埼玉県   鳩山  39.9    1997-07-05
千葉県   茂原  39.9    2013-08-11

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

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

ソースコード

import sys

args = sys.argv

f = open("hightemp.txt", "r")
lines = f.readlines()

a = {}

for i in range(len(lines)):
  line = lines[i].split("\t")
  a[line[0]] = a.get(line[0], 0) + 1

b = sorted(a.items(), key=lambda x: x[1])

for i in reversed(b):
  print(i)

f.close()

get()は辞書からキーを指定して値を取り出すメソッドです。get()メソッドの特徴はキーが登録されていない時にデフォルト値を返すという点で、デフォルト値はget()メソッドの第2引数に指定します。このプログラムでは、辞書に都道府県名と出現頻度を対応させました。都道府県名 (キー) に対応する出現頻度 () が無ければ0を返し、1ずつ足すことで、都道府県ごとの出現頻度を保持します。

UNIXコード

cut -f1 hightemp.txt | sort | uniq -c | sort --reverse

実行結果

('群馬県', 3)
('山梨県', 3)
('山形県', 3)
(中略)
('和歌山県', 1)
('高知県', 1)

実は今、横浜へインターンに来ています。自然言語処理は全く関係のない業界の会社です笑 でも、どんなことでも、面白いことが出来るのは凄く幸せだと思ってます。まだまだ頑張るぞー!