LoginSignup
0
1

逆ポーランド記法への変換を実装してみる

Posted at

逆ポーランド記法への変換を実装してみる

なんらかの言語を作りたくなったのでflexとbisonを調べていたら、結局諸々の実装は自力でやらないといけないことが分かり、そんなに作るなら全部自分で作ったほうが楽じゃない?と思いスタックマシンの作り方とか調べてました
逆ポーランド記法が良いとのことで調べて実装してみました

参考にしたサイト
https://qiita.com/phenan/items/df157fef2fea590e3fa9
https://zenn.dev/peg/articles/79a4823d86c21e

中置記法の計算式文字列を逆ポーランド記法(後置記法)に変換してから計算するプログラムを作成しました
四則演算と括弧に対応しています
以下ソース

import re
import sys
import enum
import typing

class TokenIdDefine(enum.Enum):
  NOP = enum.auto()
  NUMBER = enum.auto()
  PLUS = enum.auto()
  MINUS = enum.auto()
  MULTIPLE = enum.auto()
  DIVISION = enum.auto()
  BEGIN_BRACKET = enum.auto()
  END_BRACKET = enum.auto()
  ERROR = enum.auto()

def indexing(array, indexes):
  res = []
  for i in indexes:
    res.append(array[i])
  return res

def matchToken(tokenRules: typing.List[typing.List], infixNotation: str, index: int) -> typing.Tuple[typing.Any, int]:
  for tokenRule in tokenRules:
    regexp, token = tokenRule
    match = re.match(regexp, infixNotation[index:])
    if match != None:
      return token, match.end()
  return TokenIdDefine.ERROR, 0

def toTokensAndValues(infixNotation):
  tokenRules = [
      ["^\s+", TokenIdDefine.NOP],
      ["^\d+", TokenIdDefine.NUMBER],
      ["^\+", TokenIdDefine.PLUS],
      ["^\-", TokenIdDefine.MINUS],
      ["^\*", TokenIdDefine.MULTIPLE],
      ["^\/", TokenIdDefine.DIVISION],
      ["^\(", TokenIdDefine.BEGIN_BRACKET],
      ["^\)", TokenIdDefine.END_BRACKET],
  ]
  tokens = []
  values = []
  index = 0
  while(index < len(infixNotation)):
    token, size = matchToken(tokenRules=tokenRules, infixNotation=infixNotation, index=index)
    if token == TokenIdDefine.ERROR or size <= 0:
      print("error on {} character".format(index))
      break
    value = infixNotation[index:index + size]
    tokens.append(token)
    values.append(value)
    index += size
  return tokens, values

def infixNotationToReversePolishNotation(tokens):
  tokenIdToInputPriority = {
      TokenIdDefine.NUMBER:        9,
      TokenIdDefine.PLUS:          4,
      TokenIdDefine.MINUS:         4,
      TokenIdDefine.MULTIPLE:      6,
      TokenIdDefine.DIVISION:      6,
      TokenIdDefine.BEGIN_BRACKET: 8,
      TokenIdDefine.END_BRACKET:   0,
      TokenIdDefine.NOP:           1,
      TokenIdDefine.ERROR:         999,
  }
  tokenIdToStackPriority = {
      TokenIdDefine.NUMBER:        9,
      TokenIdDefine.PLUS:          5,
      TokenIdDefine.MINUS:         5,
      TokenIdDefine.MULTIPLE:      7,
      TokenIdDefine.DIVISION:      7,
      TokenIdDefine.BEGIN_BRACKET: 1,
      TokenIdDefine.END_BRACKET:   0,
      TokenIdDefine.NOP:           1,
      TokenIdDefine.ERROR:         999,
  }
  output = []
  stack = []
  size = len(tokens)
  inputIndex = 0
  while(inputIndex < size):
    inputToken = tokens[inputIndex]
    if inputToken == TokenIdDefine.NOP:
      inputIndex += 1
      continue
    if inputToken == TokenIdDefine.END_BRACKET:
      while len(stack) > 0 and (tokens[stack[-1]] != TokenIdDefine.BEGIN_BRACKET):
        poped = stack.pop()
        output.append(poped)
      if len(stack) > 0:
        stack.pop()
      inputIndex += 1
      continue

    if len(stack) == 0 or tokenIdToInputPriority[inputToken] > tokenIdToStackPriority[tokens[stack[-1]]]:
      stack.append(inputIndex)
      inputIndex += 1
    else:
      poped = stack.pop()
      output.append(poped)
  output.extend(list(reversed(stack)))
  return output

def calcReversePolishNotation(size, rpnTokens, rpnValues):
  stack = []
  for i in range(size):
    token = rpnTokens[i]
    value = rpnValues[i]
    if token == TokenIdDefine.NUMBER:
      stack.append(float(value))
    else:
      rhs = stack.pop()
      lhs = stack.pop()
      if token == TokenIdDefine.PLUS:
        stack.append(lhs + rhs)
      elif token == TokenIdDefine.MINUS:
        stack.append(lhs - rhs)
      elif token == TokenIdDefine.MULTIPLE:
        stack.append(lhs * rhs)
      elif token == TokenIdDefine.DIVISION:
        stack.append(lhs / rhs)
  return stack[-1]


