too late amatuar programmer

[python] テーブルデータを扱う方法を考える

2010-07-12 by ebon | Lavel:

unixやlinuxで作業していると、あるコマンドで得た、テーブルデータ式の出力結果を、
管理する良い方法がないか考えるシーンがたまにだが、ある.
e.g) df や mount の出力結果を管理したい時 etc..

pythonの学習を兼ねて、テーブルデータ形式の出力結果を扱う方法を考えてみる.
簡単な例を元に考える.
ファイルや、標準出力で何らか以下のようなデーブルデータ形式のデータを得たとする.

X Y Z
a 1 2
b 3 4
c 5 6

多次元リストにして扱う という方法が通常の?やり方だと思う.
その場合、リストに対して該当列を行番号で指定する方法か、awkのように行ごと処理する
というように、欲しいデータが得られるがやり方がやや気に入らない.
やはりここは、行×列 という形式で各項目を指定することで値を得るようにしたい.

そこで,このテーブル形式のデータについて、

hogehogeのfuga

というより自然な形でアクセスできるようにしたい.


そのためには、 多次元連想配列にしておくと良さそう.
# pythonでは連想配列はディクショナリという.(ハッシュの方が呼びやすいな..辞書と呼ぶものなんだか..)



目的のデータ構造;ディクショナリのディクショナリ

を得るために、分解していく.
以下の処理が必要と考えられる.

  1. 行ごとに読み込み、改行ごとに行のリストを得る -> readlinesメソッド
  2. 各行について、特定の文字列で分割し、列ごとのリストを得る -> splitメソッド
  3. 各行について、特定の列の値をkeyとした、残りの列をvalueとするディクショナリを作る 

    1,2は普通によくやる事で、この時点で、デーブル形式のデータが得られる.
    その先でやりたいことは、特定のkeyで呼び出すようにしたいという事だ.
    そのため、単純な"多次元リスト"から、"多次元辞書"の形式にしたい.

    3が厄介だ..
    少し考えると、行処理×列処理の2重ループなど、面倒な事になると分かる.

    目的のデータ構造を得るため、3について、以下のステップに分解して考える.
    やり方は煩雑かもしれない.

    ⅰ. 列項目となるレコード、実際のデータレコードを決定する
    ⅱ. 特定の列の値をkeyとした、残りの列をvalueとするディクショナリ
      というデータ構造を得る.そのために以下ステップを踏む
       a) "行×列"のディクショナリにする
       b) a) の各行を、("特定の列の値",{項目:その他の列の値})というペアにする
       c) dict() によりディクショナリにする
    

    以下、サンプル.

    # テーブルデータを、リストのリストから、 特定の列の値をkeyとした、残りの列をvalueとするディクショナリに変換する
    # table_dict['a']['Y'] ==> 1 という形で、値を取得する方法を実現する.
    
    table_list = [
        ['X','Y','Z'],
        ['a',1,2],
        ['b',3,4],
        ['c',5,6],
    ]
    
    # ⅰ. 列項目となるレコード、実際のデータレコードを決定する
    termrecord,valuerecords = table_list[0],table_list[1:]
    
    # ⅱ. 特定の列の値をkeyとした、残りの列をvalueとするディクショナリ
    # というデータ構造を得る.そのために以下ステップを踏む
    # a) "行×列"のディクショナリにする
    # b) a) の各行を、("特定の列の値",{項目:その他の列の値})というペアにする
    # c) dict() によりディクショナリにする
    
    # ⅱ-a)
    table_dict_tmp 
        = [ dict(zip(termrecord,valuerecord)) for valuerecord in valuerecords ]
    
    # ⅱ-b)
    table_dict_pair 
        = [ devide_dict_to_pair(dc,'X') for dc in table_dict_tmp ]
    
    # ⅱ-c)
    table_dict = dict(table_dict_pair)
    
    # 各行を、("特定の列の値",{項目:その他の列の値})というペアにする関数
    def devide_dict_to_pair(dc,targetKey):
        '''
        porpose : 辞書を、特定のkeyのvalueを全体のkeyとし、それ以外のkey:valueの辞書を要素としたペアに変換する
        contract: devide_dict :: {a} -> a ==> (a,{})
        example : devide_dict({'X':'a','Y':'b'},'X') ==> ('a',{'Y':b})
        '''
        rest_dc = {}
        mainkey = dc[targetKey]
        for key,value in dc.items():
            if key != targetKey:
                rest_dc[key] = value
        return (mainkey,rest_dc)
    
    # test : devide_dict_to_pair()
    testdc = {'Y': 1, 'X': 'a', 'Z': 2}
    test_devide_dict = devide_dict_to_pair(testdc,'X')
    
    

    小生の説明、コードは分かりにくい.
    流れを自分で理解するため、いちいち変数にあてている.
    本当はわざわざ変数化しないで、続きで処理を記述するのが良いんだろうか..
    # ちなみに、内包表記は便利すぎて、ついネストさせまくって記述したくなってしまう

    データ構造の変遷を図示しておく.
    少しは意図が分かりやすくなることを期待する..

    ・デーブルデータ
    [
        [X,Y,Z],
        [a,1,2],
        [b,3,4],
        [c,5,6],
    ]
    
    # ⅰ. 列項目となるレコード、実際のデータレコードを決定する
    ↓ keyrecord,valuerecords = records[0],records[1:]
    
    keyrecord    = [X,Y,Z]
    valuerecords = [[a,1,2],[b,3,4],[c,5,6]]
    
    # ⅱ. "特定の列の値をkeyとした、残りの列をvalueとするディクショナリ"というデータ構造を得る
    
    # a) "行×列"のディクショナリにする
    ↓ [ dict(zip(keyrecord,x)) for x in valuerecords ] 
    
    [
        {'Y': 1, 'X': 'a', 'Z': 2},
        {'Y': 3, 'X': 'b', 'Z': 4},
        {'Y': 5, 'X': 'c', 'Z': 6},
    ]
    
    # b) a) の各行を、("特定の列の値",{項目:その他の列の値})というペアにする
    ↓ devide_dict_to_pair([{a}],'key') for each record
    
    [
        ('a', {'Y': 1, 'Z': 2}),
        ('b', {'Y': 3, 'Z': 4}),
        ('c', {'Y': 5, 'Z': 6}),
    ]
    
    # c) dict() によりディクショナリにする
    ↓ dict()
    
    {
        'a': {'Y': 1, 'Z': 2},
        'c': {'Y': 5, 'Z': 6},
        'b': {'Y': 3, 'Z': 4},
    }
    
    

    目的の、"特定の列の値をkeyとした、残りの列をvalueとするディクショナリ"
    というデータ構造が得られた.

    これで、"xx"の"xx" といった自然な形で値を取ることができる.
    上記データを、tabel_dictとしたとき、
    "aのYの値" は以下のように取得する.

    table_dict['a']['Y'] ==> 1
    

    ここまでやってきたものの、意味があったのか甚だ疑問..
    わざわざディクショナリにしなくとも、textによるfladDBのような形にしておき、
    呼び出すquery関数側で請け負わせる方が、柔軟性があって、使い勝手も良さそうな気がする.
    しかも、データが大量になったときに遅いだろうから、実質あまり使えなそう...
    良いプラクティスを考えるにはスキルも経験も足りないかな。。

    0 comment:

    Post a Comment