summer_tree_home

Check iOでPython3をマスターするぜっ

Brackets (Electronic Station) 括弧をチェック

どんな問題?

Brackets
http://www.checkio.org/mission/brackets/

いつものように、ストーリーは華麗にスルー。

数式で正しく括弧が使われているかをチェックせよ。

  • 数式は'((5+3)*2+1)' のような文字列
  • 数字や記号は無視する
  • 括弧は { } ( ) [ ] の3種類
  • 正しく括弧が使われていれば True、でなければ False

例はこちら。

checkio("((5+3)*2+1)") == True
checkio("{[(3+1)+2]+}") == True
checkio("(3+{1-1)}") == False
checkio("[1+1]+(2*2)-{3/3}") == True
checkio("(({[(((1)-2)+3)-3]/3}-3)") == False

どうやって解く?

こういう構文解析って定番のやり方があるんだろうなと思って、「括弧 アルゴリズム」で検索したら、いっぱい出てきた。でも、先に見てしまうとつまらないので、最初は何も見ずに解いてみることにする。

まず、頭から1文字ずつチェックして、{ ( [ が出てきたら階層を1下げる、] ) } が出てきたら階層を1上げる、ペアの組み合わせが間違ってたらNG、文字列の最後まで調べて、終わったときに階層が0ならOK、こんな感じでいけそう。括弧をスタックに入れていく形になりそうだ。

Pythonでのスタックとキュー

ところで、PythonってQueueクラスはあるのに、Stackクラスはないのかな。スタックはどうすればいいんだ?
調べてみたら、どちらもリストを使えばいいだけらしい。

スタック(stack) list.append() で追加、list.pop()で最後の要素を取り出す
キュー(queue) list.append() で追加、list.pop(0)で最初の要素を取り出す

ただし、pop(0)は遅いので、collections.dequeクラスを使うとよい。
右に追加(append)、左に追加(appendleft)、右側から取り出す(pop)、左側から取り出す(popleft)メソッドがあって、スタックでもキューでも高速に処理できる。さらに反転(reverse)、ローテート(rotate)などもできる優れもの。(そういや、先日の Open Labyrinth でもdeque.rotate使ったっけ。)

queue.Queueクラスはマルチスレッド対応が必要なときに使う、ってことかな?

dequeクラス http://docs.python.jp/3.3/library/collections.html#collections.deque
Queueクラス http://docs.python.jp/3.3/library/queue.html

コード書いてみた

open_brackets = ('(', '[', '{')
close_brackets = (')', ']', '}')

def checkio(expr):
    stack = []
    for c in expr:
        if c in open_brackets:
            stack.append(c)
        elif c in close_brackets:
            if len(stack) == 0:
                return False  # 開く括弧が不足
            else:
                last = stack.pop()
                if open_brackets.index(last) != close_brackets.index(c):
                    return False  # 括弧の種類が違う
    if len(stack) != 0:
        return False  # 閉じる括弧が不足
    return True

テストはあっさりクリア。

でも、実際にこういう構文解析するときには、True/Falseじゃなくて、例外処理を使って、詳しいメッセージを出すよなぁ。そういう場合は、どう書けばいいんだろう?

def check_brackets(expr):
    # 問題があるときは例外発生させる。例、raise BracketError('}が足りません')
    # 何も問題なければ、return True
    pass

def checkio(expr):
    try:
        return check_brackets(expr)
    except BracketError as e:
        # ここでエラーメッセージを表示する
        return False

みたいな感じでいいのかな?

他の人の答え

だいたい似たような感じか。正規表現を使っている人もいた。

いろいろ見てると、自分のを直したくなってくる。まず、Pythonは定数を全部大文字で書くんだっけ?
(別に見た目の問題だけで、機能的には普通の変数と同じなんだが。)

OPEN_BRACKETS = ('(', '[', '{')

と書いた方がわかりやすいな。

OPEN_BRACKETS = '([{'

でもいいけど、タプルの方が見やすいか。

if open_brackets.index(last) != close_brackets.index(c):

この、括弧のペアチェックも、別の関数 is_pair_bracket() とした方がわかりやすいかな。

OPEN_BRACKETS = ('(', '[', '{')
CLOSE_BRACKETS = (')', ']', '}')

def is_pair_bracket(o, c):
    return OPEN_BRACKETS.index(o) == CLOSE_BRACKETS.index(c)

def checkio(expr):
    stack = []
    for ch in expr:
        if ch in OPEN_BRACKETS:
            stack.append(ch)  # push open bracket
        elif ch in CLOSE_BRACKETS:
            if len(stack) == 0:
                return False
            o = stack.pop()  # pop open bracket
            if not is_pair_bracket(o, ch):
                return False
    return len(stack) == 0

完成!