def calc(infixNotation):
  tokens, values = toTokensAndValues(infixNotation)
  output = infixNotationToReversePolishNotation(tokens)
  rpnTokens = indexing(tokens, output)
  rpnValues = indexing(values, output)
  answer = calcReversePolishNotation(len(rpnTokens), rpnTokens, rpnValues)
  return infixNotation, rpnTokens, rpnValues, answer

def test1():
  samples = [
      ["(1 + 2) * 3", "1 2 + 3 *"],
      ["(4 + 5) * (6 - 3)", "4 5 + 6 3 - *"],
      ["((7 - 2) + 5) * 4", "7 2 - 5 + 4 *"],
      ["8 * (3 + (2 * 5))", "8 3 2 5 * + *"],
      ["(4 * 5) + (3 - 2)", "4 5 * 3 2 - +"],
      ["3 + 4 * 2 / (1 - 5)", "3 4 2 * 1 5 - / +"],
      ["(10 + 5) * (8 - (2 + 6))", "10 5 + 8 2 6 + - *"],
      ["(10 / 2) - (3 + (4 * 2))  ", "10 2 / 3 4 2 * + -"],
      ["(5 + (5 * 10) / 2) - 3", "5 5 10 * 2 / + 3 -"],
      ["6 + (4 * (3 + 2)) - 7", "6 4 3 2 + * + 7 -"],
  ]
  for (a, b) in samples:
    infixNotation, rpnTokens, rpnValues, answer = calc(a)
    answerConfirm = eval(infixNotation)
    rpnString = " ".join([str(token) for token in rpnTokens])
    print("infix notation {}, rpn {}, answer={}, confirm answer={}".format(infixNotation, b, answer, answerConfirm))

def main():
  test1()

if __name__ == "__main__":
  main()

# https://qiita.com/phenan/items/df157fef2fea590e3fa9
# https://zenn.dev/peg/articles/79a4823d86c21e

主要部分だけ解説

infixNotationToReversePolishNotation 関数について解説していきます
infixNotationToReversePolishNotation 関数は構文解析済みのトークン配列を受け取り逆ポーランド記法のルールで並び替えたインデックスの配列を返します

優先度定義

参考サイトに記載してあるように数値、演算子に優先度を定義します、input -> (stack or output) の判定に使用します

# infixNotationToReversePolishNotation 関数、優先度定義
def infixNotationToReversePolishNotation(tokens):
  tokenIdToInputPriority = {
      TokenIdDefine.NUMBER:        9,
      TokenIdDefine.PLUS:          4,
      TokenIdDefine.MINUS:         4,
      TokenIdDefine.MULTIPLE:      6,
      TokenIdDefine.DIVISION:      6,
      TokenIdDefine.BEGIN_BRACKET: 8,
      TokenIdDefine.END_BRACKET:   0,
      TokenIdDefine.NOP:           1,
      TokenIdDefine.ERROR:         999,
  }
  tokenIdToStackPriority = {
      TokenIdDefine.NUMBER:        9,
      TokenIdDefine.PLUS:          5,
      TokenIdDefine.MINUS:         5,
      TokenIdDefine.MULTIPLE:      7,
      TokenIdDefine.DIVISION:      7,
      TokenIdDefine.BEGIN_BRACKET: 1,
      TokenIdDefine.END_BRACKET:   0,
      TokenIdDefine.NOP:           1,
      TokenIdDefine.ERROR:         999,
  }
  output = []
  stack = []
  size = len(tokens)
  inputIndex = 0


終わり括弧の出現処理

入力に終わり括弧が出現したら、stackを始まりの括弧が現れるまで取り出してoutputに追加します

  while(inputIndex < size):
    inputToken = tokens[inputIndex]
    if inputToken == TokenIdDefine.NOP:
      inputIndex += 1
      continue
    if inputToken == TokenIdDefine.END_BRACKET:
      while len(stack) > 0 and (tokens[stack[-1]] != TokenIdDefine.BEGIN_BRACKET):
        poped = stack.pop()
        output.append(poped)
      if len(stack) > 0:
        stack.pop()
      inputIndex += 1
      continue

入力とスタックの優先度を見て stack か output を振り分ける

冒頭で宣言した数値・演算子の優先度を比較して「stackに追加するか」か「stackからoutputに追加するか」を振り分けます

    if len(stack) == 0 or tokenIdToInputPriority[inputToken] > tokenIdToStackPriority[tokens[stack[-1]]]:
      stack.append(inputIndex)
      inputIndex += 1
    else:
      poped = stack.pop()
      output.append(poped)

最後はstackをひっくり返してoutputにくっつける

最後にstackに残っている数値をoutputにひっくり返してからくっつけます、これは popしてappendを繰り返すを省略した作業です

  output.extend(list(reversed(stack)))
  return output

以上です、各所で解説されてるので詳細はそれらを参照してください

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1