読者です 読者をやめる 読者になる 読者になる

括弧付の構文をパースする簡単なスクリプト /w python

プレーンファイルのデータ変換をいろいろとやっていると、表題のようなことが必要になることがある。

たとえば、

  • 各種設定ファイル
  • ログファイル
  • SQLとか、言語
  • そのほか ...

最近はxmlのものも多くて、その場合はxmlライブラリ(pythonならbeautifulsoupか)を使って組める。beautifulsoupはインターフェースがかなり直感的に書けて、何も考えないでも、書いた後 try & errorスクリプトを調整すれば割と要件を満たせる。

xmlではなく、言語のような独自の構文であったりする場合、データとして取り込むときに、とりあえず、文字列の中の括弧の構造を木構造にしたい。

例えば、次のような文があったとする。

  • 本日(今日のこと(日曜日だね))は晴天([めちゃ日差し強いよ]晴れのこと)なり。

括弧とその中の文を、"子の文"として、リストに入れる。元の文には別の文字を置き換える。pythonらしく"%"で置き換えるとすると、次の3タプル構造で文を表現する。

  • (  ""  ,  "本日%は晴天%なり。" ,  [...]  )

 タプルの1番目は、文全体がどんな括弧にくくられているかを示すもので、""の場合は地の文となる。タプルの2番目は置き換えた後の本文で、3番目は子の文のリストである。3番目のリストをちゃんと書くと、

  • [("(","今日のこと%",[("(","日曜日だね",)]),("(","%晴れのこと",[("[","めっちゃ日差し強いよ",)])]

となる。

下準備

parseするかっこの種類を辞書式でセットしておく。これのキーがタプル構造の1番目にはいる。また、文字列のどの部分までをparseしたかを取り出せるように、一文の3タプル構造を引数として、parseされた文字列の長さを返す関数を用意しておく。

bracketPair={u'(':u')','(':')','[':']',u'[':u']'}

def countlen(d):
    l= len(d[1]) -len(d[2])
    if d[0]!="":l+=len(d[0] + bracketPair[d[0]])
    return l+sum(map(countlen, d[2]))

直感的に書く

いずれにしても再帰を使うのだが、ある程度、手続き的に直感で書くとこんな感じ。whileで途中までたどる。

def parseBracket(hd,st):
    d=[hd,"",[]]
    idx=0
    while idx<len(st):
        if hd!="" and st[idx]==bracketPair[hd]:
            break
        if st[idx] in bracketPair:
            c =parseBracket(st[idx],st[idx+1:])
            d[2].append(c)
            d[1]+= "%"
            idx += countlen(c)
        else:
            d[1]+= st[idx]
            idx += 1
    return tuple(d)

再帰

再帰だけで書くとこうなる。

def parseBracket(hd,st):
    if st=="" or ( hd!="" and st[0]==bracketPair[hd] ):
        return (hd,"",[])
    if st[0] in bracketPair:
        c=parseBracket(st[0],st[1:])
        n=parseBracket(hd,st[countlen(c):])
        return (n[0],"%"+n[1],[c]+n[2])
    n=parseBracket(hd,st[1:])
    return (n[0],st[0]+n[1],n[2])

どっちがお好みかな?