以下の内容はhttps://engineeringnote.hateblo.jp/entry/python/algorithm-and-data-structures/regex_syntax_treeより取得しました。


正規表現(構文木の作成) (Pythonによるアルゴリズムとデータ構造)

brute forcing

本記事は、ソフトバンクパブリッシングから発行されている「」を参考にPythonアルゴリズムとデータ構造について学習していきます。

今回は、正規表現を解析し、構文木(Syntax Tree)を作成する方法について学んでいきます。

 

 

はじめに

正規表現によるパターンマッチを行うプログラムを作成するにあたり、以下の段階分けを行います。

 

  1. 正規表現を解析し、構文木を作成する
  2. 構文木から非決定性有限オートマトンを作成する
  3. 非決定性有限オートマトンから決定性有限オートマトンを作成する
  4. 決定性有限オートマトンを使いパターンマッチをする

 

今回は上記1の正規表現を解析し、構文木(Syntax Tree)を作成する方法について学びます。

 

正規表現とは

前回までで学んだ文字列探索では、対象の文字列とパターンが完全に一致した際に成功したとするものでした。

しかし、ファイルの拡張子が".txt"のファイル名を探索するなどの場合には、正規表現(Regular Expression)と呼ばれる記述方法を使うと便利です。

正規表現の歴史を遡ると、人間の神経系の仕組みに関する研究から始まり、1956 年に数学者のStephen Kleeneが正則集合と呼ばれる概念としてモデル化しました。

そして、UNIXの開発者であるKen ThompsonがテキストエディタQED正規表現によるパターンマッチを実装し、その後のgrepsedawk等に受け継がれていきます。

 

正規表現は、主に以下の3つを再帰的に操作していきます。

 

  • 連結(Concatenation)
  • 選択(Union)
  • 繰り返し(Closure(閉包))

 

また、正規表現の中でも特別な意味を持つ文字としてメタキャラクタ(Metacharacter)と呼ばれるものがあります。

基本的なメタキャラクタとして、以下の4つがあります。

 

  • ' * ':繰り返し
  • ' | ':選択
  • ' ( ':優先順位を変える
  • ' ) ':同上

 

連結とは

連結とは、正規表現が出現する順番を表すものです。

例えば、"ab"という正規表現は、文字a→文字bの順番で出現することを表します。

 

選択とは

選択とは、上記のメタ文字である" | "を使って表した正規表現です。

例えば、"a|b"という正規表現は、文字aもしくは文字bが一つ出現することを表します。

 

繰り返しとは

繰り返しとは、上記のメタ文字である" * "を表した正規表現です。

例えば、"a*"という正規表現は、文字aが0回以上連続して出現することを表します。

 

Python構文木を作成する

それでは、Python正規表現を解析し、構文木(Syntax Tree)を作成します。

なお、今回は上述の基本的な正規表現に加え、'+'の拡張表現のみを解析するものとします。

 

# syntax_tree.py
import sys

class Node:
    def __init__(self, op, left=None, right=None, char=None):
        self.op = op
        self.char = char
        self.left = left
        self.right = right

class SyntaxTree:
    def __init__(self, regex, debug=False):
        self.regex = list(regex)
        self.current_token = None
        self.current_token = None
        self.tree = None
        self.debug = debug

    def get_token(self):
        if len(self.regex) == 0:
            self.current_token = 'end'
            return
        c = self.regex.pop(0)
        if c == '|':
            self.current_token = 'union'
        elif c == '(':
            self.current_token = 'lpar'
        elif c == ')':
            self.current_token = 'rpar'
        elif c == '*':
            self.current_token = 'star'
        elif c == '+':
            self.current_token = 'plus'
        else:
            self.current_token = 'char'
            self.token_char = c

    def make_node(self, op, left, right):
        node = Node(op, left, right)
        return node

    def make_atom(self, c):
        atom = Node('char', char=c)
        return atom

    def primary(self):
        if self.current_token == 'char':
            x = self.make_atom(self.token_char)
            self.get_token()
        elif self.current_token == 'lpar':
            self.get_token()
            x = self.parse_regex()
            if self.current_token != 'rpar':
                self.syntax_error("Close paren is expected.")
            self.get_token()
        else:
            self.syntax_error("Normal character or open paren is expected.")
        return x

    def factor(self):
        x = self.primary()
        if self.current_token == 'star':
            x = self.make_node('closure', x, None)
            self.get_token()
        elif self.current_token == 'plus':
            x = self.make_node('concat', x, self.make_node('closure', x, None))
            self.get_token()
        return x

    def term(self):
        if self.current_token in ['union', 'rpar', 'end']:
            x = self.make_node('empty')
        else:
            x = self.factor()
            while self.current_token not in ['union', 'rpar', 'end']:
                x = self.make_node('concat', x, self.factor())
        return x

    def parse_regex(self):
        x = self.term()
        while self.current_token == 'union':
            self.get_token()
            x = self.make_node('union', x, self.term())
        return x

    def make_tree(self):
        self.get_token()
        self.tree = self.parse_regex()
        if self.current_token != 'end':
            print("Extra character at end of pattern.")
            sys.exit(1)
        if self.debug:
            print("------ Syntax Tree ------")
            self.dump_tree(self.tree)
            print()
        return self.tree

    def dump_tree(self, tree):
        op = tree.op
        if op == 'char':
            print("'{}'".format(tree.char), end="")
        elif op == 'concat':
            print("(concat ", end="")
            self.dump_tree(tree.left)
            print(" ", end="")
            self.dump_tree(tree.right)
            print(")", end="")
        elif op == 'union':
            print("(or ", end="")
            self.dump_tree(tree.left)
            print(" ", end="")
            self.dump_tree(tree.right)
            print(")", end="")
        elif op == 'closure':
            print("(closure )", end="")
            self.dump_tree(tree.left)
            print(")", end="")
        elif op == 'empty':
            print("Empty")
        else:
            print("This cannot happen in ")
            sys.exit(1)

r = SyntaxTree("a(a|b)*a", debug=True)
r.make_tree()

 

動作確認 

それでは上記で作成したスクリプトを実行してみます。

今回は a(a|b)*a という正規表現から構文木を作成してみます。

fig1. [tex:a(a|b)*a] の構文木

fig1. a(a|b)*a構文木

 

 > python syntax_tree.py
 ------ Syntax Tree ------
 (concat (concat 'a' (closure )(or 'a' 'b'))) 'a')

 

ダンプしてみると、問題なく構文木を作成することができました。

 

参考書籍

定本 Cプログラマのためのアルゴリズムとデータ構造 (SOFTBANK BOOKS)




以上の内容はhttps://engineeringnote.hateblo.jp/entry/python/algorithm-and-data-structures/regex_syntax_treeより取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14