四則演算をするスタックマシン(更新版)

先日書いたスタックマシンちゃんを少し修正しました。

ちょっとした改良をするだけのつもりでしたが、恥ずかしいミスを見つけたので、メモを残すことにしました。

直した点は、次のとおりです。

  1. オペレータがスタックをやり取りするようにした
  2. オペレータもオペランドトークンとして扱うことにした
  3. 2桁以上の数値に対応した
  4. テストデータの逆ポーランド記法が間違っていたので修正した(万死)
import java.util.ArrayDeque;
import java.util.Deque;

interface Token {
	Deque<Integer> execute(Deque<Integer> stack);
}

class Operand implements Token {
	Integer value;

	Operand(Integer val) {
		value = val;
	}

	@Override
	public Deque<Integer> execute(Deque<Integer> stack) {
		stack.push(value);
		System.out.println("[スタック] <= " + value);
		return stack;
	}
}

interface Operator extends Token {
	// Deque<Integer> execute(Deque<Integer> stack);
}

class Add implements Operator {
	@Override
	public Deque<Integer> execute(Deque<Integer> stack) {
		if (stack != null) {
			Integer num1 = stack.pop();
			System.out.println("[スタック] => " + num1);
			Integer num0 = stack.pop();
			System.out.println("[スタック] => " + num0);

			stack.push(num0 + num1);
			System.out.println("[スタック] <= " + (num0 + num1));
		}
		return stack;
	}
}

class Sub implements Operator {
	@Override
	public Deque<Integer> execute(Deque<Integer> stack) {
		if (stack != null) {
			Integer num1 = stack.pop();
			System.out.println("[スタック] => " + num1);
			Integer num0 = stack.pop();
			System.out.println("[スタック] => " + num0);

			stack.push(num0 - num1);
			System.out.println("[スタック] <= " + (num0 - num1));
		}
		return stack;
	}
}

class Mul implements Operator {
	@Override
	public Deque<Integer> execute(Deque<Integer> stack) {
		if (stack != null) {
			Integer num1 = stack.pop();
			System.out.println("[スタック] => " + num1);
			Integer num0 = stack.pop();
			System.out.println("[スタック] => " + num0);

			stack.push(num0 * num1);
			System.out.println("[スタック] <= " + (num0 * num1));
		}
		return stack;
	}
}

class Div implements Operator {
	@Override
	public Deque<Integer> execute(Deque<Integer> stack) {
		if (stack != null) {
			Integer num1 = stack.pop();
			System.out.println("[スタック] => " + num1);
			Integer num0 = stack.pop();
			System.out.println("[スタック] => " + num0);

			if (num1 != 0) {
				stack.push(num0 / num1);
				System.out.println("[スタック] <= " + (num0 / num1));
			}
		}
		return stack;
	}
}

public class StackMachine {

	protected Token createToken(String s) {
		Token result = null;
		if (s.equals("+")) {
			result = new Add();
		} else if (s.equals("-")) {
			result = new Sub();
		} else if (s.equals("*")) {
			result = new Mul();
		} else if (s.equals("/")) {
			result = new Div();
		} else {
			result = new Operand(Integer.valueOf(s));
		}
		return result;
	}

	protected int execute(String input) {
		Deque<Integer> stack = new ArrayDeque<>();

		String[] splited = input.split("\\s");
		if (splited != null && 0 < splited.length) {
			for (String s : splited) {
				Token token = createToken(s);
				token.execute(stack);
			}
		}
		return stack.pop();
	}

	public static void main(String[] args) {
		StackMachine stackMachine = new StackMachine();

		// (1 + 2) * 4 + (4 - 2) == 14
		System.out.println(stackMachine.execute("1 2 + 4 * 4 2 - +"));

		// (1 + 4) * (3 + 7) / 5 == 10
		System.out.println(stackMachine.execute("1 4 + 3 7 + * 5 /"));

		// 10 + 2 == 12
		System.out.println(stackMachine.execute("10 2 +"));

		// 10 / 2 == 5
		System.out.println(stackMachine.execute("10 2 /"));
	}
}

以下、詳細です。

1. オペレータがスタックをやり取りするようにした

id:skrb さんからコメントでアドバイスを頂きました。

Operatorはスタックから値を取り出して、結果をスタックに積むという意味からも
interface Operator {
Deque execute(Deque stack);
}
の方がいいのではないでしょうか。
こうしておけば、符号を変えるとか絶対値を求めるなどの2項演算子でない演算に対してもすぐに対応できるようになりますよ。

http://d.hatena.ne.jp/torazuka/20120829/stackmachine#c1346796099

たしかに、そうですね! ありがとうございます。

これによって、スタックへの値のプッシュとスタックからの値のポップが、「トークンを処理する側」から「処理されるトークンの側」に移りました。

2. オペレータもオペランドトークンとして扱うことにした

鍋谷さんのJava回答が、そんな感じでカッコよかったので。

パクったら真似したら、処理を分けていたif文が消えました!

3. 2桁以上の数値に対応した

これも、鍋谷さんのコードを読んでいて、ですよねーと思ったので修正。

スペース区切りの記法を受け取るようにして、2桁以上の数値も計算できるようにしました。

4. テストデータの逆ポーランド記法が間違っていたので修正した

修正中に除算の結果がおかしくなったので、何かと思ったら、テストデータの逆ポーランド記法を間違えていました。

// 元の式
(1 + 4) * (3 + 7) / 5 == 10

こんなふうにしていました。

// 誤った記法
1 4 + 3 7 + 5 * /

ダメジャン……。こうですよね。

// 正しい記法
1 4 + 3 7 + * 5 /

前回、誤った記法をよしとしていたのは、除算の処理でオペランドの順序を逆にするという二重のミスを犯していたためです。偶然答えが合っていたわけですね。おそろしい。

白状すると、上の式は、こちらのページからお借りしました。

    (1 + 4) * (3 + 7) / 5 ;通常
   1 4 + 3 7 + 5 * /    ;逆ポーランド記法

逆ポーランド記法への変換1

同じ間違いです。しかし、元のサイトでは、その次のページの解説で、正しい変換を紹介しています。

 手元 (1 + 4) * (3 + 7) / 5
 脇  ;スタート

(中略)

 手元 1 4 + 3 7 + * 5 /
 脇  ;残った演算子をつけて終了

逆ポーランド記法への変換2

テストを書かないとこうなるし、頭を使ってテストデータを作らないとこうなる、という例ですね。テストを書きましょう。>自分