似たような文字列を含んだデータをまとめる
説明しにくいんだけど、日付とデータが一行に入ってるデータファイルがあるとき、データの方だけ合計を取って、日付の共通部分は残しておきたい、みたいなことを考えた。
つまり、
#date time count time 2013/03/12 08:23:34 12 0.21 2013/03/15 08:40:20 13 0.22 2013/03/19 12:23:34 22 0.21 2013/03/23 00:41:44 12 0.20
みたいなデータを食べさせると
2013/03/XX XX:XXXXX 59 0.84
みたいな出力をしてほしい。
pythonでそういうスクリプトを書いた。各データについてまず整数として、次に浮動小数点としてパースを試みて、どっちもだめなら文字列と見なして、それらの共通部分を作る。
python2.1から入っているシーケンスの比較を行う組み込みライブラリである difflib を使った。
sum_rows.py
#!/usr/bin/env python # -*- coding: utf-8 -*- # sum_rows.py import difflib def try_convert_into_number(s): """try convert s into integer. if could not try convert into float. then give up (return None)""" try: return int(s) except ValueError: try: return float(s) except ValueError: return None def make_diff_to_XXX(s1,s2): """compare two strings s1 and s2 then return string marked with 'X' where s1 differs from s2""" s = difflib.SequenceMatcher(a=s1, b=s2) str='' back = 0 for block in s.get_matching_blocks(): # print "match at a[%d] and b[%d] of length %d" % block if block[0] != back: str = str + 'X' * (block[0]-back) str = str + s1[block[0]:block[0]+block[2]] back = block[0]+block[2] return str # make_diff_to_XXX("This is a pen", "This is a pan") def nonempty_lines(stream): """yield lines that is neither comment (begins by #) nor empty""" comment_char = '#' for l in stream: if l == "" or l[0] == comment_char: continue l = l.rstrip() yield l def sum_rows(stream): """sum numbers in each columns from lines in stream. raises error when a column contains both number and string.""" hist = [] max_cols=0 for l in nonempty_lines(stream): words = l.split() max_cols = max(max_cols, len(words)) if(max_cols>len(hist)): hist.extend([None]*(max_cols-len(hist))) for i, w in enumerate(words): num = try_convert_into_number(w) if num != None: hist[i] = (hist[i] or 0) + num elif not hist[i]: hist[i] = w # comment-out 2 lines below for large file else: hist[i] = make_diff_to_XXX(hist[i], w) return hist import sys if __name__ == "__main__": if len(sys.argv) <= 1: print "usage: {0} file('-' for stdin)".format(sys.argv[0]) exit(1) fin = sys.stdin if (sys.argv[1] == '-') else open(sys.argv[1]) hist = sum_rows(fin) for i in range(len(hist)): print hist[i], print
僕は
$ sum_rows datafile
とか
$ grep ^2014/05/ datafile | sum_rows -
みたいな感じで使っている。