2018年02月22日

言語処理100本ノックでPython入門 #16 - リストをN分割

  
言語処理100本ノック 2015の問題16に挑戦です。

■問題


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

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


■ 最初に書いたPythonのコード
import sys
import os.path
def split(filename, n): with open(filename, 'r', encoding='utf8') as fin: lines = fin.readlines() total = len(lines) div = [(total + i) // n for i in range(n)] start = 0 for i in range(n): end = start + div[i] with open(getFileName(filename, i+1), 'w', encoding='utf8') as fout: fout.writelines(lines[start:end]) start = end
def getFileName(filename, n): name, ext = os.path.splitext(filename) return name + str(n) + ext
def main(): n = int(sys.argv[1]) split('hightemp.txt', n)
if __name__ == '__main__': main()

■ 本日のトピック等

演算子 //

除算をもとめる演算子ですが、結果は小数点以下は切り捨てられます。


total個をn個に分割する方法
div = [(total + i) // n for i in range(n)]
誰が考えたのか、とてもすっきりと書けますね。
ネットで調べていたら見つけました。 多少効率悪いけどそれを補って余りある簡潔さです。
例えば、tital = 24, n = 5 の場合は、divは`[4, 5, 5, 5, 5]`となります。


ファイルパスを拡張子とそれ以外に分割する
import os.path
name, ext = os.path.splitext(filename)
extには、ピリオドを含んだ文字列が返ります。
name + ext = filenameです。


ブロック内の変数のスコープ

with内で宣言?した変数は、withの外でも使えるみたいです。
というか、withに限らず、for文やif文でも同じようです。

この辺りは、JavaScriptのvar変数みたいですね。C#とは明らかに違っています。Pythonはかなり緩いですね。どちらがいいんだろう。


■次に書いたPythonのコード

もし、数十万行ある巨大なファイルだったら?
昔の人間なので、全部メモリに入れるのは気が引けます。ということで省メモリバージョンも作成しました。
2度読みしないといけないのだけど、まあ仕方がないです。
import sys
import os.path

def getFileName(filename, n): name, ext = os.path.splitext(filename) return name + str(n) + ext
def countLine(filename): count = 0 with open(filename, 'r', encoding='utf8') as fin: for _ in fin: count += 1 return count
def split(filename, n): total = countLine(filename) div = [(total + i) // n for i in range(n)] with open(filename, 'r', encoding='utf8') as fin: i = 0 for m in div: i += 1 with open(getFileName(filename, i), 'w', encoding='utf8') as fout: for _ in range(m): line = fin.readline() fout.write(line)
def main(): n = int(sys.argv[1]) split('hightemp.txt', n)
if __name__ == '__main__': main()

■ 本日のトピック等(2)

インクリメント演算子が無い
i++
って書いたら、エラーになりました
i += 1
は、大丈夫みたいです。


■ 結果

3分割した場合の結果です。

hightemp1.txt
高知県	江川崎	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

hightemp2.txt
埼玉県	越谷	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

hightemp3.txt
岐阜県	美濃	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