Pythonで逆ポーランド記法プログラムを書いてみた

逆ポーランド記法というのは、たとえば

2 + 3

という式を

2 3 +

と表記する記法、つまり2つの演算対象の後ろに演算子をつけるように変換する記法のこと。Lispの逆(つまりポーランド記法?)バージョンといえるのかな。

(a+b*c)*(p-q)

なら(プログラムでは括弧をはずして)

abc*+pq-*

となる。

以下プログラム

#2007/10/17ちょっと修正しました
def reverse_polish():
    LIST = list()    #リスト作成
    read_a_line(LIST) #一行読み込む
    skip_spaces(LIST) #空欄は削除する
    #ここから逆ポーランド変換
    List1 = []    #演算数を入れるリスト&逆ポーランド記法の結果が入る
    List2 = ['@'] #演算子を入れるリスト
    import string
    i,n = 0,len(LIST)-1
    while i <= n:
        if LIST[i] == "$": #$が入力されたら終了
            break
        if not (LIST[i] in string.letters or LIST[i] in string.digits or LIST[i] in string.punctuation):
            reverse_polish(LIST)
        elif LIST[i] in string.letters or LIST[i] in string.digits:
            List1.append(LIST[i])
        elif LIST[i] == '(':
            List2.append(LIST[i])
        elif LIST[i] == ')':
            while not List2[len(List2)-1] == '(':
                List1.append(List2[len(List2)-1])
                del(List2[len(List2)-1])
            del(List2[len(List2)-1])
        elif LIST[i] in ('+','-','*','/'):
            while less_or_equal_prec(LIST[i],List2[len(List2)-1]):
                List1.append(List2[len(List2)-1])
                del(List2[len(List2)-1])
            List2.append(LIST[i])
        i = i + 1
    while not List2[len(List2)-1] == '@':
        List1.append(List2[len(List2)-1])
        del(List2[len(List2)-1])
    print List1 #逆ポーランド変換ここまで
    #ここから数字を計算する
    i,n = 0,len(List1)-1
    List3 = []  #ここに計算結果が入る
    while i <= n:
           if List1[0] in string.digits:
                List3.append(List1.pop(0))
           elif List1[0] == '+':
                temp = int(List3.pop()) + int(List3.pop())
                List3.append(temp)
                del(List1[0])
           elif List1[0] == '-':
               temp = int(List3.pop(-2)) - int(List3.pop())
               List3.append(temp)
               del(List1[0])
           elif List1[0] == '*':
               temp = int(List3.pop()) * int(List3.pop())
               List3.append(temp)
               del(List1[0])
           elif List1[0] == '/':
               temp = int(List3.pop(-2)) / int(List3.pop())
               List3.append(temp)
               del(List1[0])
           i = i + 1
    if len(List3) == 1: #1個だけの時はちゃんと計算ができた
            return List3[0]

まずLIST = list()で空リストLISTを作る。そしてinitialise(list)関数(は必要なーし)

def initialise(list):
    i,n = 0,len(list)-1
    while i <= n:
        del(list[0])
        i = i + 1

でリストをイニシャライズする。次にread_a_line(list)関数

def read_a_line(list):
    inp = raw_input()
    for i in inp:
        list.append(i)

で、読み込んだ1行の式をリストへ入れる。さらにその中にスペースを入れないためにskip_spaces(list)関数

def skip_spaces(list):
    i = 0
    while i <= len(list)-1:
        if list[i] == " ":
            del(list[i])
        i = i + 1        

でスペースを削除する。

いま、残ったリストLISTには読み込まれた式がスペースなしに入ってる。たとえば

A  + B * C

と入力すると、

['A', '+', 'B', '*', 'C']

という風にリストLISTに格納されてる。

ここからいよいよ逆ポーランド変換するのだけど、詳細は割愛。ちなみに
while less_or_equal_prec(a,b)については

def less_or_equal_prec(a,b):
    #aの優先順位がbの優先順位より低いか等しいとき真となる
    if a in ('*','/') and b in ('*','/'):
        return True
    elif a in ('+','-') and b in ('+','-','*','/'):
        return True
    else: return False

のような関数。つまり'+'や'-'は'*'や'/'よりも優先順位が低い(2+3*4のときは*を先に計算しなければならないということ)から、より高い優先順位の演算子が出るまでList2に入ってなければならない。

#ここから数字を計算する

のところからは、入力された式が全部数字のとき、計算結果を返すプログラム。


実行結果は

>>> reverse_polish()
3+4
['3', '4', '+']
7
>>> reverse_polish()
A + B * C
['A', 'B', 'C', '*', '+']
>>> reverse_polish()
2+3*3
['2', '3', '3', '*', '+']
11
>>> reverse_polish()
(a+b*c)*(p-q)
['a', 'b', 'c', '*', '+', 'p', 'q', '-', '*']


読みづらくて、へんてこなプログラムかもしれないけど、いい練習にはなったかな。
ただ、このプログラムは1文字単位だけしか認識しない。つまり

apple + lemmon

とすると、おかしなことになる

['a', 'p', 'p', 'l', 'e', 'l', 'e', 'm', 'm', 'o', 'n', '+']

世に出てるプログラミング言語だとvarとかp1とか任意の変数を指定できるけど、これはちっともできてない。まだよく分からないけど、これは集合という考え方を使えばうまくいくらしい・・・。もっと勉強します。