素人の言語処理100本ノック:19


言語処理100本ノック 2015の挑戦記録です。環境はUbuntu 16.04 LTS + Python 3.5.2 :: Anaconda 4.1.1 (64-bit)です。過去のノックの一覧はこちらからどうぞ。

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

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

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

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

出来上がったコード:

main.py
# coding: utf-8
from itertools import groupby
fname = 'hightemp.txt'

# 都道府県名の読み込み
lines = open(fname).readlines()
kens = [line.split('\t')[0] for line in lines]

# 都道府県で集計し、(都道府県, 出現頻度)のリスト作成
kens.sort()    # goupbyはソート済みが前提
result = [(ken, len(list(group))) for ken, group in groupby(kens)]

# 出現頻度でソート
result.sort(key=lambda ken: ken[1], reverse=True)

# 結果出力
for ken in result:
    print('{ken}({count})'.format(ken=ken[0], count=ken[1]))

実行結果:

端末
埼玉県(3)
山形県(3)
山梨県(3)
群馬県(3)
千葉県(2)
岐阜県(2)
愛知県(2)
静岡県(2)
和歌山県(1)
大阪府(1)
愛媛県(1)
高知県(1)

UNIXコマンド確認用のシェルスクリプト:

test.sh
#!/bin/sh

# 1カラム目でソートし、重複除去して件数付きで出力、その結果をソート
cut --fields=1 hightemp.txt | sort | uniq --count | sort --reverse

結果の確認:

端末
segavvy@ubuntu:~/ドキュメント/言語処理100本ノック2015/19$ ./test.sh
      3 山梨県
      3 山形県
      3 埼玉県
      3 群馬県
      2 千葉県
      2 静岡県
      2 岐阜県
      2 愛知県
      1 和歌山県
      1 大阪府
      1 高知県
      1 愛媛県

前問同様、出現頻度が同じところの並びは変わってしまうので、書式を合わせてdiffするところまではがんばらず、目視で確認しました。

内包表記

今回初めて内包表記を使ってみました。リストを作成する部分は、なんとか読み書きできるようになってきました。

集計処理

都道府県で集計する部分は、itertools.groupby()を利用しました。確認で使っているUNIXのuniq同様、事前にソートしておく必要があるのが注意点ですね。

問題の解釈の誤り

実は最初、問題の意味を取り違えていて、

各行の1列目の文字列の出現頻度を求め,その高い順に【各行を】並べて表示せよ.

だと思っていました。せっかくなので、そのコードと結果も載せておきます。

出来上がったコード(勘違い版):

main2.py
# coding: utf-8
from itertools import groupby
fname = 'hightemp.txt'


def get_ken(target):
    '''1行分のデータから都道府県部分を切り出す

    引数:
    target -- 1行分のデータ
    戻り値:
    都道府県の文字列
    '''
    return target.split('\t')[0]

# 読み込み
lines = open(fname).readlines()

# 都道府県で集計
lines.sort(key=get_ken)    # goupbyはソート済みが前提
groups = groupby(lines, key=get_ken)

# 集計結果を(都道府県, 出現頻度, 該当行のリスト)のリストに変換
result = []
for ken, group in groups:
    lines = list(group)
    result.append((ken, len(lines), lines))

# 出現頻度でソート
result.sort(key=lambda group: group[1], reverse=True)

# 結果表示
for group in result:
    for line in group[2]:
        print(line, end='')

get_ken()は1カラム目の都道府県を取り出す関数で、sort()groupby()の2箇所で使うのでラムダ式ではなく関数にしました。

実行結果(勘違い版):

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

 
20本目のノックは以上です。誤りなどありましたら、ご指摘いただけますと幸いです。